plugin install

This commit is contained in:
Tony Volpe
2024-06-18 17:29:05 -04:00
parent e1aaedd1ae
commit 41f50eacc4
5880 changed files with 1057631 additions and 39681 deletions

View File

@@ -0,0 +1,3 @@
#gfwebapi-qrcode-container{
padding:5px;
}

View File

@@ -0,0 +1 @@
#gfwebapi-qrcode-container{padding:5px}

View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

View File

@@ -0,0 +1,168 @@
<?php
require_once( ABSPATH . '/wp-admin/includes/class-wp-list-table.php' );
class GF_API_Keys_Table extends WP_List_Table {
public function __construct( $args = array() ) {
parent::__construct( $args );
}
protected function get_table_classes() {
return array( 'widefat', 'striped', 'feeds', 'api_key_table' );
}
/**
* Returns an array of columns to be included in the list table.
*
* @since 2.4
* @since 2.4.22 Removed the key column.
*
* @return array
*/
function get_columns() {
return array(
'description' => esc_html__( 'Description', 'gravityforms' ),
'user' => esc_html__( 'User', 'gravityforms' ),
'permissions' => esc_html__( 'Permissions', 'gravityforms' ),
'last_access' => esc_html__( 'Last Access', 'gravityforms' ),
);
}
function prepare_items() {
$this->_column_headers = array( $this->get_columns(), array(), array() );
$this->items = GFWebAPI::get_api_keys();
}
function process_action() {
$action = rgget( 'single_action' );
if ( $action !== 'revoke' ) {
return;
}
check_admin_referer( 'gforms_revoke_key' );
$this->delete_api_key( rgget( 'key_id' ) );
}
function column_default( $item, $column_name ) {
return $item[ $column_name ];
}
function column_description( $item ) {
// create a nonce
$revoke_nonce = wp_create_nonce( 'gforms_revoke_key' );
$description = $item['description'];
$confirm = "javascript: if( ! confirm('WARNING: You are about to revoke this API Key. \'Cancel\' to stop, \'OK\' to revoke.')){ event.stopPropagation(); return false } ";
$nonce_url = wp_nonce_url( '?page=gf_settings&subview=gravityformswebapi', 'gf_revoke_key' );
$actions = array(
'edit' => '<a href="javascript:editKey( ' . $item['key_id'] . ' );">' . esc_html__( 'Edit', 'gravityforms' ) . '</a>',
'delete' => sprintf( '<a data-wp-lists="delete:the-list:key_row_%d::status=delete&action=delete_key&key=%d" onclick="%s" href="%s" class="submitdelete">Revoke</a>', absint( $item['key_id'] ), absint( $item['key_id'] ), $confirm, $nonce_url ),
);
return $description . $this->row_actions( $actions );
}
function get_edit_url( $key_id ) {
return sprintf( '?page=gf_settings&subview=gravityformswebapi&action=edit&key_id=%s', absint( $key_id ) );
}
function column_last_access( $item ) {
return empty( $item['last_access'] ) ? __( 'Never Accessed', 'gravityforms' ) : GFCommon::format_date( $item['last_access'], true, '', true );
}
function column_permissions( $item ) {
if ( $item['permissions'] == 'read_write' ) {
return 'Read/Write';
} else {
return ucwords( $item['permissions'] );
}
}
function no_items() {
echo '<div style="padding:10px;">' . sprintf( esc_html__( 'You don\'t have any API keys. Let\'s go %1$screate one%2$s!', 'gravityforms' ), '<a href="javascript:editKey( 0 );">', '</a>' ) . '</div>';
}
/**
* Display the table
*
* @since 3.1.0
*/
public function display() {
$singular = $this->_args['singular'];
$this->screen->render_screen_reader_content( 'heading_list' );
?>
<input type="hidden" name="single_action"/> <input type="hidden" name="action_args"/>
<table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
<thead>
<tr>
<?php $this->print_column_headers(); ?>
</tr>
</thead>
<tbody id="the-list"<?php
if ( $singular ) {
echo " data-wp-lists='list:$singular'";
} ?>>
<?php $this->display_rows_or_placeholder(); ?>
</tbody>
</table>
<div>
<a class="button" id="add_setting_button" href="javascript:editKey( 0 );">Add Key</a>
</div>
<?php
}
/**
* Generates content for a single row of the table
*
* @since 3.1.0
*
* @param object $item The current item
*/
public function single_row( $item ) {
echo "<tr id='key_row_{$item['key_id']}' >";
$this->single_row_columns( $item );
echo '</tr>';
}
public function output_styles() {
?>
<style>
table.gforms_form_settings .api_key_table td { padding-left: 10px; vertical-align: top; }
#add_setting_button { margin-top: 10px; }
tr:hover .row-actions { position: relative; }
.api_key_table tr:hover .row-actions { position: static; }
</style>
<?php
}
public function output_scripts() {
?>
<script type="text/javascript">
jQuery(document).ready(function () {
jQuery("#the-list").wpList();
});
</script>
<?php
}
}

View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

View File

@@ -0,0 +1,8 @@
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
(function(){var h=CryptoJS,j=h.lib.WordArray;h.enc.Base64={stringify:function(b){var e=b.words,f=b.sigBytes,c=this._map;b.clamp();b=[];for(var a=0;a<f;a+=3)for(var d=(e[a>>>2]>>>24-8*(a%4)&255)<<16|(e[a+1>>>2]>>>24-8*((a+1)%4)&255)<<8|e[a+2>>>2]>>>24-8*((a+2)%4)&255,g=0;4>g&&a+0.75*g<f;g++)b.push(c.charAt(d>>>6*(3-g)&63));if(e=c.charAt(64))for(;b.length%4;)b.push(e);return b.join("")},parse:function(b){var e=b.length,f=this._map,c=f.charAt(64);c&&(c=b.indexOf(c),-1!=c&&(e=c));for(var c=[],a=0,d=0;d<
e;d++)if(d%4){var g=f.indexOf(b.charAt(d-1))<<2*(d%4),h=f.indexOf(b.charAt(d))>>>6-2*(d%4);c[a>>>2]|=(g|h)<<24-8*(a%4);a++}return j.create(c,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();

View File

@@ -0,0 +1 @@
!function(){var r=CryptoJS,h=r.lib.WordArray;r.enc.Base64={stringify:function(r){var a=r.words,t=r.sigBytes,n=this._map;r.clamp(),r=[];for(var i=0;i<t;i+=3)for(var e=(a[i>>>2]>>>24-i%4*8&255)<<16|(a[i+1>>>2]>>>24-(i+1)%4*8&255)<<8|a[i+2>>>2]>>>24-(i+2)%4*8&255,f=0;f<4&&i+.75*f<t;f++)r.push(n.charAt(e>>>6*(3-f)&63));if(a=n.charAt(64))for(;r.length%4;)r.push(a);return r.join("")},parse:function(r){var a=r.length,t=this._map;(e=t.charAt(64))&&-1!=(e=r.indexOf(e))&&(a=e);for(var n,i,e=[],f=0,c=0;c<a;c++)c%4&&(n=t.indexOf(r.charAt(c-1))<<c%4*2,i=t.indexOf(r.charAt(c))>>>6-c%4*2,e[f>>>2]|=(n|i)<<24-f%4*8,f++);return h.create(e,f)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}}();

View File

@@ -0,0 +1,214 @@
function gfapiCalculateSig(stringToSign, privateKey) {
var hash = CryptoJS.HmacSHA1(stringToSign, privateKey);
var base64 = hash.toString(CryptoJS.enc.Base64);
return encodeURIComponent(base64);
}
jQuery(document).ready(function () {
jQuery("#gfwebapi-qrbutton").click(function () {
jQuery("#gfwebapi-qrcode-container").toggle();
var $img = jQuery('#gfwebapi-qrcode');
if ($img.length > 0)
$img.attr('src', ajaxurl + '?action=gfwebapi_qrcode&rnd=' + Date.now());
return false;
});
jQuery("#public_key, #private_key").on("keyup", function () {
jQuery("#gfwebapi-qrcode-container").html("The keys have changes. Please save the changes and try again.")
});
jQuery("#gfapi-url-builder-button").click(function (e) {
e.preventDefault();
var publicKey, privateKey, expiration, method, route, stringToSign, url, sig;
publicKey = jQuery("#public_key").val();
privateKey = jQuery("#private_key").val();
expiration = parseInt(jQuery("#gfapi-url-builder-expiration").val());
method = jQuery("#gfapi-url-builder-method").val();
route = jQuery("#gfapi-url-builder-route").val();
route = route.replace(/\/$/, ""); // remove trailing slash
var d = new Date;
var unixtime = parseInt(d.getTime() / 1000);
var future_unixtime = unixtime + expiration;
stringToSign = publicKey + ":" + method + ":" + route + ":" + future_unixtime;
sig = gfapiCalculateSig(stringToSign, privateKey);
url = gfapiBaseUrl + "/" + route + "/?api_key=" + publicKey + "&signature=" + sig + "&expires=" + future_unixtime;
jQuery('#gfapi-url-builder-generated-url').val(url);
return false;
});
var gfapiTesterAjaxRequest;
jQuery("#gfapi-url-tester-button").click(function (e) {
var $button = jQuery(this);
var $loading = jQuery("#gfapi-url-tester-loading");
var $results = jQuery("#gfapi-url-tester-results");
var url = jQuery('#gfapi-url-tester-url').val();
var method = jQuery('#gfapi-url-tester-method').val();
gfapiTesterAjaxRequest = jQuery.ajax({
url : url + "&test=1",
type : method,
dataType : 'json',
data : {},
beforeSend: function (xhr, opts) {
$button.attr('disabled', 'disabled');
$loading.show();
}
})
.done(function (data, textStatus, xhr) {
$button.removeAttr('disabled');
$loading.hide();
$results.html(xhr.status);
$results.fadeTo("fast", 1);
})
.fail(function (jqXHR) {
$button.removeAttr('disabled');
$loading.hide();
$results.fadeTo("fast", 1);
var msg;
$loading.hide();
if (msg == "abort") {
msg = "Request cancelled";
} else {
msg = jqXHR.status + ": " + jqXHR.statusText;
}
$results.html(msg);
});
return false;
});
// Reload page when modal is closed.
jQuery( 'body' ).on( 'thickbox:removed', function( e ) {
if ( modalSubmitted ) {
jQuery( '#gform-settings' ).submit();
}
} );
});
var modalSubmitted = false;
// Update key.
function saveKey() {
var requestData = {
action: 'gfwebapi_edit_key',
nonce: jQuery( '#gform-webapi-edit input[name="_wpnonce"]' ).val(),
key_id: jQuery( '#gform-webapi-key' ).val(),
description: jQuery( '#gform-webapi-description' ).val(),
user_id: jQuery( '#gform-webapi-user' ).val(),
permissions: jQuery( '#gform-webapi-permissions' ).val()
};
// Attempt to save key, display response.
jQuery.ajax(
{
url: ajaxurl,
type: 'POST',
dataType: 'json',
data: requestData,
success: function ( response ) {
// Get alert class.
var alertClass = response.success ? 'success' : 'error';
// Remove existing alert, add new alert.
jQuery( '#gform-webapi-edit .alert', document ).remove();
jQuery( '#gform-webapi-edit' ).prepend( '<div class="alert ' + alertClass + '">' + response.data.message + '</div>' );
// Display consumer key, secret.
if ( response.data.key ) {
jQuery( '#gform-webapi-description, #gform-webapi-user, #gform-webapi-permissions, #gform-webapi-truncated-key, #gform-webapi-last-access' ).parent().hide();
jQuery( '#gform-webapi-consumer-key' ).val( response.data.key.consumer_key ).parent().show();
jQuery( '#gform-webapi-consumer-secret' ).val( response.data.key.consumer_secret ).parent().show();
jQuery( '#gform-webapi-edit button' ).hide();
} else {
jQuery( '#gform-webapi-consumer-key' ).val( '' ).parent().hide();
jQuery( '#gform-webapi-consumer-secret' ).val( '' ).parent().hide();
}
}
}
);
modalSubmitted = true;
return false;
}
// Open edit key modal.
function editKey( keyId ) {
modalSubmitted = false;
// Remove existing alert, hide consumer key/secret, show button.
jQuery( '#gform-webapi-edit .alert', document ).remove();
jQuery( '#gform-webapi-consumer-key, #gform-webapi-consumer-secret' ).parent().hide();
jQuery( '#gform-webapi-edit button' ).show();
// If this is a new key, reset the form and open modal.
if ( keyId == 0 ) {
jQuery( '#gform-webapi-key' ).val( keyId );
jQuery( '#gform-webapi-description' ).val( '' );
jQuery( '#gform-webapi-user' ).val( jQuery( '#gform-webapi-user option:first-child' ).val() );
jQuery( '#gform-webapi-permissions' ).val( jQuery( '#gform-webapi-permissions option:first-child' ).val() );
jQuery( '#gform-webapi-edit button' ).html( jQuery( '#gform-webapi-edit button' ).data( 'add' ) );
jQuery( '#gform-webapi-key, #gform-webapi-description, #gform-webapi-user, #gform-webapi-permissions' ).parent().show();
jQuery( '#gform-webapi-truncated-key, #gform-webapi-last-access' ).parent().hide();
tb_show( 'Add New Key', '#TB_inline?width=375&height=330&inlineId=gform-webapi-edit-container' );
jQuery( '#gform-webapi-edit', document ).on( 'submit', saveKey );
return;
}
// Get key details, open modal.
jQuery.ajax(
{
url: ajaxurl,
type: 'GET',
dataType: 'json',
data: {
action: 'gfwebapi_edit_key',
key_id: keyId,
nonce: jQuery( '#gform-webapi-edit input[name="_wpnonce"]' ).val(),
},
success: function ( response ) {
// If key could not be retrieve, display error.
if ( ! response.success ) {
alert( response.data.message );
return;
}
var key = response.data.key;
jQuery( '#gform-webapi-key' ).val( key.key_id );
jQuery( '#gform-webapi-description' ).val( key.description );
jQuery( '#gform-webapi-user' ).val( key.user_id );
jQuery( '#gform-webapi-permissions' ).val( key.permissions );
jQuery( '#gform-webapi-truncated-key' ).html( key.consumer_key ).parent().show();
jQuery( '#gform-webapi-last-access' ).html( key.last_access ).parent().show();
jQuery( '#gform-webapi-edit button' ).html( jQuery( '#gform-webapi-edit button' ).data( 'edit' ) );
jQuery( '#gform-webapi-description, #gform-webapi-user, #gform-webapi-permissions, #gform-webapi-truncated-key, #gform-webapi-last-access' ).parent().show();
tb_show( 'Edit Key', '#TB_inline?width=375&height=445&inlineId=gform-webapi-edit-container' );
jQuery( '#gform-webapi-edit', document ).on( 'submit', saveKey );
}
}
);
}

View File

@@ -0,0 +1 @@
function gfapiCalculateSig(e,r){e=CryptoJS.HmacSHA1(e,r).toString(CryptoJS.enc.Base64);return encodeURIComponent(e)}jQuery(document).ready(function(){jQuery("#gfwebapi-qrbutton").click(function(){jQuery("#gfwebapi-qrcode-container").toggle();var e=jQuery("#gfwebapi-qrcode");return 0<e.length&&e.attr("src",ajaxurl+"?action=gfwebapi_qrcode&rnd="+Date.now()),!1}),jQuery("#public_key, #private_key").on("keyup",function(){jQuery("#gfwebapi-qrcode-container").html("The keys have changes. Please save the changes and try again.")}),jQuery("#gfapi-url-builder-button").click(function(e){e.preventDefault(),e=jQuery("#public_key").val(),r=jQuery("#private_key").val(),t=parseInt(jQuery("#gfapi-url-builder-expiration").val()),o=jQuery("#gfapi-url-builder-method").val(),a=(a=jQuery("#gfapi-url-builder-route").val()).replace(/\/$/,"");var r,a,i=new Date,i=parseInt(i.getTime()/1e3)+t,t=gfapiCalculateSig(e+":"+o+":"+a+":"+i,r),o=gfapiBaseUrl+"/"+a+"/?api_key="+e+"&signature="+t+"&expires="+i;return jQuery("#gfapi-url-builder-generated-url").val(o),!1}),jQuery("#gfapi-url-tester-button").click(function(e){var i=jQuery(this),t=jQuery("#gfapi-url-tester-loading"),o=jQuery("#gfapi-url-tester-results"),r=jQuery("#gfapi-url-tester-url").val(),a=jQuery("#gfapi-url-tester-method").val();return jQuery.ajax({url:r+"&test=1",type:a,dataType:"json",data:{},beforeSend:function(e,r){i.attr("disabled","disabled"),t.show()}}).done(function(e,r,a){i.removeAttr("disabled"),t.hide(),o.html(a.status),o.fadeTo("fast",1)}).fail(function(e){var r;i.removeAttr("disabled"),t.hide(),o.fadeTo("fast",1),t.hide(),r="abort"==r?"Request cancelled":e.status+": "+e.statusText,o.html(r)}),!1}),jQuery("body").on("thickbox:removed",function(e){modalSubmitted&&jQuery("#gform-settings").submit()})});var modalSubmitted=!1;function saveKey(){var e={action:"gfwebapi_edit_key",nonce:jQuery('#gform-webapi-edit input[name="_wpnonce"]').val(),key_id:jQuery("#gform-webapi-key").val(),description:jQuery("#gform-webapi-description").val(),user_id:jQuery("#gform-webapi-user").val(),permissions:jQuery("#gform-webapi-permissions").val()};return jQuery.ajax({url:ajaxurl,type:"POST",dataType:"json",data:e,success:function(e){var r=e.success?"success":"error";jQuery("#gform-webapi-edit .alert",document).remove(),jQuery("#gform-webapi-edit").prepend('<div class="alert '+r+'">'+e.data.message+"</div>"),(e.data.key?(jQuery("#gform-webapi-description, #gform-webapi-user, #gform-webapi-permissions, #gform-webapi-truncated-key, #gform-webapi-last-access").parent().hide(),jQuery("#gform-webapi-consumer-key").val(e.data.key.consumer_key).parent().show(),jQuery("#gform-webapi-consumer-secret").val(e.data.key.consumer_secret).parent().show(),jQuery("#gform-webapi-edit button")):(jQuery("#gform-webapi-consumer-key").val("").parent().hide(),jQuery("#gform-webapi-consumer-secret").val("").parent())).hide()}}),!(modalSubmitted=!0)}function editKey(e){modalSubmitted=!1,jQuery("#gform-webapi-edit .alert",document).remove(),jQuery("#gform-webapi-consumer-key, #gform-webapi-consumer-secret").parent().hide(),jQuery("#gform-webapi-edit button").show(),0==e?(jQuery("#gform-webapi-key").val(e),jQuery("#gform-webapi-description").val(""),jQuery("#gform-webapi-user").val(jQuery("#gform-webapi-user option:first-child").val()),jQuery("#gform-webapi-permissions").val(jQuery("#gform-webapi-permissions option:first-child").val()),jQuery("#gform-webapi-edit button").html(jQuery("#gform-webapi-edit button").data("add")),jQuery("#gform-webapi-key, #gform-webapi-description, #gform-webapi-user, #gform-webapi-permissions").parent().show(),jQuery("#gform-webapi-truncated-key, #gform-webapi-last-access").parent().hide(),tb_show("Add New Key","#TB_inline?width=375&height=330&inlineId=gform-webapi-edit-container"),jQuery("#gform-webapi-edit",document).on("submit",saveKey)):jQuery.ajax({url:ajaxurl,type:"GET",dataType:"json",data:{action:"gfwebapi_edit_key",key_id:e,nonce:jQuery('#gform-webapi-edit input[name="_wpnonce"]').val()},success:function(e){var r;e.success?(r=e.data.key,jQuery("#gform-webapi-key").val(r.key_id),jQuery("#gform-webapi-description").val(r.description),jQuery("#gform-webapi-user").val(r.user_id),jQuery("#gform-webapi-permissions").val(r.permissions),jQuery("#gform-webapi-truncated-key").html(r.consumer_key).parent().show(),jQuery("#gform-webapi-last-access").html(r.last_access).parent().show(),jQuery("#gform-webapi-edit button").html(jQuery("#gform-webapi-edit button").data("edit")),jQuery("#gform-webapi-description, #gform-webapi-user, #gform-webapi-permissions, #gform-webapi-truncated-key, #gform-webapi-last-access").parent().show(),tb_show("Edit Key","#TB_inline?width=375&height=445&inlineId=gform-webapi-edit-container"),jQuery("#gform-webapi-edit",document).on("submit",saveKey)):alert(e.data.message)}})}

View File

@@ -0,0 +1,17 @@
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
var CryptoJS=CryptoJS||function(g,l){var e={},d=e.lib={},m=function(){},k=d.Base={extend:function(a){m.prototype=this;var c=new m;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
p=d.WordArray=k.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=l?c:4*a.length},toString:function(a){return(a||n).stringify(this)},concat:function(a){var c=this.words,q=a.words,f=this.sigBytes;a=a.sigBytes;this.clamp();if(f%4)for(var b=0;b<a;b++)c[f+b>>>2]|=(q[b>>>2]>>>24-8*(b%4)&255)<<24-8*((f+b)%4);else if(65535<q.length)for(b=0;b<a;b+=4)c[f+b>>>2]=q[b>>>2];else c.push.apply(c,q);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
32-8*(c%4);a.length=g.ceil(c/4)},clone:function(){var a=k.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],b=0;b<a;b+=4)c.push(4294967296*g.random()|0);return new p.init(c,a)}}),b=e.enc={},n=b.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],f=0;f<a;f++){var d=c[f>>>2]>>>24-8*(f%4)&255;b.push((d>>>4).toString(16));b.push((d&15).toString(16))}return b.join("")},parse:function(a){for(var c=a.length,b=[],f=0;f<c;f+=2)b[f>>>3]|=parseInt(a.substr(f,
2),16)<<24-4*(f%8);return new p.init(b,c/2)}},j=b.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],f=0;f<a;f++)b.push(String.fromCharCode(c[f>>>2]>>>24-8*(f%4)&255));return b.join("")},parse:function(a){for(var c=a.length,b=[],f=0;f<c;f++)b[f>>>2]|=(a.charCodeAt(f)&255)<<24-8*(f%4);return new p.init(b,c)}},h=b.Utf8={stringify:function(a){try{return decodeURIComponent(escape(j.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return j.parse(unescape(encodeURIComponent(a)))}},
r=d.BufferedBlockAlgorithm=k.extend({reset:function(){this._data=new p.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=h.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,b=c.words,f=c.sigBytes,d=this.blockSize,e=f/(4*d),e=a?g.ceil(e):g.max((e|0)-this._minBufferSize,0);a=e*d;f=g.min(4*a,f);if(a){for(var k=0;k<a;k+=d)this._doProcessBlock(b,k);k=b.splice(0,a);c.sigBytes-=f}return new p.init(k,f)},clone:function(){var a=k.clone.call(this);
a._data=this._data.clone();return a},_minBufferSize:0});d.Hasher=r.extend({cfg:k.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){r.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(b,d){return(new a.init(d)).finalize(b)}},_createHmacHelper:function(a){return function(b,d){return(new s.HMAC.init(a,
d)).finalize(b)}}});var s=e.algo={};return e}(Math);
(function(){var g=CryptoJS,l=g.lib,e=l.WordArray,d=l.Hasher,m=[],l=g.algo.SHA1=d.extend({_doReset:function(){this._hash=new e.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(d,e){for(var b=this._hash.words,n=b[0],j=b[1],h=b[2],g=b[3],l=b[4],a=0;80>a;a++){if(16>a)m[a]=d[e+a]|0;else{var c=m[a-3]^m[a-8]^m[a-14]^m[a-16];m[a]=c<<1|c>>>31}c=(n<<5|n>>>27)+l+m[a];c=20>a?c+((j&h|~j&g)+1518500249):40>a?c+((j^h^g)+1859775393):60>a?c+((j&h|j&g|h&g)-1894007588):c+((j^h^
g)-899497514);l=g;g=h;h=j<<30|j>>>2;j=n;n=c}b[0]=b[0]+n|0;b[1]=b[1]+j|0;b[2]=b[2]+h|0;b[3]=b[3]+g|0;b[4]=b[4]+l|0},_doFinalize:function(){var d=this._data,e=d.words,b=8*this._nDataBytes,g=8*d.sigBytes;e[g>>>5]|=128<<24-g%32;e[(g+64>>>9<<4)+14]=Math.floor(b/4294967296);e[(g+64>>>9<<4)+15]=b;d.sigBytes=4*e.length;this._process();return this._hash},clone:function(){var e=d.clone.call(this);e._hash=this._hash.clone();return e}});g.SHA1=d._createHelper(l);g.HmacSHA1=d._createHmacHelper(l)})();
(function(){var g=CryptoJS,l=g.enc.Utf8;g.algo.HMAC=g.lib.Base.extend({init:function(e,d){e=this._hasher=new e.init;"string"==typeof d&&(d=l.parse(d));var g=e.blockSize,k=4*g;d.sigBytes>k&&(d=e.finalize(d));d.clamp();for(var p=this._oKey=d.clone(),b=this._iKey=d.clone(),n=p.words,j=b.words,h=0;h<g;h++)n[h]^=1549556828,j[h]^=909522486;p.sigBytes=b.sigBytes=k;this.reset()},reset:function(){var e=this._hasher;e.reset();e.update(this._iKey)},update:function(e){this._hasher.update(e);return this},finalize:function(e){var d=
this._hasher;e=d.finalize(e);d.reset();return d.finalize(this._oKey.clone().concat(e))}})})();

View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_API {
/**
* Contains an instance of this class, if available.
*
* @since 2.4-beta-1
*
* @var object $_instance If available, contains an instance of this class
*/
private static $_instance = null;
/**
* Returns an instance of this class, and stores it in the $_instance property.
*
* @since 2.4-beta-1
*
* @return GF_REST_API $_instance An instance of the GF_REST_API class
*/
public static function get_instance() {
if ( self::$_instance == null ) {
self::$_instance = new GF_REST_API();
}
return self::$_instance;
}
/**
* @since 2.4-beta-1
*/
private function __clone() {
} /* do nothing */
/**
* GF_REST_API constructor.
*
* @since 2.4-beta-1
*/
public function __construct() {
add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
}
/**
* Register REST API routes
*
* @since 2.4-beta-1
*/
public function register_rest_routes() {
$controllers = array(
'GF_REST_Entries_Controller',
'GF_REST_Entry_Properties_Controller',
'GF_REST_Entry_Notifications_Controller',
'GF_REST_Notes_Controller',
'GF_REST_Entry_Notes_Controller',
'GF_REST_Form_Entries_Controller',
'GF_REST_Form_Results_Controller',
'GF_REST_Form_Submissions_Controller',
'GF_REST_Form_Submissions_Validation_Controller',
'GF_REST_Forms_Controller',
'GF_REST_Feeds_Controller',
'GF_REST_Form_Feeds_Controller',
'GF_REST_Form_Field_Filters_Controller',
'GF_REST_Feed_Properties_Controller',
);
foreach ( $controllers as $controller ) {
$controller_obj = new $controller();
$controller_obj->register_routes();
}
}
}

View File

@@ -0,0 +1,896 @@
<?php
/**
* REST API Authentication
*
* @since 2.4.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API authentication class.
*
* @since 2.4-beta-1
*/
class GF_REST_Authentication {
/**
* Authentication error.
*
* @since 2.4-beta-1
*
* @var WP_Error
*/
protected $error = null;
/**
* Logged in user data.
*
* @since 2.4-beta-1
*
* @var stdClass
*/
protected $user = null;
/**
* Current auth method.
*
* @since 2.4-beta-1
*
* @var string
*/
protected $auth_method = '';
/**
* Initialize authentication actions.
*
* @since 2.4-beta-1
*/
public function __construct() {
$this->init();
}
/***
* Initializes REST authentication by adding appropriate filters
*
* @since 2.4-beta-1
*/
public function init() {
add_filter( 'determine_current_user', array( $this, 'authenticate' ), 15 );
add_filter( 'rest_authentication_errors', array( $this, 'authentication_fallback' ) );
add_filter( 'rest_authentication_errors', array( $this, 'check_authentication_error' ), 99 );
add_filter( 'rest_pre_dispatch', array( $this, 'check_user_permissions' ), 99, 3 );
add_filter( 'rest_post_dispatch', array( $this, 'send_unauthorized_headers' ), 50 );
}
/**
* If request is to our API and we did not set any authentication errors, override authentication errors that may
* be set by other REST API authenticators.
*
* @since 2.4-beta-1
*
* @deprecated 2.4.22
*
* @param $errors
*
* @return null
*/
public function override_rest_authentication_errors( $errors ) {
_deprecated_function( __METHOD__, '2.4.22', 'GF_REST_Authentication::check_authentication_error' );
if ( $this->is_request_to_rest_api() && ! $this->get_error() ) {
return null;
}
return $errors;
}
/**
* Check if is request to Gravity Forms REST API.
*
* @since 2.4-beta-1
*
* @return bool Returns true if this is a request to the Gravity Forms REST API. False otherwise
*/
protected function is_request_to_rest_api() {
if ( empty( $_SERVER['REQUEST_URI'] ) || ! ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
return false;
}
$rest_prefix = trailingslashit( rest_get_url_prefix() );
// Check if our endpoint.
$is_gf_endpoint = ( strpos( $_SERVER['REQUEST_URI'], $rest_prefix . 'gf/' ) !== false );
// Allow third party plugins use our authentication methods.
$third_party = ( false !== strpos( $_SERVER['REQUEST_URI'], $rest_prefix . 'gf-' ) );
if ( has_filter( 'gform_is_request_to_rest_api' ) ) {
$this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_is_request_to_rest_api.' );
}
/**
* Allows filtering of whether or not the current request is a request to the Gravity Forms REST API.
*
* @param bool $is_rest_api_request True if this is a request to the Gravity Forms REST API. False if not.
*/
return apply_filters( 'gform_is_request_to_rest_api', $is_gf_endpoint || $third_party );
}
/**
* Authenticate user.
*
* @since 2.4-beta-1
*
* @param int|false $user_id User ID if one has been determined, false otherwise.
* @return int|false Returns the User ID of the authenticated user.
*/
public function authenticate( $user_id ) {
if ( ! $this->is_request_to_rest_api() ) {
return $user_id;
}
if ( ! empty( $user_id ) ) {
$this->log_debug( __METHOD__ . sprintf( '(): User #%d already authenticated.', $user_id ) );
return $user_id;
}
$this->clear_errors();
$this->log_debug( __METHOD__ . '(): Running.' );
if ( is_ssl() ) {
$user_id = $this->perform_basic_authentication();
if ( $user_id ) {
return $user_id;
}
$user_id = $this->perform_application_password_authentication();
if ( $user_id ) {
return $user_id;
}
}
return $this->perform_oauth_authentication();
}
/**
* Authenticate the user if authentication wasn't performed during the determine_current_user action.
*
* Necessary in cases where wp_get_current_user() is called before Gravity Forms is loaded.
*
* @since 2.4.22
*
* @param WP_Error|null|bool $error Error data.
*
* @return WP_Error|null|bool
*/
public function authentication_fallback( $error ) {
if ( ! empty( $error ) ) {
// Another plugin has already declared a failure.
return $error;
}
if ( empty( $this->error ) && empty( $this->auth_method ) && empty( $this->user ) && 0 === get_current_user_id() ) {
// Authentication hasn't occurred during `determine_current_user`, so check auth.
$user_id = $this->authenticate( false );
if ( $user_id ) {
wp_set_current_user( $user_id );
return true;
}
}
return $error;
}
/**
* Check for authentication error.
*
* @since 2.4-beta-1
*
* @param WP_Error|null|bool $error Error data.
*
* @return WP_Error|null|bool
*/
public function check_authentication_error( $error ) {
if ( ! $this->is_request_to_rest_api() || $_SERVER['REQUEST_METHOD'] === 'OPTIONS' ) {
// Pass through OPTIONS requests or those to non-GF endpoints.
return $error;
}
$error = $this->get_error();
if ( empty( $error ) ) {
// rest_handle_options_request() will be called by $this->check_user_permissions().
remove_filter( 'rest_pre_dispatch', 'rest_handle_options_request' );
// Indicate auth succeeded.
return true;
}
return $error;
}
/**
* Set authentication error.
*
* @since 2.4-beta-1
*
* @param WP_Error $error Authentication error data.
*/
protected function set_error( $error ) {
// Reset user.
$this->user = null;
$this->error = $error;
$this->log_error( __METHOD__ . '(): ' . json_encode( $error ) );
}
/***
* Clears all authentication errors and resets user.
*
* @since 2.4-beta-1
*/
protected function clear_errors() {
// Reset user.
$this->user = null;
$this->error = null;
}
/**
* Get authentication error.
*
* @since 2.4-beta-1
*
* @return WP_Error|null.
*/
protected function get_error() {
return $this->error;
}
/**
* Sets the user property for the authenticated user and clears the error property.
*
* @since 2.4.22
*
* @param object $user An object containing the user id and some other optional properties.
*
* @return int The ID of the authenticated user.
*/
protected function set_user( $user ) {
$this->user = $user;
$this->error = null;
return $this->user->user_id;
}
/**
* Attempts to authenticate the request using the application password feature introduced in WordPress 5.6.
*
* @since 2.4.22
*
* @return false|int False or the ID of the authenticated user.
*/
private function perform_application_password_authentication() {
if ( ! function_exists( 'wp_validate_application_password' ) ) {
return false;
}
$this->log_debug( __METHOD__ . '(): Running.' );
$this->auth_method = 'application_password';
$user_id = wp_validate_application_password( false );
if ( empty( $user_id ) ) {
global $wp_rest_application_password_status;
if ( is_wp_error( $wp_rest_application_password_status ) ) {
$this->set_error( new WP_Error( 'gform_rest_authentication_error', $wp_rest_application_password_status->get_error_message(), array( 'status' => 401 ) ) );
}
$this->log_error( __METHOD__ . '(): Aborting; user not found.' );
return false;
}
$this->log_debug( __METHOD__ . '(): Valid.' );
return $this->set_user( (object) array( 'user_id' => $user_id ) );
}
/**
* Basic Authentication.
*
* SSL-encrypted requests are not subject to sniffing or man-in-the-middle
* attacks, so the request can be authenticated by simply looking up the user
* associated with the given consumer key and confirming the consumer secret
* provided is valid.
*
* @since 2.4-beta-1
*
* @return int|bool Returs the authenticated user's User ID if successfull. Otherwise, returns false.
*/
private function perform_basic_authentication() {
$this->log_debug( __METHOD__ . '(): Running.' );
$this->auth_method = 'basic_auth';
$consumer_key = '';
$consumer_secret = '';
// If the $_GET parameters are present, use those first.
if ( ! empty( $_GET['consumer_key'] ) && ! empty( $_GET['consumer_secret'] ) ) {
$consumer_key = $_GET['consumer_key']; // WPCS: sanitization ok.
$consumer_secret = $_GET['consumer_secret']; // WPCS: sanitization ok.
}
// If the above is not present, we will do full basic auth.
if ( ! $consumer_key && ! empty( $_SERVER['PHP_AUTH_USER'] ) && ! empty( $_SERVER['PHP_AUTH_PW'] ) ) {
$consumer_key = $_SERVER['PHP_AUTH_USER']; // WPCS: sanitization ok.
$consumer_secret = $_SERVER['PHP_AUTH_PW']; // WPCS: sanitization ok.
}
// Stop if don't have any key.
if ( ! $consumer_key || ! $consumer_secret ) {
$this->log_error( __METHOD__ . '(): Aborting; credentials not found.' );
return false;
}
// Get user data.
$user = $this->get_user_data_by_consumer_key( $consumer_key );
if ( empty( $user ) ) {
$this->log_error( __METHOD__ . '(): Aborting; user not found.' );
return false;
}
// Validate user secret.
if ( ! hash_equals( $user->consumer_secret, $consumer_secret ) ) {
$this->set_error( new WP_Error( 'gform_rest_authentication_error', __( 'Consumer secret is invalid.', 'gravityforms' ), array( 'status' => 401 ) ) );
return false;
}
$this->log_debug( __METHOD__ . '(): Valid.' );
return $this->set_user( $user );
}
/**
* Parse the Authorization header into parameters.
*
* @since 2.4-beta-1
*
* @param string $header Authorization header value (not including "Authorization: " prefix).
*
* @return array Map of parameter values.
*/
public function parse_header( $header ) {
if ( 'OAuth ' !== substr( $header, 0, 6 ) ) {
return array();
}
// From OAuth PHP library, used under MIT license.
$params = array();
if ( preg_match_all( '/(oauth_[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches ) ) {
foreach ( $matches[1] as $i => $h ) {
$params[ $h ] = urldecode( empty( $matches[3][ $i ] ) ? $matches[4][ $i ] : $matches[3][ $i ] );
}
if ( isset( $params['realm'] ) ) {
unset( $params['realm'] );
}
}
return $params;
}
/**
* Get the authorization header.
*
* On certain systems and configurations, the Authorization header will be
* stripped out by the server or PHP. Typically this is then used to
* generate `PHP_AUTH_USER`/`PHP_AUTH_PASS` but not passed on. We use
* `getallheaders` here to try and grab it out instead.
*
* @since 2.4-beta-1
*
* @return string Authorization header if set.
*/
public function get_authorization_header() {
if ( ! empty( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
return wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ); // WPCS: sanitization ok.
}
if ( function_exists( 'getallheaders' ) ) {
$headers = getallheaders();
// Check for the authoization header case-insensitively.
foreach ( $headers as $key => $value ) {
if ( 'authorization' === strtolower( $key ) ) {
return $value;
}
}
}
return '';
}
/**
* Get oAuth parameters from $_GET, $_POST or request header.
*
* @since 2.4-beta-1
*
* @return array|WP_Error
*/
public function get_oauth_parameters() {
$params = array_merge( $_GET, $_POST ); // WPCS: CSRF ok.
$params = wp_unslash( $params );
$header = $this->get_authorization_header();
if ( ! empty( $header ) ) {
// Trim leading spaces.
$header = trim( $header );
$header_params = $this->parse_header( $header );
if ( ! empty( $header_params ) ) {
$params = array_merge( $params, $header_params );
}
}
$param_names = array(
'oauth_consumer_key',
'oauth_timestamp',
'oauth_nonce',
'oauth_signature',
'oauth_signature_method',
);
$errors = array();
$have_one = false;
// Check for required OAuth parameters.
foreach ( $param_names as $param_name ) {
if ( empty( $params[ $param_name ] ) ) {
$errors[] = $param_name;
} else {
$have_one = true;
}
}
// All keys are missing, so we're probably not even trying to use OAuth.
if ( ! $have_one ) {
return array();
}
// If we have at least one supplied piece of data, and we have an error,
// then it's a failed authentication.
if ( ! empty( $errors ) ) {
$message = sprintf(
/* translators: %s: amount of errors */
_n( 'Missing OAuth parameter %s', 'Missing OAuth parameters %s', count( $errors ), 'gravityforms' ),
implode( ', ', $errors )
);
$this->set_error( new WP_Error( 'gform_rest_authentication_missing_parameter', $message, array( 'status' => 401 ) ) );
return array();
}
return $params;
}
/**
* Perform OAuth 1.0a "one-legged" (http://oauthbible.com/#oauth-10a-one-legged) authentication for non-SSL requests.
*
* This is required so API credentials cannot be sniffed or intercepted when making API requests over plain HTTP.
*
* This follows the spec for simple OAuth 1.0a authentication (RFC 5849) as closely as possible, with two exceptions:
*
* 1) There is no token associated with request/responses, only consumer keys/secrets are used.
*
* 2) The OAuth parameters are included as part of the request query string instead of part of the Authorization header,
* This is because there is no cross-OS function within PHP to get the raw Authorization header.
*
* @link http://tools.ietf.org/html/rfc5849 for the full spec.
*
* @since 2.4-beta-1
*
* @return int|bool
*/
private function perform_oauth_authentication() {
$this->log_debug( __METHOD__ . '(): Running.' );
$this->auth_method = 'oauth1';
$params = $this->get_oauth_parameters();
if ( empty( $params ) ) {
$this->log_error( __METHOD__ . '(): Aborting; OAuth parameters not found.' );
return false;
}
// Fetch WP user by consumer key.
$user = $this->get_user_data_by_consumer_key( $params['oauth_consumer_key'] );
if ( empty( $user ) ) {
$this->set_error( new WP_Error( 'gform_rest_authentication_error', __( 'Consumer key is invalid.', 'gravityforms' ), array( 'status' => 401 ) ) );
return false;
}
// Perform OAuth validation.
$signature = $this->check_oauth_signature( $user, $params );
if ( is_wp_error( $signature ) ) {
$this->set_error( $signature );
return false;
}
$timestamp_and_nonce = $this->check_oauth_timestamp_and_nonce( $user, $params['oauth_timestamp'], $params['oauth_nonce'] );
if ( is_wp_error( $timestamp_and_nonce ) ) {
$this->set_error( $timestamp_and_nonce );
return false;
}
$this->log_debug( __METHOD__ . '(): Valid.' );
return $this->set_user( $user );
}
/**
* Verify that the consumer-provided request signature matches our generated signature,
* this ensures the consumer has a valid key/secret.
*
* @since 2.4-beta-1
*
* @param stdClass $user User data.
* @param array $params The request parameters.
* @return true|WP_Error
*/
private function check_oauth_signature( $user, $params ) {
$http_method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( $_SERVER['REQUEST_METHOD'] ) : ''; // WPCS: sanitization ok.
$request_path = isset( $_SERVER['REQUEST_URI'] ) ? parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ) : ''; // WPCS: sanitization ok.
$wp_base = get_home_url( null, '/', 'relative' );
if ( substr( $request_path, 0, strlen( $wp_base ) ) === $wp_base ) {
$request_path = substr( $request_path, strlen( $wp_base ) );
}
$base_request_uri = rawurlencode( get_home_url( null, $request_path, is_ssl() ? 'https' : 'http' ) );
// Get the signature provided by the consumer and remove it from the parameters prior to checking the signature.
$consumer_signature = rawurldecode( str_replace( ' ', '+', $params['oauth_signature'] ) );
unset( $params['oauth_signature'] );
// Sort parameters.
if ( ! uksort( $params, 'strcmp' ) ) {
return new WP_Error( 'gform_rest_authentication_error', __( 'Invalid signature - failed to sort parameters.', 'gravityforms' ), array( 'status' => 401 ) );
}
// Normalize parameter key/values.
$params = $this->normalize_parameters( $params );
$query_string = implode( '%26', $this->join_with_equals_sign( $params ) ); // Join with ampersand.
$string_to_sign = $http_method . '&' . $base_request_uri . '&' . $query_string;
if ( 'HMAC-SHA1' !== $params['oauth_signature_method'] && 'HMAC-SHA256' !== $params['oauth_signature_method'] ) {
return new WP_Error( 'gform_rest_authentication_error', __( 'Invalid signature - signature method is invalid.', 'gravityforms' ), array( 'status' => 401 ) );
}
$hash_algorithm = strtolower( str_replace( 'HMAC-', '', $params['oauth_signature_method'] ) );
$secret = $user->consumer_secret . '&';
$signature = base64_encode( hash_hmac( $hash_algorithm, $string_to_sign, $secret, true ) );
if ( ! hash_equals( $signature, $consumer_signature ) ) {
$this->log_debug( __METHOD__ . '(): Signature base: ' . $string_to_sign );
return new WP_Error( 'gform_rest_authentication_error', __( 'Invalid signature - provided signature does not match.', 'gravityforms' ), array( 'status' => 401 ) );
}
return true;
}
/**
* Creates an array of urlencoded strings out of each array key/value pairs.
*
* @since 2.4-beta-1
*
* @param array $params Array of parameters to convert.
* @param array $query_params Array to extend.
* @param string $key Optional Array key to append.
* @return string Array of urlencoded strings.
*/
private function join_with_equals_sign( $params, $query_params = array(), $key = '' ) {
foreach ( $params as $param_key => $param_value ) {
if ( $key ) {
$param_key = $key . '%5B' . $param_key . '%5D'; // Handle multi-dimensional array.
}
if ( is_array( $param_value ) ) {
$query_params = $this->join_with_equals_sign( $param_value, $query_params, $param_key );
} else {
$string = $param_key . '=' . $param_value; // Join with equals sign.
$query_params[] = $this->urlencode_rfc3986( $string );
}
}
return $query_params;
}
/**
* Normalize each parameter by assuming each parameter may have already been
* encoded, so attempt to decode, and then re-encode according to RFC 3986.
*
* Note both the key and value is normalized so a filter param like:
*
* 'filter[period]' => 'week'
*
* is encoded to:
*
* 'filter%255Bperiod%255D' => 'week'
*
* This conforms to the OAuth 1.0a spec which indicates the entire query string
* should be URL encoded.
*
* @since 2.4-beta-1
*
* @see rawurlencode()
* @param array $parameters Un-normalized parameters.
* @return array Normalized parameters.
*/
private function normalize_parameters( $parameters ) {
$keys = $this->urlencode_rfc3986( array_keys( $parameters ) );
$values = $this->urlencode_rfc3986( array_values( $parameters ) );
$parameters = array_combine( $keys, $values );
return $parameters;
}
/**
* Verify that the timestamp and nonce provided with the request are valid. This prevents replay attacks where
* an attacker could attempt to re-send an intercepted request at a later time.
*
* - A timestamp is valid if it is within 15 minutes of now.
* - A nonce is valid if it has not been used within the last 15 minutes.
*
* @since 2.4-beta-1
*
* @param stdClass $user User data.
* @param int $timestamp The unix timestamp for when the request was made.
* @param string $nonce A unique (for the given user) 32 alphanumeric string, consumer-generated.
* @return bool|WP_Error
*/
private function check_oauth_timestamp_and_nonce( $user, $timestamp, $nonce ) {
global $wpdb;
$valid_window = 15 * 60; // 15 minute window.
if ( ( $timestamp < time() - $valid_window ) || ( $timestamp > time() + $valid_window ) ) {
return new WP_Error( 'gform_rest_authentication_error', __( 'Invalid timestamp.', 'gravityforms' ), array( 'status' => 401 ) );
}
$used_nonces = maybe_unserialize( $user->nonces );
if ( empty( $used_nonces ) ) {
$used_nonces = array();
}
if ( in_array( $nonce, $used_nonces ) ) {
return new WP_Error( 'gform_rest_authentication_error', __( 'Invalid nonce - nonce has already been used.', 'gravityforms' ), array( 'status' => 401 ) );
}
$used_nonces[ $timestamp ] = $nonce;
// Remove expired nonces.
foreach ( $used_nonces as $nonce_timestamp => $nonce ) {
if ( $nonce_timestamp < ( time() - $valid_window ) ) {
unset( $used_nonces[ $nonce_timestamp ] );
}
}
$used_nonces = maybe_serialize( $used_nonces );
$wpdb->update(
$wpdb->prefix . 'gf_rest_api_keys',
array( 'nonces' => $used_nonces ),
array( 'key_id' => $user->key_id ),
array( '%s' ),
array( '%d' )
);
return true;
}
/**
* Return the user data for the given consumer_key.
*
* @since 2.4-beta-1
*
* @param string $consumer_key Consumer key.
* @return array
*/
private function get_user_data_by_consumer_key( $consumer_key ) {
global $wpdb;
$consumer_key = GFWebAPI::api_hash( sanitize_text_field( $consumer_key ) );
$user = $wpdb->get_row(
$wpdb->prepare(
"
SELECT key_id, user_id, permissions, consumer_key, consumer_secret, nonces
FROM {$wpdb->prefix}gf_rest_api_keys
WHERE consumer_key = %s
", $consumer_key
)
);
return $user;
}
/**
* Check that the API keys provided have the proper key-specific permissions to either read or write API resources.
*
* @since 2.4-beta-1
*
* @param string $method Request method.
* @return bool|WP_Error
*/
private function check_permissions( $method ) {
if ( ! $this->is_gf_auth_method() ) {
return true;
}
$permissions = $this->user->permissions;
switch ( $method ) {
case 'HEAD':
case 'GET':
if ( 'read' !== $permissions && 'read_write' !== $permissions ) {
return new WP_Error( 'gform_rest_authentication_error', __( 'The API key provided does not have read permissions.', 'gravityforms' ), array( 'status' => 401 ) );
}
break;
case 'POST':
case 'PUT':
case 'PATCH':
case 'DELETE':
if ( 'write' !== $permissions && 'read_write' !== $permissions ) {
return new WP_Error( 'gform_rest_authentication_error', __( 'The API key provided does not have write permissions.', 'gravityforms' ), array( 'status' => 401 ) );
}
break;
case 'OPTIONS':
return true;
default:
return new WP_Error( 'gform_rest_authentication_error', __( 'Unknown request method.', 'gravityforms' ), array( 'status' => 401 ) );
}
return true;
}
/**
* Updated API Key last access datetime.
*
* @since 2.4-beta-1
*
*/
private function update_last_access() {
if ( ! $this->is_gf_auth_method() ) {
return;
}
global $wpdb;
$wpdb->update(
$wpdb->prefix . 'gf_rest_api_keys',
array( 'last_access' => current_time( 'mysql' ) ),
array( 'key_id' => $this->user->key_id ),
array( '%s' ),
array( '%d' )
);
}
/**
* If the consumer_key and consumer_secret $_GET parameters are NOT provided
* and the Basic auth headers are either not present or the consumer secret does not match the consumer
* key provided, then return the correct Basic headers and an error message.
*
* @since 2.4-beta-1
*
* @param WP_REST_Response $response Current response being served.
* @return WP_REST_Response
*/
public function send_unauthorized_headers( $response ) {
if ( is_wp_error( $this->get_error() ) && 'basic_auth' === $this->auth_method ) {
$auth_message = __( 'Gravity Forms API. Use a consumer key in the username field and a consumer secret in the password field.', 'gravityforms' );
$response->header( 'WWW-Authenticate', 'Basic realm="' . $auth_message . '"', true );
}
return $response;
}
/**
* Check for user permissions and register last access.
*
* @since 2.4-beta-1
*
* @param mixed $result Response to replace the requested version with.
* @param WP_REST_Server $server Server instance.
* @param WP_REST_Request $request Request used to generate the response.
* @return mixed
*/
public function check_user_permissions( $result, $server, $request ) {
if ( ! $this->user ) {
return $result;
}
$this->log_debug( sprintf( '%s(): Running for user #%d.', __METHOD__, $this->user->user_id ) );
// Check API Key permissions.
$allowed = $this->check_permissions( $request->get_method() );
if ( is_wp_error( $allowed ) ) {
$this->log_error( __METHOD__ . '(): ' . print_r( $allowed, true ) );
return $allowed;
}
// Register last access.
$this->update_last_access();
$this->log_debug( __METHOD__ . '(): Permissions valid.' );
return rest_handle_options_request( null, $server, $request );
}
/**
* Encodes a value according to RFC 3986.
* Supports multidimensional arrays.
*
* @since 2.4-beta-1
*
* @param string|array $value The value to encode.
* @return string|array Encoded values.
*/
public function urlencode_rfc3986( $value ) {
if ( is_array( $value ) ) {
return array_map( array( $this, 'urlencode_rfc3986' ), $value );
} else {
return str_replace( array( '+', '%7E' ), array( ' ', '~' ), rawurlencode( $value ) );
}
}
/**
* Write an error message to the Gravity Forms API log.
*
* @since 2.4.11
*
* @param string $message The message to be logged.
*/
public function log_error( $message ) {
GFAPI::log_error( $message );
}
/**
* Write a debug message to the Gravity Forms API log.
*
* @since 2.4.11
*
* @param string $message The message to be logged.
*/
public function log_debug( $message ) {
GFAPI::log_debug( $message );
}
/**
* Determines if the request is authenticated using credentials generated by Gravity Forms.
*
* @since 2.4.22
*
* @return bool
*/
private function is_gf_auth_method() {
return in_array( $this->auth_method, array( 'basic_auth', 'oauth1' ) );
}
}
new GF_REST_Authentication();

View File

@@ -0,0 +1,884 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Manages the entry results cache expiry and rebuild.
*
* GF_Results_Cache::get_results() will attempt to calculate the results inside the time_limit arg.
* If incomplete then a WP Cron task is kicked off.
* If the cron task is unable to finish within time_limit_cron then another task is scheduled until the results are complete.
*
* @package Gravity Forms
* @subpackage GF_Results_Cache
* @access public
*/
class GF_Results_Cache {
/**
* GF_Results_Cache constructor.
*
* @since 2.4-beta-1
*/
public function __construct() {
if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
add_action( 'gravityforms_results_cron', array( $this, 'results_cron' ), 10, 4 );
return;
}
add_action( 'gform_entry_created', array( $this, 'entry_created' ), 10, 2 );
add_action( 'gform_after_update_entry', array( $this, 'entry_updated' ), 10, 2 );
add_action( 'gform_update_status', array( $this, 'update_entry_status' ), 10, 2 );
add_action( 'gform_after_save_form', array( $this, 'after_save_form' ), 10, 2 );
}
/**
* Contains an instance of this class, if available.
*
* @since 2.4-beta-1
*
* @var GF_Results_Cache $_instance If available, contains an instance of this class.
*/
private static $_instance = null;
/**
* Returns an instance of this class, and stores it in the $_instance property.
*
* @since 2.4-beta-1
*
* @return GF_Results_Cache $_instance
*/
public static function get_instance() {
if ( self::$_instance == null ) {
self::$_instance = new GF_Results_Cache();
}
return self::$_instance;
}
/**
* Returns the default args for the results cache process.
*
* @since 2.4-beta-1
*
* time_limit - Max seconds allowed per batch.
* time_limit_cron - Max seconds allowed per batch while inside the cron task.
* page_size - Page size for each batch search results.
* callbacks - An array of callbacks. One supported callback: 'calculation' $cache_data, $form, $fields, $entries
* wait - Time in seconds to wait between each cron task.
* field_ids - An array of field IDs to include in the results.
*
* @return array
*/
public function get_default_args() {
return array(
'time_limit' => 15, // Max seconds for the initial attempt.
'time_limit_cron' => 15, // Max seconds for the cron task.
'page_size' => 100,
'callbacks' => array(),
'wait' => 10,
'field_ids' => false,
'labels' => true,
);
}
/**
* Callback for the gform_update_status action.
*
* @since 2.4-beta-1
*
* @param $entry_id
*/
public function update_entry_status( $entry_id ) {
$entry = GFAPI::get_entry( $entry_id );
$form_id = $entry['form_id'];
$form = GFFormsModel::get_form_meta( $form_id );
$this->maybe_update_results_cache_meta( $form );
}
/**
* Callback for the gform_after_update_entry action.
*
* @since 2.4-beta-1
*
* @param $form
* @param $entry_id
*/
public function entry_updated( $form, $entry_id ) {
$this->maybe_update_results_cache_meta( $form );
}
/**
* Callback for the gform_entry_created action.
*
* @since 2.4-beta-1
*
* @param $entry
* @param $form
*/
public function entry_created( $entry, $form ) {
$this->maybe_update_results_cache_meta( $form );
}
/**
* Callback for the gform_after_save_form action.
*
* @since 2.4-beta-1
*
* @param $form
* @param $is_new
*/
public function after_save_form( $form, $is_new ) {
if ( $is_new ) {
return;
}
$form_id = $form['id'];
// only need to update the cache meta when cached results exist
if ( ! $this->cached_results_exists( $form_id ) ) {
return;
}
$fields = rgar( $form, 'fields' );
$current_fields_hash = wp_hash( json_encode( $fields ) );
$cache_meta = $this->get_results_cache_meta( $form_id );
$cached_fields_hash = rgar( $cache_meta, 'fields_hash' );
if ( ! hash_equals( $current_fields_hash, $cached_fields_hash ) ) {
// delete the meta for this form
$this->delete_results_cache_meta( $form_id );
// delete all cached results for this form
$this->delete_cached_results( $form_id );
}
}
/**
* When entries are added or updated the cache needs to be expired and rebuilt.
*
* This cache meta records the last updated time for each form and a hash of the fields array.
* Each time results are requested this value is checked to make sure the cache is still valid.
*
* @since 2.4-beta-1
*
* @param $form
*/
private function maybe_update_results_cache_meta( $form ) {
$form_id = $form['id'];
// Only need to update the cache meta when cached results exist.
if ( ! $this->cached_results_exists( $form_id ) ) {
return;
}
$this->update_results_cache_meta( $form_id, rgar( $form, 'fields' ) );
}
/**
* Updates the results cache meta containing a hash of the all the fields and a timestamp.
*
* @since 2.4-beta-1
*
* @param $form_id
* @param $fields
*/
private function update_results_cache_meta( $form_id, $fields ) {
$data = array(
'fields_hash' => wp_hash( json_encode( $fields ) ),
'timestamp' => time(),
);
$key = $this->get_results_cache_meta_key( $form_id );
$this->update_results_cache( $key, $data );
}
/**
* Deletes the cache meta.
*
* @since 2.4-beta-1
*
*
* @param $form_id
*/
private function delete_results_cache_meta( $form_id ) {
$key = $this->get_results_cache_meta_key( $form_id );
delete_option( $key );
}
/**
* Returns the cache meta key.
*
* @since 2.4-beta-1
*
* @param $form_id
*
* @return string
*/
private function get_results_cache_meta_key( $form_id ) {
$key = 'gf-results-cache-meta-form-' . $form_id;
return $key;
}
/**
* Returns the cache meta.
*
* @since 2.4-beta-1
*
* @param $form_id
*
* @return mixed|void
*/
private function get_results_cache_meta( $form_id ) {
$key = $this->get_results_cache_meta_key( $form_id );
$cache_meta = get_option( $key );
return $cache_meta;
}
/**
* Updates the results cache.
*
* @since 2.4-beta-1
*
* @param $key
* @param $data
*
* @return bool
*/
private function update_results_cache( $key, $data ) {
/* From: https://codex.wordpress.org/Function_Reference/add_option
*
* Until version 4.2, you could not specify autoload='no' if you use update_option().
* If you need to specify autoload='no', and you are not sure whether the option already exists,
* then call delete_option() first before calling add_option().
*/
delete_option( $key );
$result = add_option( $key, $data, '', 'no' );
return $result;
}
/**
* Checks whether a cache exists for the given form ID.
*
* @since 2.4-beta-1
*
* @param $form_id
*
* @return bool
*/
private function cached_results_exists( $form_id ) {
global $wpdb;
$key = $this->get_results_cache_key_prefix( $form_id );
$key = '%' . GFCommon::esc_like( $key ) . '%';
$sql = $wpdb->prepare( "SELECT count(option_id) FROM $wpdb->options WHERE option_name LIKE %s", $key );
$result = $wpdb->get_var( $sql );
return $result > 0;
}
/**
* Deletes all the cached results for the given form ID.
*
* @since 2.4-beta-1
*
* @param $form_id
*
* @return false|int|void
*/
public function delete_cached_results( $form_id ) {
global $wpdb;
$form = GFAPI::get_form( $form_id );
if ( ! ( $form ) || ! is_array( $form ) ) {
return;
}
$key = $this->get_results_cache_key_prefix( $form_id );
$key = '%' . GFCommon::esc_like( $key ) . '%';
$sql = $wpdb->prepare( "DELETE FROM $wpdb->options WHERE option_name LIKE %s", $key );
$result = $wpdb->query( $sql );
return $result;
}
/**
* Returns the prefix for the results cache option name.
*
* @since 2.4-beta-1
*
* @param $form_id
*
* @return string
*/
public function get_results_cache_key_prefix( $form_id ) {
$key = sprintf( 'gf-results-cache-%s-', $form_id );
// The option_name column in the options table has a max length of 64 chars.
// Truncate the key if it's too long for column and allow space for the 'tmp' prefix
$key = substr( $key, 0, 60 );
return $key;
}
/**
* Generates a unique key for the cache meta based on form ID, fields and
*
* @since 2.4-beta-1
*
* @param $form_id
* @param $search_criteria
*
* @return string
*/
public function get_results_cache_key( $form_id, $search_criteria = array() ) {
$key = $this->get_results_cache_key_prefix( $form_id );
$key .= wp_hash( json_encode( $search_criteria ) );
return $key;
}
/**
* Recursive wp_cron task to continue the calculation of results.
*
* @since 2.4-beta-1
*
* @param $form_id
* @param $search_criteria
* @param $args
*/
public function results_cron( $form_id, $search_criteria, $args ) {
$args = wp_parse_args( $args, $this->get_default_args() );
$form = GFAPI::get_form( $form_id );
$key = $this->get_results_cache_key( $form_id, $search_criteria );
$key_tmp = 'tmp' . $key;
$state = get_option( $key_tmp, array() );
if ( ! empty( $state ) ) {
$results = $this->calculate( $form, $search_criteria, $state, $args );
if ( 'complete' == $results['status'] ) {
if ( isset( $results['progress'] ) ) {
unset( $results['progress'] );
}
$this->update_results_cache( $key, $results );
if ( false == empty( $state ) ) {
delete_option( $key_tmp );
}
} else {
$this->update_results_cache( $key_tmp, $results );
$data = get_option( $key );
if ( $data ) {
$data['progress'] = $results['progress'];
$this->update_results_cache( $key, $data );
}
$this->schedule_results_cron( $form_id, $search_criteria, $args );
}
}
}
/**
* Schedules the cron task.
*
* @since 2.4-beta-1
*
* @param $form_id
* @param $search_criteria
* @param $args
*/
private function schedule_results_cron( $form_id, $search_criteria, $args ) {
$args = wp_parse_args( $args, $this->get_default_args() );
$cron_args = array( $form_id, $search_criteria, $args );
$delay_in_seconds = $args['wait'];
wp_schedule_single_event( time() + $delay_in_seconds, $this->get_results_cron_hook(), $cron_args );
}
/**
* Checks if the results cron job is currently scheduled
*
* @since 2.4-beta-1
*
* @param $form_id
* @param $search_criteria
* @param $args
*
* @return false|int
*/
public function results_cron_is_scheduled( $form_id, $search_criteria, $args ) {
$args = wp_parse_args( $args, $this->get_default_args() );
$cron_args = array( $form_id, $search_criteria, $args );
return wp_next_scheduled( $this->get_results_cron_hook(), $cron_args );
}
/**
* Returs the results cron hook name
*
* @since 2.4-beta-1
*
* @return string
*/
public function get_results_cron_hook() {
return 'gravityforms_results_cron';
}
/**
* Returns an array with the results for all the fields in the form.
*
* If the results can be calculated within the time allowed in GFResults then the results are returned and nothing is cached.
* If the calculation has not finished then a single recursive wp_cron task will be scheduled for immediate execution.
* While the cache is being built by the wp_cron task this function will return the expired cache results if available or the latest step in the cache build.
* Add-On-specific results are not included e.g. grade frequencies in the Quiz Add-On.
*
* @since 2.4-beta-1
*
* @param int $form_id
* @param array $search_criteria
* @param array $args
*
* @return array|mixed|void
*/
public function get_results( $form_id, $search_criteria = array(), $args = array() ) {
$args = wp_parse_args( $args, $this->get_default_args() );
$form = GFAPI::get_form( $form_id );
if ( ! $form ) {
return new WP_Error( 'not_found', __( 'Form not found', 'gravityforms' ) );
}
$fields = rgar( $form, 'fields' );
$form_id = $form['id'];
$key = $this->get_results_cache_key( $form_id, $search_criteria );
$key_tmp = 'tmp' . $key;
$data = get_option( $key, array() );
$cache_meta = $this->get_results_cache_meta( $form_id );
// add the cache meta early so form editor updates can test for valid field hash
if ( empty( $cache_meta ) ) {
$this->update_results_cache_meta( $form_id, $fields );
}
$cache_expiry = rgar( $cache_meta, 'timestamp' );
$cache_timestamp = isset( $data['timestamp'] ) ? $data['timestamp'] : 0;
$cache_expired = $cache_expiry ? $cache_expiry > $cache_timestamp : false;
// check for valid cached results first
if ( ! empty( $data ) && 'complete' == rgar( $data, 'status' ) && ! $cache_expired ) {
$results = $data;
if ( isset( $results['progress'] ) ) {
unset( $results['progress'] );
}
} else {
$state = get_option( $key_tmp );
if ( empty( $state ) || ( 'complete' == rgar( $data, 'status' ) && $cache_expired ) ) {
$results = $this->calculate( $form, $search_criteria, $state, $args );
if ( rgar( $results, 'status' ) == 'complete' ) {
if ( false == empty( $state ) ) {
delete_option( $key_tmp );
}
} else {
if ( ! empty( $data ) && rgar( $data, 'status' ) == 'complete' && $cache_expired ) {
$data['status'] = 'expired';
$data['progress'] = $results['progress'];
$this->update_results_cache( $key, $data );
}
$this->update_results_cache( $key_tmp, $results );
$this->schedule_results_cron( $form_id, $search_criteria, $args );
if ( $data ) {
$results = $data;
}
}
} else {
// The cron task is recursive, not periodic, so system restarts, script timeouts and memory issues can prevent the cron from restarting.
// Check timestamp and kick off the cron again if it appears to have stopped
$state_timestamp = rgar( $state, 'timestamp' );
$state_age = time() - $state_timestamp;
if ( $state_age > 180 && ! $this->results_cron_is_scheduled( $form, $search_criteria, $args ) ) {
$this->schedule_results_cron( $form_id, $search_criteria, $args );
}
if ( ! empty( $data ) && rgar( $data, 'status' ) == 'expired' ) {
$results = $data;
} else {
$results = $state;
}
}
}
$field_data = rgar( $results, 'field_data' );
if ( ! empty( $field_data ) && $args['labels'] ) {
// add choice labels to the results so the client doesn't need to cross-reference with the form object
$results['labels'] = $this->get_labels( $form, $args );
}
return $results;
}
/**
* Calculate a batch of entry results.
*
* @since 2.4-beta-1
*
* @param $form
* @param array $search_criteria
* @param array $state_array
* @param array $args
*
* @return array|mixed
*/
public function calculate( $form, $search_criteria = array(), $state_array = array(), $args = array() ) {
$args = wp_parse_args( $args, $this->get_default_args() );
$max_execution_time = defined( 'DOING_CRON' ) && DOING_CRON ? $args['time_limit_cron'] : $args['time_limit'];
$page_size = $args['page_size'];
$callbacks = $args['callbacks'];
$time_start = microtime( true );
$form_id = $form['id'];
$data = array();
$offset = 0;
$entry_count = 0;
$field_data = array();
$fields = $this->filter_fields( $form, $args['field_ids'] );
if ( $state_array ) {
// get counts from state
$data = $state_array;
$offset = (int) rgar( $data, 'offset' );
unset( $data['offset'] );
$entry_count = $offset;
$field_data = rgar( $data, 'field_data' );
} else {
// initialize counts
foreach ( $fields as $field ) {
/* @var GF_Field $field */
$field_type = $field->get_input_type();
if ( ! isset( $field->choices ) ) {
$field_data[ $field->id ] = 0;
continue;
}
$choices = $field->choices;
if ( $field_type == 'likert' && $field->gsurveyLikertEnableMultipleRows ) {
foreach ( $field->gsurveyLikertRows as $row ) {
foreach ( $choices as $choice ) {
$field_data[ $field->id ][ $row['value'] ][ $choice['value'] ] = 0;
}
if ( $field->gsurveyLikertEnableScoring ) {
$field_data[ $field->id ][ $row['value'] ]['row_score_sum'] = 0;
}
}
} else {
if ( ! empty( $choices ) && is_array( $choices ) ) {
foreach ( $choices as $choice ) {
$field_data[ $field->id ][ $choice['value'] ] = 0;
}
} else {
$field_data[ $field->id ] = 0;
}
}
if ( $field_type == 'likert' && rgar( $field, 'gsurveyLikertEnableScoring' ) ) {
$field_data[ $field->id ]['sum_of_scores'] = 0;
}
}
}
$count_search_entries = GFAPI::count_entries( $form_id, $search_criteria );
$data['entry_count'] = $count_search_entries;
if ( $count_search_entries == 0 ) {
$data['status'] = 'complete';
}
$entries_left = $count_search_entries - $offset;
while ( $entries_left > 0 ) {
$paging = array(
'offset' => $offset,
'page_size' => $page_size,
);
$search_entries_time_start = microtime( true );
$entries = GFAPI::get_entries( $form_id, $search_criteria, null, $paging );
$search_entries_time_end = microtime( true );
$search_entries_time = $search_entries_time_end - $search_entries_time_start;
$entries_in_search = count( $entries );
$entry_count += $entries_in_search;
$entries_processed = 0;
foreach ( $entries as $entry ) {
$entry_time_start = microtime( true );
foreach ( $fields as $field ) {
$field_type = $field->get_input_type();
$field_id = $field->id;
$value = GFFormsModel::get_lead_field_value( $entry, $field );
if ( $field_type == 'likert' && rgar( $field, 'gsurveyLikertEnableMultipleRows' ) ) {
if ( empty( $value ) ) {
continue;
}
foreach ( $value as $value_vector ) {
if ( empty( $value_vector ) ) {
continue;
}
list( $row_val, $col_val ) = explode( ':', $value_vector, 2 );
if ( isset( $field_data[ $field->id ][ $row_val ] ) && isset( $field_data[ $field->id ][ $row_val ][ $col_val ] ) ) {
$field_data[ $field->id ][ $row_val ][ $col_val ] ++;
if ( $field->gsurveyLikertEnableScoring ) {
$field_data[ $field->id ][ $row_val ]['row_score_sum'] += $this->get_likert_row_score( $row_val, $field, $entry );
}
}
}
} elseif ( $field_type == 'rank' ) {
$score = count( rgar( $field, 'choices' ) );
$values = explode( ',', $value );
foreach ( $values as $ranked_value ) {
$field_data[ $field->id ][ $ranked_value ] += $score;
$score --;
}
} else {
if ( empty( $field->choices ) ) {
if ( ( ! is_array( $value ) && ! empty( $value ) ) || ( is_array( $value ) && ! GFCommon::is_empty_array( $value ) ) ) {
$field_data[ $field_id ] ++;
}
continue;
}
$choices = $field->choices;
foreach ( $choices as $choice ) {
$choice_is_selected = false;
if ( is_array( $value ) ) {
$choice_value = rgar( $choice, 'value' );
if ( in_array( $choice_value, $value ) ) {
$choice_is_selected = true;
}
} else {
if ( GFFormsModel::choice_value_match( $field, $choice, $value ) ) {
$choice_is_selected = true;
}
}
if ( $choice_is_selected ) {
$field_data[ $field_id ][ $choice['value'] ] ++;
}
}
}
if ( $field_type == 'likert' && rgar( $field, 'gsurveyLikertEnableScoring' ) ) {
$field_data[ $field->id ]['sum_of_scores'] += $this->get_likert_score( $field, $entry );
}
}
$entries_processed ++;
$entry_time_end = microtime( true );
$total_execution_time = $entry_time_end - $search_entries_time_start;
$entry_execution_time = $entry_time_end - $entry_time_start;
if ( $total_execution_time + $entry_execution_time > $max_execution_time ) {
break;
}
}
$data['field_data'] = $field_data;
if ( isset( $callbacks['calculation'] ) && is_callable( $callbacks['calculation'] ) ) {
$data = call_user_func( $callbacks['calculation'], $data, $form, $fields, $entries );
$field_data = $data['field_data'];
}
$offset += $entries_processed;
$entries_left -= $entries_processed;
$time_end = microtime( true );
$execution_time = ( $time_end - $time_start );
if ( $entries_left > 0 && $execution_time + $search_entries_time > $max_execution_time ) {
$data['status'] = 'incomplete';
$data['offset'] = $offset;
$progress = $data['entry_count'] > 0 ? round( $data['offset'] / $data['entry_count'] * 100 ) : 0;
$data['progress'] = $progress;
break;
}
if ( $entries_left <= 0 ) {
$data['status'] = 'complete';
}
}
$data['timestamp'] = time();
return $data;
}
/**
* Returns the likert field row score
*
* @since 2.4-beta-1
*
*
* @param $row_val
* @param $field
* @param $entry
*
* @return float|int
*/
private function get_likert_row_score( $row_val, $field, $entry ) {
return is_callable( array(
'GFSurvey',
'get_likert_row_score',
) ) ? GFSurvey::get_likert_row_score( $row_val, $field, $entry ) : 0;
}
/**
* Returns the likert field score
*
* @since 2.4-beta-1
*
*
* @param $field
* @param $entry
*
* @return float|int
*/
private function get_likert_score( $field, $entry ) {
return is_callable( array(
'GFSurvey',
'get_field_score',
) ) ? GFSurvey::get_field_score( $field, $entry ) : 0;
}
/**
* Returns an array with field labels and choice labels
*
* @since 2.4-beta-1
*
*
* @param $form
* @param $args
*
* @return array
*/
private function get_labels( $form, $args ) {
$args = wp_parse_args( $args, $this->get_default_args() );
$fields = $this->filter_fields( $form, $args['field_ids'] );
$labels = array();
// replace the values/ids with text labels
foreach ( $fields as $field ) {
$field_id = $field->id;
$field = GFFormsModel::get_field( $form, $field_id );
if ( is_array( $field->choices ) ) {
$label = array();
$choice_labels = array();
foreach ( $field->choices as $choice ) {
$choice_labels[ $choice['value'] ] = $choice['text'];
}
if ( $field instanceof GF_Field_Likert && $field->gsurveyLikertEnableMultipleRows ) {
/* @var GF_Field_Likert $field */
$label = array(
'label' => $field->label,
'cols' => $choice_labels,
'rows' => array(),
);
foreach ( $field->gsurveyLikertRows as $row ) {
$label['rows'][ $row['value'] ] = $row['text'];
}
} else {
$label['label'] = $field->label;
$label['choices'] = $choice_labels;
}
} else {
$label = $field['label'];
}
$labels[ $field->id ] = $label;
}
return $labels;
}
/**
* Filters the form array, returning only the fields matching the specified list of $field_ids
*
* @since 2.4-beta-1
*
* @param $form The form array to be filtered
* @param $field_ids The list of field ids to be returned
*
* @return array Returns a filtered form array only containing fields that match the $field_ids list
*/
private function filter_fields( $form, $field_ids ) {
$fields = $form['fields'];
if ( is_array( $field_ids ) && ! empty( $field_ids ) ) {
foreach ( $fields as $key => $field ) {
if ( ! in_array( $field->id, $field_ids ) ) {
unset( $fields[ $key ] );
}
}
$fields = array_values( $fields );
}
return $fields;
}
}
/**
* @return GF_Results_Cache
*/
function gf_results_cache() {
return GF_Results_Cache::get_instance();
}
gf_results_cache();

View File

@@ -0,0 +1,369 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Entries_Controller extends GF_REST_Form_Entries_Controller {
/**
* @since 2.4-beta-1
*
* @var string
*/
public $rest_base = 'entries';
/**
* Register the routes for the objects of the controller.
*
* @since 2.4-beta-1
*
*/
public function register_routes() {
$namespace = $this->namespace;
$base = $this->rest_base;
register_rest_route( $namespace, '/' . $base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
) );
register_rest_route( $namespace, '/' . $base . '/(?P<entry_id>[\d]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(),
),
array(
'methods' => 'PUT',
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( false ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(),
),
) );
}
/**
* Get a collection of entries
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
return parent::get_items( $request );
}
/**
* Get one item from the collection
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$entry_id = $request->get_param( 'entry_id' );
$entry = GFAPI::get_entry( $entry_id );
if ( is_wp_error( $entry ) ) {
return new WP_Error( 'gf_entry_invalid_id', __( 'Invalid entry id.', 'gravityforms' ), array( 'status' => 404 ) );
}
// Get form id here, it could be removed when _field_ids are specified.
$form_id = $entry['form_id'];
$field_ids = $request['_field_ids'];
if ( ! empty( $field_ids ) ) {
$field_ids = (array) explode( ',', $request['_field_ids'] );
$field_ids = array_map( 'trim', $field_ids );
if ( ! empty( $field_ids ) ) {
$entry = $this->filter_entry_fields( $entry, $field_ids );
}
}
$labels = $request['_labels'];
if ( $labels ) {
$form = GFAPI::get_form( $form_id );
$entry['_labels'] = $this->get_entry_labels( $form, compact( 'field_ids' ) );
}
$data = $this->prepare_item_for_response( $entry, $request );
return $data;
}
/**
* Create one item from the collection
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Request
*/
public function create_item( $request ) {
return parent::create_item( $request );
}
/**
* Update one item from the collection
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
$entry = $this->prepare_item_for_database( $request );
if ( is_wp_error( $entry ) ) {
return $entry;
}
$result = GFAPI::update_entry( $entry );
if ( is_wp_error( $result ) ) {
$status = $this->get_error_status( $result );
return new WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => $status ) );
}
$updated_entry = GFAPI::get_entry( $entry['id'] );
$response = $this->prepare_item_for_response( $updated_entry, $request );
return rest_ensure_response( $response );
}
/**
* Delete one item from the collection
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
$entry_id = $request['entry_id'];
$entry = GFAPI::get_entry( $entry_id );
if ( is_wp_error( $entry ) ) {
return new WP_Error( 'gf_entry_invalid_id', __( 'Invalid entry id.', 'gravityforms' ), array( 'status' => 404 ) );
}
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
if ( $force ) {
$result = GFAPI::delete_entry( $entry_id );
if ( is_wp_error( $result ) ) {
$message = $result->get_error_message();
return new WP_Error( 'gf_cannot_delete', $message, array( 'status' => 500 ) );
}
$previous = $this->prepare_item_for_response( $entry, $request );
$response = new WP_REST_Response();
$response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) );
} else {
if ( rgar( $entry, 'status' ) == 'trash' ) {
$message = __( 'The entry has already been deleted.', 'gravityforms' );
return new WP_Error( 'gf_already_trashed', $message, array( 'status' => 410 ) );
}
// Trash the entry
GFAPI::update_entry_property( $entry_id, 'status', 'trash' );
$entry = GFAPI::get_entry( $entry_id );
$response = rest_ensure_response( $entry );
}
return $response;
}
/**
* Check if a given request has access to get items
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_items_permissions_check( $request ) {
/**
* Filters the capability required to get entries via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_get_entries', 'gravityforms_view_entries', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to get a specific item
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_item_permissions_check( $request ) {
return $this->get_items_permissions_check( $request );
}
/**
* Check if a given request has access to create items
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function create_item_permissions_check( $request ) {
/**
* Filters the capability required to create entries via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_post_entries', 'gravityforms_edit_entries', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to update a specific item
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function update_item_permissions_check( $request ) {
/**
* Filters the capability required to update entries via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_put_entries', 'gravityforms_edit_entries', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to delete a specific item
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function delete_item_permissions_check( $request ) {
/**
* Filters the capability required to delete entries via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_delete_entries', 'gravityforms_delete_entries', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Prepare the item for create or update operation
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Request object
*
* @return WP_Error|array $prepared_item
*/
protected function prepare_item_for_database( $request ) {
$entry = $request->get_json_params();
if ( empty( $entry ) ) {
return new WP_Error( 'missing_entry', __( 'Missing entry JSON', 'gravityforms' ) );
}
$entry_id = $request['entry_id'];
if ( ! empty( $entry_id ) ) {
$entry['id'] = $entry_id;
}
$entry = $this->maybe_json_encode_applicable_fields( $entry );
$entry = $this->maybe_serialize_list_fields( $entry );
return $entry;
}
/**
* Prepare the item for the REST response
*
* @since 2.4-beta-1
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
*
* @return WP_REST_Response Returns the item wrapped in a WP_REST_Response object
*/
public function prepare_item_for_response( $item, $request ) {
$item = $this->prepare_entry_for_response( $item );
$response = new WP_REST_Response( $item, 200 );
return $response;
}
}

View File

@@ -0,0 +1,307 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Entry_Notes_Controller extends GF_REST_Controller {
/**
* @var string Base for the REST request.
*/
public $rest_base = 'entries/(?P<entry_id>[\d]+)/notes';
/**
* Register the routes for the objects of the controller.
*/
public function register_routes() {
$namespace = $this->namespace;
$base = $this->rest_base;
register_rest_route(
$namespace,
'/' . $base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
)
);
}
/**
* Get all notes for one entry.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$entry_id = $request->get_param( 'entry_id' );
if ( ! GFAPI::entry_exists( $entry_id ) ) {
return new WP_Error( 'gf_entry_invalid_id', __( 'Invalid entry id.', 'gravityforms' ), array( 'status' => 404 ) );
}
$criteria = $request->get_params();
$allowed_criteria = array(
'user_id',
'note_type',
'sub_type',
'user_name'
);
foreach ( $criteria as $key => $value ) {
if ( in_array( $key, $allowed_criteria ) ) {
$criteria[$key] = $value;
}
}
$criteria['entry_id'] = $entry_id;
$sorting = '';
if ( isset( $criteria['sorting'] ) ) {
$sorting = $criteria['sorting'];
unset( $criteria['sorting'] );
}
$notes = GFAPI::get_notes( $criteria, $sorting );
if ( is_wp_error( $notes ) ) {
return new WP_Error( 'gf_entry_invalid_notes', __( 'Error retrieving notes.', 'gravityforms' ), array( 'status' => 404 ) );
}
if ( ! is_array( $notes ) || empty( $notes ) ) {
return array();
}
$data = array();
foreach ( $notes as $note ) {
$data[ $note->id ] = $note;
}
$response = new WP_REST_Response( $data, 200 );
return $response;
}
/**
* Create one note.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Request
*/
public function create_item( $request ) {
$note = $this->prepare_item_for_database( $request );
$entry_id = $request->get_param( 'entry_id' );
if ( is_wp_error( $note ) ) {
return $note;
}
$note_id = GFAPI::add_note( $entry_id, $note['user_id'], $note['user_name'], $note['note'] );
if ( is_wp_error( $note_id ) ) {
$status = $this->get_error_status( $note_id );
return new WP_Error( $note_id->get_error_code(), $note_id->get_error_message(), array( 'status' => $status ) );
}
$note['id'] = $note_id;
$note = $this->prepare_note_for_response( $note_id );
$response = rest_ensure_response( $note );
$response->set_status( 201 );
$base = sprintf( 'entries/%d/notes/', $note_id );
$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $base, $note_id ) ) );
return $response;
}
/**
* Check if a given request has access to get items.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_items_permissions_check( $request ) {
/**
* Filters the capability required to get entries via the REST API.
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_get_notes', 'gravityforms_view_entry_notes', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to get a specific item.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_item_permissions_check( $request ) {
return $this->get_items_permissions_check( $request );
}
/**
* Check if a given request has access to create items.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function create_item_permissions_check( $request ) {
/**
* Filters the capability required to create entries via the REST API.
*
* @since 2.4.18
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_post_notes', 'gravityforms_edit_entry_notes', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Prepare the item for create or update operation.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Request object.
*
* @return WP_Error|array $prepared_item.
*/
protected function prepare_item_for_database( $request ) {
$note = $request->get_json_params();
if ( empty( $note ) ) {
return new WP_Error( 'missing_entry', __( 'Missing entry JSON', 'gravityforms' ) );
}
$note['user_id'] = intval( $note['user_id'] );
$note['note'] = wp_kses_post( $note['value'] );
return $note;
}
/**
* Prepare the item for the REST response.
*
* @since 2.4.18
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
*
* @return WP_REST_Response Returns the item wrapped in a WP_REST_Response object
*/
public function prepare_item_for_response( $item, $request ) {
$item = $this->prepare_note_for_response( $item->id );
$response = new WP_REST_Response( $item, 200 );
return $response;
}
/***
* Prepares note for REST API response, decoding or unserializing appropriate fields.
*
* @since 2.4.18
*
* @param int $note_id The note id.
*
* @return bool|array Returns the entry array ready to be send in the REST API response.
*/
public function prepare_note_for_response( $note_id ) {
$note = GFAPI::get_note( $note_id );
if ( is_wp_error( $note ) || ! isset( $note->ID ) ) {
return $note;
}
return $note;
}
/**
* Check if a given request has access to update a specific item.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function update_item_permissions_check( $request ) {
/**
* Filters the capability required to update entries via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_put_notes', 'gravityforms_edit_entries', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to delete a specific item.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function delete_item_permissions_check( $request ) {
/**
* Filters the capability required to delete entries via the REST API.
*
* @since 2.4.18
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_delete_notes', 'gravityforms_edit_entry_notes', $request );
return $this->current_user_can_any( $capability, $request );
}
}

View File

@@ -0,0 +1,128 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Entry_Notifications_Controller extends GF_REST_Controller {
/**
* @since 2.4-beta-1
*
* @var string
*/
public $rest_base = 'entries/(?P<entry_id>[\d]+)/notifications';
/**
* Register the routes for the objects of the controller.
*
* @since 2.4-beta-1
*
*/
public function register_routes() {
$namespace = $this->namespace;
$base = $this->rest_base;
register_rest_route( $namespace, '/' . $base, array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_collection_params(),
),
) );
}
/**
* Re-sends notifications for an entry.
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function create_item( $request ) {
$entry_id = $request['entry_id'];
$entry = GFAPI::get_entry( $entry_id );
if ( is_wp_error( $entry ) ) {
return $entry;
}
$form_id = $entry['form_id'];
$form = GFAPI::get_form( $form_id );
if ( empty( $form ) ) {
return new WP_Error( __( 'Form not found.', 'gravityforms' ) );
}
$notification_ids = $request['_notifications'];
if ( ! empty( $notification_ids ) ) {
$notification_ids = (array) explode( ',', $request['_notifications'] );
$notification_ids = array_map( 'trim', $notification_ids );
}
$event = isset( $request['_event'] ) ? $request['_event'] : 'form_submission';
if ( empty( $notification_ids ) ) {
$notification_ids = GFAPI::send_notifications( $form, $entry, $event );
} else {
foreach ( $notification_ids as $notification_id ) {
if ( empty( $form['notifications'][ $notification_id ] ) ) {
/* translators: %s: The notification id */
return new WP_Error( __( sprintf( 'Notification %s not found.', $notification_id ), 'gravityforms' ) );
}
GFCommon::send_notification( $form['notifications'][ $notification_id ], $form, $entry );
}
}
return new WP_REST_Response( $notification_ids, 200 );
}
/**
* Check if a given request has permission to send notifications.
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function create_item_permissions_check( $request ) {
/**
* Filters the capability required to re-send notifications via the REST API.
*
* @since 2.4-beta-1
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_post_entries_notifications', 'gravityforms_edit_entries', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Get the query params for collections
*
* @since 2.4-beta-1
*
* @return array
*/
public function get_collection_params() {
return array(
'include' => array(
'description' => 'Limit the notifications to specific IDs.',
),
'event' => array(
'description' => 'The event to trigger. Default: form_submission.',
),
);
}
}

View File

@@ -0,0 +1,140 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Entry_Properties_Controller extends GF_REST_Form_Entries_Controller {
/**
* @since 2.4-beta-1
*
*
* @var string
*/
public $rest_base = 'entries/(?P<entry_id>[\d]+)/properties';
/**
* Register the routes for the objects of the controller.
*
* @since 2.4-beta-1
*
*/
public function register_routes() {
$namespace = $this->namespace;
$base = $this->rest_base;
register_rest_route( $namespace, '/' . $base, array(
array(
'methods' => 'PUT',
'callback' => array( $this, 'update_items' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( true ),
),
) );
}
/**
* Update one item from the collection
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function update_items( $request ) {
$entry_id = $request['entry_id'];
$key_value_pairs = $this->prepare_item_for_database( $request );
if ( empty( $key_value_pairs ) ) {
$message = __( 'No property values were found in the request body', 'gravityforms' );
return new WP_REST_Response( $message, 400 );
} elseif ( ! is_array( $key_value_pairs ) ) {
$message = __( 'Property values should be sent as an array', 'gravityforms' );
return new WP_REST_Response( $message, 400 );
}
$result = false;
foreach ( $key_value_pairs as $key => $property_value ) {
$result = GFAPI::update_entry_property( $entry_id, $key, $property_value );
if ( is_wp_error( $result ) ) {
break;
}
}
if ( is_wp_error( $result ) ) {
$status = $this->get_error_status( $result );
return new WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => $status ) );
}
$message = __( 'Entry updated successfully', 'gravityforms' );
return new WP_REST_Response( $message, 200 );
}
/**
* Check if a given request has access to update a specific item
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function update_item_permissions_check( $request ) {
/**
* Filters the capability required to update entries via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_put_entries', 'gravityforms_edit_entries', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Prepare the item for create or update operation
*
* @since 2.4
*
* @param WP_REST_Request $request Request object
*
* @return WP_Error|array $prepared_item
*/
protected function prepare_item_for_database( $request ) {
$properties = $request->get_json_params();
if ( empty( $properties ) ) {
return new WP_Error( 'missing_properties', __( 'Missing Key Value Pairs JSON', 'gravityforms' ) );
}
return $properties;
}
/**
* Get the query params for collections
*
* @since 2.4
*
* @return array
*/
public function get_collection_params() {
return array();
}
/**
* Get the Entry Property schema, conforming to JSON Schema.
*
* @since 2.4-beta-1
*
* @return array
*/
public function get_item_schema() {
return array();
}
}

View File

@@ -0,0 +1,129 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Feed_Properties_Controller extends GF_REST_Feeds_Controller {
/**
* The base of this controller's route.
*
* @since 2.4.24
*
* @var string
*/
public $rest_base = 'feeds/(?P<feed_id>[\d]+)/properties';
/**
* Register the routes for the objects of the controller.
*
* @since 2.4.24
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => 'PUT',
'callback' => array( $this, 'update_items' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( true ),
),
) );
}
/**
* Updates the specified feed with the given properties.
*
* @since 2.4.24
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function update_items( $request ) {
$properties = $this->prepare_item_for_database( $request );
if ( is_wp_error( $properties ) ) {
return $properties;
}
$result = $this->update_feed_properties( $request['feed_id'], $properties );
if ( is_wp_error( $result ) ) {
return $result;
}
return new WP_REST_Response( __( 'Feed updated successfully', 'gravityforms' ), 200 );
}
/**
* Retrieves the properties from the request body.
*
* @since 2.4.24
*
* @param WP_REST_Request $request Request object
*
* @return WP_Error|array
*/
protected function prepare_item_for_database( $request ) {
$properties = $request->get_json_params();
if ( empty( $properties ) ) {
return new WP_Error( 'missing_properties', __( 'Invalid JSON. Properties should be sent as key value pairs.', 'gravityforms' ), array( 'status' => 400 ) );
}
return $properties;
}
/**
* Get the query params for collections
*
* @since 2.4.24
*
* @return array
*/
public function get_collection_params() {
return array();
}
/**
* Get the Feed schema, conforming to JSON Schema.
*
* @since 2.4.24
*
* @return array
*/
public function get_item_schema() {
return array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'feed',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the feed.', 'gravityforms' ),
'type' => 'integer',
'readonly' => true,
),
'form_id' => array(
'description' => __( 'The Form ID for the feed.', 'gravityforms' ),
'type' => 'integer',
),
'is_active' => array(
'description' => __( 'Indicates if the feed is active or inactive.', 'gravityforms' ),
'type' => 'boolean',
),
'feed_order' => array(
'description' => __( 'The position of the feed on the feeds list page and when processed; for add-ons which support feed ordering.', 'gravityforms' ),
'type' => 'integer',
),
'meta' => array(
'description' => __( 'The JSON string containing the feed meta.', 'gravityforms' ),
'type' => 'object',
),
'addon_slug' => array(
'description' => __( 'The add-on the feed belongs to.', 'gravityforms' ),
'type' => 'string',
),
),
);
}
}

View File

@@ -0,0 +1,363 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Feeds_Controller extends GF_REST_Form_Feeds_Controller {
/**
* @since 2.4
*
* @var string
*/
public $rest_base = 'feeds';
/**
* Register the routes for the objects of the controller.
*
* @since 2.4
*
*/
public function register_routes() {
$namespace = $this->namespace;
$base = $this->rest_base;
register_rest_route( $namespace, '/' . $base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
) );
register_rest_route( $namespace, '/' . $base . '/(?P<feed_id>[\d]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(),
),
array(
'methods' => 'PUT,PATCH',
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( false ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(),
),
) );
}
/**
* Get a collection of feeds.
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$feed_ids = $request['include'];
if ( ! empty( $feed_ids ) ) {
if ( ! is_array( $feed_ids ) ) {
$feed_ids = array( $feed_ids );
}
$feed_ids = array_map( 'absint', $feed_ids );
}
$addon_slug = $request['addon'];
$feeds = GFAPI::get_feeds( $feed_ids, null, $addon_slug );
return new WP_REST_Response( $feeds, 200 );
}
/**
* Get one item from the collection
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$feed_id = $request->get_param( 'feed_id' );
$feed = GFAPI::get_feed( $feed_id );
if ( is_wp_error( $feed ) ) {
return new WP_Error( 'gf_feed_invalid_id', __( 'Invalid feed id.', 'gravityforms' ), array( 'status' => 404 ) );
}
return $this->prepare_item_for_response( $feed, $request );
}
/**
* Create one item from the collection
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Request
*/
public function create_item( $request ) {
return parent::create_item( $request );
}
/**
* Update one item from the collection
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
if ( ! GFAPI::feed_exists( $request['feed_id'] ) ) {
return new WP_Error( 'gf_feed_invalid_id', __( 'Invalid feed id.', 'gravityforms' ), array( 'status' => 404 ) );
}
$properties = $this->prepare_item_for_database( $request );
if ( is_wp_error( $properties ) ) {
return $properties;
}
unset( $properties['id'] );
$result = $this->update_feed_properties( $request['feed_id'], $properties );
if ( is_wp_error( $result ) ) {
return $result;
}
return $this->prepare_item_for_response( GFAPI::get_feed( $request['feed_id'] ), $request );
}
/**
* Prepares the item for the update operation.
*
* @since 2.4.24
*
* @param WP_REST_Request $request Request object
*
* @return WP_Error|array
*/
protected function prepare_item_for_database( $request ) {
if ( $request->get_method() !== 'PATCH' ) {
return parent::prepare_item_for_database( $request );
}
$properties = $request->get_json_params();
if ( empty( $properties ) ) {
return new WP_Error( 'missing_properties', __( 'Invalid JSON. Properties should be sent as key value pairs.', 'gravityforms' ), array( 'status' => 400 ) );
}
if ( ! empty( $properties['meta'] ) ) {
$feed = GFAPI::get_feed( $request['feed_id'] );
$properties['meta'] = $this->patch_array_recursive( $feed['meta'], $properties['meta'] );
}
return $properties;
}
/**
* Updates the specified feed with the given property values.
*
* @since 2.4.24
*
* @param int $feed_id The ID of the feed being updated.
* @param array $properties The feed properties being updated.
*
* @return bool|WP_Error
*/
protected function update_feed_properties( $feed_id, $properties ) {
foreach ( $properties as $key => $value ) {
$result = GFAPI::update_feed_property( $feed_id, $key, $value );
if ( is_wp_error( $result ) ) {
return new WP_Error(
$result->get_error_code(),
$result->get_error_message(),
array( 'status' => $this->get_error_status( $result ) )
);
}
}
return true;
}
/**
* Delete one item from the collection
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
$feed_id = $request['feed_id'];
$feed = GFAPI::get_feed( $feed_id );
if ( is_wp_error( $feed ) ) {
return new WP_Error( 'gf_feed_invalid_id', __( 'Invalid feed id.', 'gravityforms' ), array( 'status' => 404 ) );
}
$result = GFAPI::delete_feed( $feed_id );
if ( is_wp_error( $result ) ) {
return $result;
}
$previous = $this->prepare_item_for_response( $feed, $request );
$response = new WP_REST_Response();
$response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) );
return $response;
}
/**
* Check if a given request has access to get items
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_items_permissions_check( $request ) {
/**
* Filters the capability required to get feeds via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_get_feeds', 'gravityforms_edit_forms', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to get a specific item
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_item_permissions_check( $request ) {
return $this->get_items_permissions_check( $request );
}
/**
* Check if a given request has access to create items
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function create_item_permissions_check( $request ) {
/**
* Filters the capability required to create feeds via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_post_feeds', 'gravityforms_edit_forms', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to update a specific item
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function update_item_permissions_check( $request ) {
/**
* Filters the capability required to update feeds via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_put_feeds', 'gravityforms_edit_forms', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to delete a specific item
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function delete_item_permissions_check( $request ) {
/**
* Filters the capability required to delete feeds via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_delete_feeds', 'gravityforms_edit_forms', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Prepare the item for the REST response
*
* @since 2.4
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
*
* @return WP_REST_Response Returns the item wrapped in a WP_REST_Response object
*/
public function prepare_item_for_response( $item, $request ) {
$response = new WP_REST_Response( $item, 200 );
return $response;
}
}

View File

@@ -0,0 +1,496 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Form_Entries_Controller extends GF_REST_Controller {
/**
* @since 2.4-beta-1
*
* @var string
*/
public $rest_base = 'forms/(?P<form_id>[\d]+)/entries';
/**
* Register the routes for the objects of the controller.
*
* @since 2.4-beta-1
*/
public function register_routes() {
$namespace = $this->namespace;
$base = $this->rest_base;
register_rest_route( $namespace, '/' . $base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
) );
}
/**
* Get a collection of entries
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$entry_ids = $request['include'];
if ( ! empty( $entry_ids ) ) {
if ( ! is_array( $entry_ids ) ) {
$entry_ids = array( $entry_ids );
}
$entry_ids = array_map( 'absint', $entry_ids );
}
$field_ids = $request['_field_ids'];
if ( ! empty( $field_ids ) ) {
$field_ids = (array) explode( ',', $request['_field_ids'] );
$field_ids = array_map( 'trim', $field_ids );
}
$labels = $request['_labels'];
$data = array();
if ( $entry_ids ) {
foreach ( $entry_ids as $id ) {
$result = GFAPI::get_entry( $id );
if ( ! is_wp_error( $result ) ) {
$form_id = $result['form_id'];
$entry = $this->prepare_entry_for_response( $result );
if ( ! empty( $field_ids ) && ( ! empty( $entry ) ) ) {
$entry = $this->filter_entry_fields( $entry, $field_ids );
}
if ( $labels ) {
$form = GFAPI::get_form( $form_id );
$entry['_labels'] = $this->get_entry_labels( $form, compact( 'field_ids' ) );
}
$data[ $id ] = $entry;
}
}
} else {
$entry_search_params = $this->parse_entry_search_params( $request );
$entry_count = 0;
$form_id = isset( $entry_search_params['form_ids'] ) ? $entry_search_params['form_ids'] : $request['form_id'];
if ( empty( $form_id ) ) {
$form_id = 0;
}
$entries = GFAPI::get_entries( $form_id, $entry_search_params['search_criteria'], $entry_search_params['sorting'], $entry_search_params['paging'], $entry_count );
$data = array();
if ( ! is_wp_error( $entries ) ) {
foreach ( $entries as &$entry ) {
$form_id_for_entry = $entry['form_id'];
$entry = $this->prepare_entry_for_response( $entry );
if ( ! empty( $field_ids ) && ! empty( $entry ) ) {
$entry = $this->filter_entry_fields( $entry, $field_ids );
}
if ( $labels && ( empty( $form_id ) || is_array( $form_id ) ) ) {
$form = GFAPI::get_form( $form_id_for_entry );
$entry['_labels'] = $this->get_entry_labels( $form, compact( 'field_ids' ) );
}
}
$data = array( 'total_count' => $entry_count, 'entries' => $entries );
if ( $labels && ! empty( $form_id ) && ! is_array( $form_id ) ) {
$form = GFAPI::get_form( $form_id );
$data['_labels'] = $this->get_entry_labels( $form, compact( 'field_ids' ) );
}
}
}
return new WP_REST_Response( $data, 200 );
}
/**
* Create one item from the collection
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function create_item( $request ) {
$entry = $this->prepare_item_for_database( $request );
if ( is_wp_error( $entry ) ) {
return $entry;
}
$entry_id = GFAPI::add_entry( $entry );
if ( is_wp_error( $entry_id ) ) {
$status = $this->get_error_status( $entry_id );
return new WP_Error( $entry_id->get_error_code(), $entry_id->get_error_message(), array( 'status' => $status ) );
}
$entry['id'] = $entry_id;
$entry = $this->prepare_entry_for_response( $entry );
$response = rest_ensure_response( $entry );
$response->set_status( 201 );
$base = sprintf( 'forms/%d/entries', $entry['form_id'] );
$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $base, $entry_id ) ) );
return $response;
}
/**
* Check if a given request has access to get items
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_items_permissions_check( $request ) {
/**
* Filters the capability required to get entries via the REST API.
*
* @since 2.0-beta-2
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_get_entries', 'gravityforms_view_entries', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to create items
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function create_item_permissions_check( $request ) {
/**
* Filters the capability required to create entries via the REST API.
*
* @since 2.0-beta-2
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_post_entries', 'gravityforms_edit_entries', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Prepare the item for create or update operation
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Request object
*
* @return WP_Error|array $prepared_item
*/
protected function prepare_item_for_database( $request ) {
$entry = $request->get_json_params();
if ( empty( $entry ) ) {
return new WP_Error( 'missing_entry', __( 'Missing entry JSON', 'gravityforms' ) );
}
$url_params = $request->get_url_params();
// Check the URL params first
$form_id = rgar( $url_params, 'form_id' );
if ( empty( $form_id ) ) {
$form_id = $request->get_param( 'form_id' );
}
if ( $form_id ) {
$entry['form_id'] = absint( $form_id );
}
$entry = $this->maybe_json_encode_applicable_fields( $entry );
$entry = $this->maybe_serialize_list_fields( $entry );
return $entry;
}
/**
* Get the query params for collections
*
* @since 2.4-beta-1
*
* @return array
*/
public function get_collection_params() {
return array(
'sorting' => array(
'description' => 'The sorting criteria.',
),
'paging' => array(
'description' => 'The paging criteria.',
),
'search' => array(
'description' => 'The search criteria.',
'type' => 'string',
),
'include' => array(
'description' => __( 'Limit result set to specific IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
),
'_field_ids' => array(
'description' => 'Comma separated list of fields to include in the response.',
'type' => 'string',
),
'_labels' => array(
'description' => 'Whether to include the labels in the response.',
'type' => 'integer',
),
);
}
/**
* Get the Entry schema, conforming to JSON Schema.
*
* @since 2.4-beta-1
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'entry',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'gravityforms' ),
'type' => 'integer',
'readonly' => true,
),
'form_id' => array(
'description' => __( 'The Form ID for the entry.', 'gravityforms' ),
'type' => 'integer',
'required' => true,
'readonly' => false,
),
'date_created' => array(
'description' => __( 'The date the entry was created, in UTC.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
'date_updated' => array(
'description' => __( 'The date the entry was updated, in UTC.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
'is_starred' => array(
'description' => __( 'Whether the entry is starred.', 'gravityforms' ),
'type' => 'integer',
'readonly' => false,
),
'is_read' => array(
'description' => __( 'Whether the entry has been read.', 'gravityforms' ),
'type' => 'integer',
'readonly' => false,
),
'ip' => array(
'description' => __( 'The IP address of the entry creator.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
'source_url' => array(
'description' => __( 'The URL where the form was embedded.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
'user_agent' => array(
'description' => __( 'The user agent string for the browser used to submit the entry.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
'payment_status' => array(
'description' => __( 'The status of the payment, if applicable.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
'payment_date' => array(
'description' => __( 'The date of the payment, if applicable.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
'payment_amount' => array(
'description' => __( 'The amount of the payment, if applicable.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
'payment_method' => array(
'description' => __( 'The payment method for the payment, if applicable.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
'transaction_id' => array(
'description' => __( 'The transaction ID for the payment, if applicable.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
'is_fulfilled' => array(
'description' => __( 'Whether the transaction has been fulfilled, if applicable.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
'created_by' => array(
'description' => __( 'The user ID of the entry submitter.', 'gravityforms' ),
'type' => 'integer',
'readonly' => false,
),
'transaction_type' => array(
'description' => __( 'The type of the transaction, if applicable.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
'status' => array(
'description' => __( 'The status of the entry.', 'gravityforms' ),
'type' => 'string',
'readonly' => false,
),
),
);
return $schema;
}
/**
* Returns an array with field labels and choice labels
*
* @since 2.4-beta-1
*
* @param $form
* @param array $args
*
* @return array
*/
protected function get_entry_labels( $form, $args = array() ) {
$defaults = array(
'field_ids' => false,
);
$args = wp_parse_args( $args, $defaults );
$fields = $this->filter_fields( $form, $args['field_ids'] );
$labels = array();
// replace the values/ids with text labels
foreach ( $fields as $field ) {
/* @var GF_Field $field */
$field_id = $field->id;
$field = GFFormsModel::get_field( $form, $field_id );
$input_type = $field->get_input_type();
if ( in_array( $input_type , array( 'likert', 'rank', 'rating' ) ) ) {
$label = array();
$choice_labels = array();
foreach ( $field->choices as $choice ) {
$choice_labels[ $choice['value'] ] = $choice['text'];
}
if ( $input_type = 'likert' && $field->gsurveyLikertEnableMultipleRows ) {
/* @var GF_Field_Likert $field */
$label = array(
'label' => $field->label,
'cols' => $choice_labels,
'rows' => array(),
);
foreach ( $field->gsurveyLikertRows as $row ) {
$label['rows'][ $row['value'] ] = $row['text'];
}
} else {
$label['label'] = $field->label;
$label['choices'] = $choice_labels;
}
} else {
$inputs = $field->get_entry_inputs();
if ( empty( $inputs ) ) {
$label = $field->get_field_label( false, null );
} else {
$label = array();
$label[ (string) $field->id ] = $field->get_field_label( false, null );
foreach ( $inputs as $input ) {
$label[ (string) $input['id'] ] = $input['label'];
}
}
}
$labels[ $field->id ] = $label;
}
return $labels;
}
/**
* Filters the form array, returning only the fields matching the specified list of $field_ids
*
* @since 2.4-beta-1
*
* @param array $form The form array
* @param array $field_ids The list of fields to be returned
*
* @return array
*/
private function filter_fields( $form, $field_ids ) {
$fields = $form['fields'];
if ( is_array( $field_ids ) && ! empty( $field_ids ) ) {
foreach ( $fields as $key => $field ) {
$found = false;
foreach ( $field_ids as $field_id ) {
if ( intval( $field_id ) == $field->id ) {
$found = true;
break;
}
}
if ( ! $found ) {
unset( $fields[ $key ] );
}
}
$fields = array_values( $fields );
}
return $fields;
}
}

View File

@@ -0,0 +1,281 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Form_Feeds_Controller extends GF_REST_Controller {
/**
* @since 2.4
*
* @var string
*/
public $rest_base = 'forms/(?P<form_id>[\d]+)/feeds';
/**
* Register the routes for the objects of the controller.
*
* @since 2.4
*/
public function register_routes() {
$namespace = $this->namespace;
$base = $this->rest_base;
register_rest_route( $namespace, '/' . $base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
) );
}
/**
* Get a collection of feeds for the form.
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$form_id = $request['form_id'];
$addon_slug = $request['addon'];
$feed_ids = $request['include'];
if ( ! empty( $feed_ids ) ) {
if ( ! is_array( $feed_ids ) ) {
$feed_ids = array( $feed_ids );
}
$feed_ids = array_map( 'absint', $feed_ids );
}
$feeds = GFAPI::get_feeds( $feed_ids, $form_id, $addon_slug );
if ( is_wp_error( $feeds ) ) {
return $feeds;
}
return new WP_REST_Response( $feeds, 200 );
}
/**
* Create one feed for the form.
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function create_item( $request ) {
$feed = $this->prepare_item_for_database( $request );
if ( is_wp_error( $feed ) ) {
return $feed;
}
$form_id = $feed['form_id'];
$feed_id = GFAPI::add_feed( $form_id, $feed['meta'], $feed['addon_slug'] );
if ( is_wp_error( $feed_id ) ) {
$feed_id->add_data( array( 'status' => $this->get_error_status( $feed_id ) ) );
return $feed_id;
}
$feed['id'] = $feed_id;
$response = $this->prepare_item_for_response( $feed, $request );
$response->set_status( 201 );
$base = sprintf( 'forms/%d/feeds', $form_id );
$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $base, $feed_id ) ) );
return $response;
}
/**
* Prepare the item for the REST response.
*
* @since 2.4
*
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
*
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $item, $request ) {
return rest_ensure_response( $item );
}
/**
* Check if a given request has access to get items
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_items_permissions_check( $request ) {
/**
* Filters the capability required to get feeds via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_get_feeds', 'gravityforms_edit_forms', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to create items
*
* @since 2.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function create_item_permissions_check( $request ) {
/**
* Filters the capability required to create feeds via the REST API.
*
* @since 2.0-beta-2
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_post_feeds', 'gravityforms_edit_forms', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Prepare the item for create or update operation
*
* @since 2.4
*
* @param WP_REST_Request $request Request object
*
* @return WP_Error|array $prepared_item
*/
protected function prepare_item_for_database( $request ) {
$feed = $request->get_json_params();
if ( empty( $feed ) ) {
return new WP_Error( 'missing_feed', __( 'Missing feed JSON', 'gravityforms' ), array( 'status' => 400 ) );
}
$url_params = $request->get_url_params();
// Check the URL params first
$form_id = rgar( $url_params, 'form_id' );
if ( empty( $form_id ) ) {
$form_id = rgar( $feed, 'form_id' );
}
if ( isset( $form_id ) ) {
$feed['form_id'] = absint( $form_id );
} else {
return new WP_Error( 'missing_form_id', __( 'Missing form id', 'gravityforms' ), array( 'status' => 400 ) );
}
$addon_slug = isset( $feed['addon_slug'] ) ? $feed['addon_slug'] : $request['addon'];
if ( empty( $addon_slug ) ) {
return new WP_Error( 'missing_addon_slug', __( 'Missing add-on slug', 'gravityforms' ), array( 'status' => 400 ) );
}
if ( empty( $feed['meta'] ) ) {
return new WP_Error( 'missing_feed_meta', __( 'Missing feed meta', 'gravityforms' ), array( 'status' => 400 ) );
}
return $feed;
}
/**
* Get the query params for collections
*
* @since 2.4
*
* @return array
*/
public function get_collection_params() {
return array(
'include' => array(
'description' => __( 'Limit result set to specific IDs.' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
),
);
}
/**
* Get the Feed schema, conforming to JSON Schema.
*
* @since 2.4
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'feed',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the feed.', 'gravityforms' ),
'type' => 'integer',
'readonly' => true,
),
'form_id' => array(
'description' => __( 'The Form ID for the feed.', 'gravityforms' ),
'type' => 'integer',
'required' => true,
'readonly' => true,
),
'meta' => array(
'description' => __( 'The JSON string containing the feed meta.', 'gravityforms' ),
'type' => 'object',
'readonly' => false,
),
'addon_slug' => array(
'description' => __( 'The add-on the feed belongs to.', 'gravityforms' ),
'type' => 'integer',
'readonly' => true,
),
),
);
return $schema;
}
}

View File

@@ -0,0 +1,98 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Form_Field_Filters_Controller extends GF_REST_Controller {
/**
* The base of this controller's route.
*
* @since 2.4.22
*
* @var string
*/
public $rest_base = 'forms/(?P<form_id>[\d]+)/field-filters';
/**
* Register the routes for the objects of the controller.
*
* @since 2.4.22
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
) );
}
/**
* Returns the field filters for the specified form.
*
* @since 2.4.22
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$form = GFAPI::get_form( $request['form_id'] );
if ( ! $form ) {
return new WP_Error( 'gf_not_found', __( 'Form not found', 'gravityforms' ), array( 'status' => 404 ) );
}
if ( ! empty( $request['_admin_labels'] ) ) {
/** @var GF_Field $field The field object. */
foreach ( $form['fields'] as $field ) {
$field->set_context_property( 'use_admin_label', true );
}
}
return new WP_REST_Response( GFCommon::get_field_filter_settings( $form ) );
}
/**
* Check if the user for the current request has permission to get the field filters.
*
* @since 2.4.22
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_items_permissions_check( $request ) {
/**
* Filters the capability required to get the field filters via the REST API.
*
* @since 2.4.22
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_get_field_filters', 'gravityforms_view_entries', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Returns an array of supported query params for this endpoint.
*
* @since 2.4.22
*
* @return array
*/
public function get_collection_params() {
return array(
'_admin_labels' => array(
'description' => 'Whether to include the field admin labels in the response, if configured.',
'type' => 'integer',
),
);
}
}

View File

@@ -0,0 +1,122 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Form_Results_Controller extends GF_REST_Controller {
/**
* @since 2.4-beta-1
*
* @var string
*/
public $rest_base = 'forms/(?P<form_id>[\d]+)/results';
/**
* Register the routes for the objects of the controller.
*
* @since 2.4-beta-1
*/
public function register_routes() {
$namespace = $this->namespace;
$base = $this->rest_base;
register_rest_route( $namespace, '/' . $base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
) );
register_rest_route( $namespace, '/' . $base . '/schema', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_public_item_schema' ),
'permission_callback' => '__return_true',
) );
}
/**
* Get a collection of results.
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$form_id = $request['form_id'];
$search_params = $this->parse_entry_search_params( $request );
$search_criteria = rgar( $search_params, 'search_criteria' );
$args = array(
'page_size' => 100,
'time_limit' => 5,
'wait' => 5,
);
$data = gf_results_cache()->get_results( $form_id, $search_criteria, $args );
$response = $this->prepare_item_for_response( $data, $request );
return $response;
}
/**
* Check if a given request has access to get items
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_items_permissions_check( $request ) {
/**
* Filters the capability required to get form results via the web API.
*
* @since 2.0-beta-2
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_get_results', 'gravityforms_view_entries', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Prepare the item for the REST response
*
* @since 2.4-beta-1
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
*
* @return mixed
*/
public function prepare_item_for_response( $item, $request ) {
$response = new WP_REST_Response( $item, 200 );
return $response;
}
/**
* Get the query params for collections
*
* @since 2.4-beta-1
*
* @return array
*/
public function get_collection_params() {
return array(
'search' => array(
'description' => 'The search criteria.',
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
),
);
}
}

View File

@@ -0,0 +1,93 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Form_Submissions_Validation_Controller extends GF_REST_Controller {
/**
* The base of this controller's route.
*
* @since 2.6.4
*
* @var string
*/
public $rest_base = 'forms/(?P<form_id>[\d]+)/submissions/validation';
/**
* Registers the route.
*
* @since 2.6.4
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'validate_form' ),
'permission_callback' => array( $this, 'validate_form_permissions_check' ),
),
) );
}
/**
* Validates submitted values for the specified form.
*
* @since 2.6.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function validate_form( $request ) {
$params = $request->get_json_params();
$input_values = $params;
if ( empty( $params ) ) {
$params = $request->get_body_params();
$input_values = array(); // The input values are already in $_POST.
}
$field_values = rgar( $params, 'field_values', array() );
$target_page = rgar( $params, 'target_page', 0 );
$source_page = rgar( $params, 'source_page', 1 );
$result = GFAPI::validate_form( rgar( $request->get_url_params(), 'form_id' ), $input_values, $field_values, $target_page, $source_page );
if ( is_wp_error( $result ) ) {
return new WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => 400 ) );
}
return $this->prepare_item_for_response( $result, $request );
}
/**
* All users can submit values for validation.
*
* @since 2.6.4
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|boolean
*/
public function validate_form_permissions_check( $request ) {
return true;
}
/**
* Prepares the item for the REST response.
*
* @since 2.6.4
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
*
* @return WP_REST_Response
*/
public function prepare_item_for_response( $item, $request ) {
$status = $item['is_valid'] ? 200 : 400;
return new WP_REST_Response( $item, $status );
}
}

View File

@@ -0,0 +1,166 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Form_Submissions_Controller extends GF_REST_Controller {
/**
* @since 2.4-beta-1
*
* @var string
*/
public $rest_base = 'forms/(?P<form_id>[\d]+)/submissions';
/**
* Register the routes for the objects of the controller.
*
* @since 2.4-beta-1
*/
public function register_routes() {
$namespace = $this->namespace;
$base = $this->rest_base;
register_rest_route( $namespace, '/' . $base, array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
) );
}
/**
* Create one item from the collection.
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function create_item( $request ) {
if ( rgar( $request->get_query_params(), '_validate_only' ) ) {
return ( new GF_REST_Form_Submissions_Validation_Controller() )->validate_form( $request );
}
$form_id = $request['form_id'];
$params = $request->get_json_params();
if ( empty( $params ) ) {
$input_values = $request->get_body_params();
$field_values = isset( $input_values['field_values'] ) ? $input_values['field_values'] : array();
$target_page = isset( $input_values['target_page'] ) ? $input_values['target_page'] : 0;
$source_page = isset( $input_values['source_page'] ) ? $input_values['source_page'] : 1;
$input_values = array(); // The input values are already in $_POST
} else {
$input_values = $params;
$field_values = isset( $params['field_values'] ) ? $params['field_values'] : array();
$target_page = isset( $params['target_page'] ) ? $params['target_page'] : 0;
$source_page = isset( $params['source_page'] ) ? $params['source_page'] : 1;
}
$result = GFAPI::submit_form( $form_id, $input_values, $field_values, $target_page, $source_page );
if ( is_wp_error( $result ) ) {
return new WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => 400 ) );
}
if ( ! current_user_can( 'gravityforms_view_entries' ) && ! current_user_can( 'gravityforms_edit_entries' ) ) {
unset( $result['entry_id'] );
}
$response = $this->prepare_item_for_response( $result, $request );
if ( isset( $result['confirmation_type'] ) && $result['confirmation_type'] == 'redirect' ) {
$response->header( 'Location', $result['confirmation_redirect'] );
}
return $response;
}
/**
* Check if a given request has access to create items.
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|boolean
*/
public function create_item_permissions_check( $request ) {
return true;
}
/**
* Prepare the item for the REST response
*
* @since 2.4-beta-1
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
*
* @return mixed
*/
public function prepare_item_for_response( $item, $request ) {
$status = $item['is_valid'] ? 200 : 400;
$response = new WP_REST_Response( $item, $status );
return $response;
}
/**
* Get the query params for collections
*
* @since 2.4-beta-1
*
* @return array
*/
public function get_collection_params() {
return array();
}
/**
* Get the Entry schema, conforming to JSON Schema.
*
* @since 2.4-beta-1
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'form-submission',
'type' => 'object',
'properties' => array(
'input_[Field ID]' => array(
'description' => __( 'The input values.', 'gravityforms' ),
'type' => 'string',
),
'field_values' => array(
'description' => __( 'The field values.', 'gravityforms' ),
'type' => array( 'string', 'array' ),
),
'target_page' => array(
'description' => 'The target page number.',
'type' => 'integer',
),
'source_page' => array(
'description' => 'The source page number.',
'type' => 'integer',
),
),
);
return $schema;
}
}

View File

@@ -0,0 +1,455 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Forms_Controller extends GF_REST_Controller {
/**
* @since 2.4-beta-1
*
* @var string
*/
public $rest_base = 'forms';
/**
* Register the routes for the objects of the controller.
*
* @since 2.4-beta-1
*/
public function register_routes() {
$namespace = $this->namespace;
$base = $this->rest_base;
register_rest_route( $namespace, '/' . $base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( true ),
),
) );
register_rest_route( $namespace, '/' . $base . '/(?P<id>[\d]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => array(
'default' => 'view',
),
),
),
array(
'methods' => 'PUT',
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( false ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
),
),
),
) );
register_rest_route( $namespace, '/' . $base . '/schema', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_public_item_schema' ),
'permission_callback' => '__return_true',
) );
}
/**
* Get a collection of items.
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$form_ids = $request['include'];
if ( ! empty( $form_ids ) ) {
if ( ! is_array( $form_ids ) ) {
$form_ids = array( $form_ids );
}
$form_ids = array_map( 'absint', $form_ids );
}
$data = array();
if ( $form_ids && is_array( $form_ids ) ) {
foreach ( $form_ids as $id ) {
$form = GFAPI::get_form( $id );
$data[ $id ] = $form;
}
} else {
$forms = GFFormsModel::get_forms( true );
foreach ( $forms as $form ) {
/**
* Allows third-party code to omit form totals from the API response. This is useful for increasing
* the performance of the endpoint when totals aren't required.
*
* @since 2.5
*
* @var bool $include_totals Whether to include totals; defaults to true.
* @var object $form The current form object.
*/
$include_totals = gf_apply_filters( array( 'gform_rest_api_retrieve_form_totals', $form->id ), true, $form );
$form_id = $form->id;
$form_info = array(
'id' => $form_id,
'title' => $form->title,
);
if ( $include_totals ) {
$totals = GFFormsModel::get_form_counts( $form_id );
$form_info['entries'] = rgar( $totals, 'total' );
}
$data[ $form_id ] = $form_info;
}
}
return new WP_REST_Response( $data, 200 );
}
/**
* Get one item from the collection.
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$form_id = $request['id'];
$form = GFAPI::get_form( $form_id );
if ( $form ) {
return new WP_REST_Response( $form, 200 );
} else {
return new WP_Error( 'gf_not_found', __( 'Form not found', 'gravityforms' ) );
}
}
/**
* Create one item from the collection.
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Request
*/
public function create_item( $request ) {
$form = $this->prepare_item_for_database( $request );
if ( is_wp_error( $form ) ) {
return new WP_Error( $form->get_error_code(), $form->get_error_message(), array( 'status' => 400 ) );
}
$form_id = GFAPI::add_form( $form );
if ( is_wp_error( $form_id ) ) {
$status = $this->get_error_status( $form_id );
return new WP_Error( $form_id->get_error_code(), $form_id->get_error_message(), array( 'status' => $status ) );
}
$form = GFAPI::get_form( $form_id );
$response = $this->prepare_item_for_response( $form, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $form_id ) ) );
return $response;
}
/**
* Update one item from the collection
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Request
*/
public function update_item( $request ) {
$form_id = $request['id'];
$form = $this->prepare_item_for_database( $request );
if ( is_wp_error( $form ) ) {
return $form;
}
$result = GFAPI::update_form( $form, $form_id );
if ( is_wp_error( $result ) ) {
$status = $this->get_error_status( $result );
return new WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => $status ) );
}
$form = GFAPI::get_form( $form_id );
$response = $this->prepare_item_for_response( $form, $request );
return rest_ensure_response( $response );
}
/**
* Delete one item from the collection
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Request
*/
public function delete_item( $request ) {
$form_id = $request['id'];
$form = GFAPI::get_form( $form_id );
if ( empty( $form ) ) {
return new WP_Error( 'gf_form_invalid_id', __( 'Invalid form id.', 'gravityforms' ), array( 'status' => 404 ) );
}
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
if ( $force ) {
$result = GFAPI::delete_form( $form_id );
if ( is_wp_error( $result ) ) {
$message = $result->get_error_message();
return new WP_Error( 'gf_cannot_delete', $message, array( 'status' => 500 ) );
}
$previous = $this->prepare_item_for_response( $form, $request );
$response = new WP_REST_Response();
$response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) );
} else {
if ( rgar( $form, 'is_trash' ) ) {
$message = __( 'The form has already been deleted.', 'gravityforms' );
return new WP_Error( 'gf_already_trashed', $message, array( 'status' => 410 ) );
}
// Trash the form
GFAPI::update_form_property( $form_id, 'is_trash', 1 );
$form = GFAPI::get_form( $form_id );
$response = rest_ensure_response( $form );
}
return $response;
}
/**
* Check if a given request has access to get items
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_items_permissions_check( $request ) {
/**
* Filters the capability required to get forms via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_get_forms', 'gravityforms_edit_forms', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to get a specific item
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_item_permissions_check( $request ) {
/**
* Filters the capability required to get forms via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_get_forms', 'gravityforms_edit_forms', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to create items
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function create_item_permissions_check( $request ) {
/**
* Filters the capability required to create forms via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_post_forms', 'gravityforms_create_form', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to update a specific item.
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function update_item_permissions_check( $request ) {
/**
* Filters the capability required to update forms via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_put_forms', 'gravityforms_create_form', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to delete a specific item.
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function delete_item_permissions_check( $request ) {
/**
* Filters the capability required to delete forms via the REST API.
*
* @since 2.4
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_delete_forms', 'gravityforms_delete_forms', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Prepare the item for create or update operation.
*
* The Form object must be sent as a JSON string in order to preserve boolean values.
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Request object
*
* @return WP_Error|array $prepared_item
*/
protected function prepare_item_for_database( $request ) {
$form_json = $request->get_json_params();
if ( ! $form_json ) {
$form_json = $request->get_body_params();
if ( empty( $form_json ) || is_array( $form_json ) ) {
return new WP_Error( 'missing_form', __( 'The Form object must be sent as a JSON string in the request body with the content-type header set to application/json.', 'gravityforms' ) );
}
}
$form = ( is_string( $form_json ) ) ? json_decode( $form_json, true ) : $form_json;
$form = GFFormsModel::convert_field_objects( $form );
$form = GFFormsModel::sanitize_settings( $form );
return $form;
}
/**
* Prepare the item for the REST response
*
* @since 2.4-beta-1
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
*
* @return mixed
*/
public function prepare_item_for_response( $item, $request ) {
$response = new WP_REST_Response( $item, 200 );
return $response;
}
/**
* Get the query params for collections
*
* @since 2.4-beta-1
*
* @return array
*/
public function get_collection_params() {
return array(
'page' => array(
'description' => 'Current page of the collection.',
'type' => 'integer',
'default' => 1,
'sanitize_callback' => 'absint',
),
'per_page' => array(
'description' => 'Maximum number of items to be returned in result set.',
'type' => 'integer',
'default' => 10,
'sanitize_callback' => 'absint',
),
'search' => array(
'description' => 'The search criteria.',
'type' => 'array',
'sanitize_callback' => 'sanitize_text_field',
),
);
}
}

View File

@@ -0,0 +1,384 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_REST_Notes_Controller extends GF_REST_Entry_Notes_Controller {
/**
* @var string
*/
public $rest_base = 'notes';
/**
* Register the routes for the objects of the controller.
*/
public function register_routes() {
$namespace = $this->namespace;
$base = $this->rest_base;
register_rest_route(
$namespace,
'/' . $base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
)
);
register_rest_route(
$namespace,
'/' . $base . '/(?P<note_id>[\d]+)',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => 'PUT',
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( false ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => $this->get_collection_params(),
),
)
);
}
/**
* Get one note.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$note_id = $request->get_param( 'note_id' );
$note = GFAPI::get_note( $note_id );
if ( is_wp_error( $note ) ) {
return new WP_Error( 'gf_note_invalid_id', __( 'Invalid note id.', 'gravityforms' ), array( 'status' => 404 ) );
}
$data = $this->prepare_item_for_response( $note, $request );
return $data;
}
/**
* Get multiple notes.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$criteria = $request->get_params();
$allowed_criteria = array(
'entry_id',
'user_id',
'note_type',
'sub_type',
'user_name'
);
$search_criteria = array();
foreach ( $criteria as $key => $value ) {
if ( in_array( $key, $allowed_criteria ) ) {
$search_criteria[$key] = $value;
}
}
$sorting = '';
if ( isset( $criteria['sorting'] ) ) {
$sorting = $criteria['sorting'];
}
$notes = GFAPI::get_notes( $search_criteria, $sorting );
if ( is_wp_error( $notes ) ) {
return new WP_Error( 'gf_entry_invalid_notes', __( 'Error retrieving notes.', 'gravityforms' ), array( 'status' => 404 ) );
}
if ( ! is_array( $notes ) || empty( $notes ) ) {
return array();
}
$data = array();
foreach ( $notes as $note ) {
$data[ $note->id ] = $note;
}
$response = new WP_REST_Response( $data, 200 );
return $response;
}
/**
* Create one note.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Request
*/
public function create_item( $request ) {
$note = $this->prepare_item_for_database( $request );
$entry_id = $request->get_param( 'entry_id' );
if ( is_wp_error( $note ) ) {
return $note;
}
$note_id = GFAPI::add_note( $entry_id, $note['user_id'], $note['user_name'], $note['note'] );
if ( is_wp_error( $note_id ) ) {
$status = $this->get_error_status( $note_id );
return new WP_Error( $note_id->get_error_code(), $note_id->get_error_message(), array( 'status' => $status ) );
}
$note['id'] = $note_id;
$note = $this->prepare_note_for_response( $note_id );
$response = rest_ensure_response( $note );
$response->set_status( 201 );
$base = sprintf( 'entries/%d/notes/', $note_id );
$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $base, $note_id ) ) );
return $response;
}
/**
* Update one note.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
$note = $this->prepare_item_for_database( $request );
$note['id'] = $request['note_id'];
if ( is_wp_error( $note ) ) {
return $note;
}
$result = GFAPI::update_note( $note, $request->get_param( 'note_id' ) );
if ( is_wp_error( $result ) ) {
$status = $this->get_error_status( $result );
return new WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => $status ) );
}
$updated_note = GFAPI::get_note( $note['id'] );
$response = $this->prepare_item_for_response( $updated_note, $request );
$response->set_status( 201 );
$base = sprintf( 'entries/%d/notes/', $note['id'] );
$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $base, $note['id'] ) ) );
return rest_ensure_response( $response );
}
/**
* Delete one note.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
$note_id = $request['note_id'];
$note = GFAPI::get_note( $note_id );
if ( is_wp_error( $note ) ) {
return new WP_Error( 'gf_entry_invalid_id', __( 'Invalid note id.', 'gravityforms' ), array( 'status' => 404 ) );
}
$result = GFAPI::delete_note( $note_id );
if ( is_wp_error( $result ) ) {
$message = $result->get_error_message();
return new WP_Error( 'gf_cannot_delete', $message, array( 'status' => 500 ) );
}
$previous = $this->prepare_item_for_response( $note, $request );
$response = new WP_REST_Response();
$response->set_data(
array(
'deleted' => true,
'previous' => $previous->get_data(),
)
);
return $response;
}
/**
* Check if a given request has access to get items.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_items_permissions_check( $request ) {
return parent::get_items_permissions_check( $request );
}
/**
* Check if a given request has access to get a specific item.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function get_item_permissions_check( $request ) {
return parent::get_item_permissions_check( $request );
}
/**
* Check if a given request has access to create items.
*
* @since 2.4-.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function create_item_permissions_check( $request ) {
/**
* Filters the capability required to create entries via the REST API.
*
* @since 2.4.18
*
* @param string|array $capability The capability required for this endpoint.
* @param WP_REST_Request $request Full data about the request.
*/
$capability = apply_filters( 'gform_rest_api_capability_post_notes', 'gravityforms_edit_entry_notes', $request );
return $this->current_user_can_any( $capability, $request );
}
/**
* Check if a given request has access to update a specific item.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function update_item_permissions_check( $request ) {
return parent::update_item_permissions_check( $request );
}
/**
* Check if a given request has access to delete a specific item.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|bool
*/
public function delete_item_permissions_check( $request ) {
return parent::delete_item_permissions_check( $request );
}
/**
* Prepare the item for create or update operation.
*
* @since 2.4.18
*
* @param WP_REST_Request $request Request object.
*
* @return WP_Error|array $prepared_item.
*/
protected function prepare_item_for_database( $request ) {
$note = $request->get_json_params();
if ( empty( $note ) ) {
return new WP_Error( 'missing_entry', __( 'Missing entry JSON', 'gravityforms' ) );
}
$note['user_id'] = intval( $note['user_id'] );
$note['note'] = wp_kses_post( $note['value'] );
return $note;
}
/**
* Prepare the item for the REST response.
*
* @since 2.4.18
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
*
* @return WP_REST_Response Returns the item wrapped in a WP_REST_Response object
*/
public function prepare_item_for_response( $item, $request ) {
$item = $this->prepare_note_for_response( $item->id );
$response = new WP_REST_Response( $item, 200 );
return $response;
}
/***
* Prepares note for REST API response, decoding or unserializing appropriate fields.
*
* @since 2.4.18
*
* @param int $note_id The note id.
*
* @return bool|array Returns the entry array ready to be send in the REST API response.
*/
public function prepare_note_for_response( $note_id ) {
$note = GFAPI::get_note( $note_id );
if ( is_wp_error( $note ) || ! isset( $note->ID ) ) {
return $note;
}
return $note;
}
}

View File

@@ -0,0 +1,436 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Abstract Rest Controller Class
*
* @author Rocketgenius
* @category API
* @package Rocketgenius/Abstracts
* @extends WP_REST_Controller
*/
abstract class GF_REST_Controller extends WP_REST_Controller {
/**
* Endpoint namespace.
*
* @since 2.4-beta-1
*
* @var string
*/
protected $namespace = 'gf/v2';
/**
* Route base.
*
* @since 2.4-beta-1
*
* @var string
*/
protected $rest_base = '';
/**
* Indicates if the capability validation request has been logged.
*
* Without this the other registered methods for the route will also be logged when rest_send_allow_header() in WP rest-api.php runs.
*
* @since 2.4.11
*
* @var bool
*/
protected $_validate_caps_logged = false;
/**
* Parses the entry search, sort and paging parameters from the request
*
* @since 2.4-beta-1
*
* @param WP_REST_Request $request Full data about the request.
*
* @return array Returns an associative array with the "search_criteria", "paging" and "sorting" keys appropriately populated.
*/
public function parse_entry_search_params( $request ) {
// Sorting parameters
$sorting_param = $request->get_param( 'sorting' );
$sort_key = isset( $sorting_param['key'] ) && ! empty( $sorting_param['key'] ) ? $sorting_param['key'] : 'id';
$sort_dir = isset( $sorting_param['direction'] ) && ! empty( $sorting_param['direction'] ) ? $sorting_param['direction'] : 'DESC';
$sorting = array( 'key' => $sort_key, 'direction' => $sort_dir );
if ( isset( $sorting_param['is_numeric'] ) ) {
$sorting['is_numeric'] = $sorting_param['is_numeric'];
}
// paging parameters
$paging_param = $request->get_param( 'paging' );
$page_size = isset( $paging_param['page_size'] ) ? intval( $paging_param['page_size'] ) : 10;
if ( isset( $paging_param['current_page'] ) ) {
$current_page = intval( $paging_param['current_page'] );
$offset = $page_size * ( $current_page - 1 );
} else {
$offset = isset( $paging_param['offset'] ) ? intval( $paging_param['offset'] ) : 0;
}
$paging = array( 'offset' => $offset, 'page_size' => $page_size );
$search = $request->get_param( 'search' );
if ( isset( $search ) ) {
if ( ! is_array( $search ) ) {
$search = urldecode( ( stripslashes( $search ) ) );
$search = json_decode( $search, true );
}
} else {
$search = array();
}
if ( ! isset( $search['status'] ) ) {
$search['status'] = 'active';
}
$params = array(
'search_criteria' => $search,
'paging' => $paging,
'sorting' => $sorting,
);
$form_ids = $request->get_param( 'form_ids' );
if ( isset( $form_ids ) ) {
$params['form_ids'] = $form_ids;
}
return $params;
}
/**
* JSON encodes list fields in the specified $entry and returns the new $entry
*
* @since 2.4-beta-1
*
* @param array $entry The entry object
*
* @return array Returns the $entry array with the list fields json encoded
*/
public function maybe_json_encode_list_fields( $entry ) {
$form_id = $entry['form_id'];
$form = GFAPI::get_form( $form_id );
if ( ! empty( $form['fields'] ) && is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
/* @var GF_Field $field */
if ( $field->get_input_type() == 'list' ) {
$new_value = maybe_unserialize( $entry[ $field->id ] );
if ( ! $this->is_json( $new_value ) ) {
$new_value = json_encode( $new_value );
}
$entry[ $field->id ] = $new_value;
}
}
}
return $entry;
}
/**
* Determines if the specified values is a JSON encoded string
*
* @since 2.4-beta-1
*
* @param mixed $value The value to be checked
*
* @return bool True if the speficied value is JSON encoded. False otherwise
*/
public static function is_json( $value ) {
if ( is_string( $value ) && in_array( substr( $value, 0, 1 ), array( '{', '[' ) ) && is_array( json_decode( $value, ARRAY_A ) ) ) {
return true;
}
return false;
}
/**
* Filters an entry, removing fields that aren't in the list of specified $field_ids
*
* @since 2.4-beta-1
*
* @param array $entry The entry to be filtered
* @param array $field_ids The field IDs to be kept in the entry
*
* @return array Returns the entry array, containing only the field_ids specified in the $field_ids array.
*/
public static function filter_entry_fields( $entry, $field_ids ) {
if ( ! is_array( $field_ids ) ) {
$field_ids = array( $field_ids );
}
$new_entry = array();
foreach ( $entry as $key => $val ) {
if ( in_array( $key, $field_ids ) || ( is_numeric( $key ) && in_array( intval( $key ), $field_ids ) ) ) {
$new_entry[ $key ] = $val;
}
}
return $new_entry;
}
/***
* Prepares entry for REST API response, decoding or unserializing appropriate fields
*
* @since 2.4-beta-1
*
* @param array $entry The entry array
*
* @return bool|array Returns the entry array ready to be send in the REST API response.
*/
public function prepare_entry_for_response( $entry ) {
if ( is_wp_error( $entry ) || ! isset( $entry['form_id'] ) ) {
return $entry;
}
$form = GFAPI::get_form( $entry['form_id'] );
foreach ( $form['fields'] as $field ) {
if ( empty( $entry[ $field->id ] ) ) {
continue;
}
if ( $field instanceof GF_Field_MultiSelect ) {
$entry[ $field->id ] = $field->to_array( $entry[ $field->id ] );
} elseif ( $field instanceof GF_Field_FileUpload && $field->multipleFiles ) {
$entry[ $field->id ] = json_decode( $entry[ $field->id ] );
} elseif ( $field instanceof GF_Field_List ) {
$entry[ $field->id ] = maybe_unserialize( $entry[ $field->id ] );
}
}
return $entry;
}
/***
* Determines if the value of the specified field is stored in JSON format
*
* @since 2.4-beta-1
*
* @param GF_Field $field The field to be checked
*
* @return bool Returns true if the specified field's value is stored in JSON format. Retruns false otherwise.
*/
public function is_field_value_json( $field ) {
$input_type = $field->get_input_type();
if ( in_array( $input_type, array( 'multiselect', 'list' ) ) ) {
return true;
}
if ( $input_type == 'fileupload' && $field->multipleFiles ) {
return true;
}
return false;
}
/**
* Serializes list fields in the specified $entry array.
*
* @since 2.4-beta-1
*
* @param array $entry The entry array
* @param null $form_id The current form id
*
* @return array Returns the $entry array with all it's list fields serialized.
*/
public function maybe_serialize_list_fields( $entry, $form_id = null ) {
if ( empty( $form_id ) ) {
$form_id = $entry['form_id'];
}
$form = GFAPI::get_form( $form_id );
if ( ! empty( $form['fields'] ) && is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
/* @var GF_Field $field */
if ( $field->get_input_type() == 'list' && isset( $entry[ $field->id ] ) ) {
$new_list_value = self::maybe_decode_json( $entry[ $field->id ] );
if ( ! is_serialized( $new_list_value ) ) {
$new_list_value = serialize( $new_list_value );
}
$entry[ $field->id ] = $new_list_value;
}
}
}
return $entry;
}
/**
* JSON encodes appropriate fields in the specified $entry array
*
* @since 2.4-beta-1
*
* @param array $entry The entry array.
*
* @return array Returns the $entry array with all appropriate fields JSON encoded.
*/
public function maybe_json_encode_applicable_fields( $entry ) {
$form = GFAPI::get_form( $entry['form_id'] );
/** @var GF_Field $field */
foreach ( $form['fields'] as $field ) {
if ( empty( $entry[ $field->id ] ) ) {
continue;
}
if ( $field->get_input_type() === 'fileupload' && $field->multipleFiles ) {
$entry[ $field->id ] = json_encode( $entry[ $field->id ] );
} elseif ( $field instanceof GF_Field_MultiSelect ) {
$entry[ $field->id ] = $field->to_string( $entry[ $field->id ] );
}
}
return $entry;
}
/**
* Decodes JSON encoded strings.
*
* @since 2.4-beta-1
*
* @param string $value String to be decoded
*
* @return array|mixed Returns the decoded JSON array. If the specified $value isn't a JSON encoded string, returns
* $value.
*/
public static function maybe_decode_json( $value ) {
if ( self::is_json( $value ) ) {
return json_decode( $value, ARRAY_A );
}
return $value;
}
/**
* Returns the http error status
*
* @since 2.4-beta-1
*
* @param WP_Error $wp_error
*
* @return int Returns the http status recored in the specified $wp_error
*/
public function get_error_status( $wp_error ) {
$error_code = $wp_error->get_error_code();
$mappings = array(
'not_found' => 404,
'not_allowed' => 401,
);
$http_code = isset( $mappings[ $error_code ] ) ? $mappings[ $error_code ] : 400;
return $http_code;
}
/**
* Writes a message to the log
*
* @since 2.4-beta-1
*
* @param string $message
*/
public function log_debug( $message ) {
GFAPI::log_debug( $message );
}
/**
* Validates that the current user has the specified capability.
*
* @since 2.4.11
*
* @param string|array $capability The required capability.
* @param WP_REST_Request $request Full data about the request.
*
* @return bool
*/
public function current_user_can_any( $capability, $request ) {
$result = GFAPI::current_user_can_any( $capability );
if ( ! $this->_validate_caps_logged ) {
$this->log_debug( sprintf( '%s(): method: %s; route: %s; capability: %s; result: %s.', __METHOD__, $request->get_method(), $request->get_route(), json_encode( $capability ), json_encode( $result ) ) );
$this->_validate_caps_logged = true;
}
return $result;
}
/**
* Recursively patches the given item with the supplied changes (deletions, updates, and additions).
*
* @since 2.4.24
*
* @param mixed $current The existing item to be modified (e.g. feed).
* @param mixed $changes The changes to be applied.
*
* @return mixed
*/
public function patch_array_recursive( $current, $changes ) {
if ( ! $this->is_assoc_array( $changes ) ) {
return $changes;
}
if ( ! $this->is_assoc_array( $current ) ) {
$current = array();
}
foreach ( $changes as $key => $value ) {
if ( is_null( $value ) ) {
unset( $current[ $key ] );
continue;
}
$current[ $key ] = $this->patch_array_recursive( rgar( $current, $key ), $value );
}
return $current;
}
/**
* Determines if the passed variable is an associative array.
*
* @since 2.4.24
*
* @param mixed $array The variable to be checked.
*
* @return bool
*/
private function is_assoc_array( $array ) {
if ( ! is_array( $array ) ) {
return false;
}
foreach ( array_keys( $array ) as $key ) {
if ( $key !== (int) $key ) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,764 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* This is a copy of WP_REST_Controller which is not currently in the WordPress core.
* https://github.com/WP-API/WP-API/blob/develop/lib/endpoints/class-wp-rest-controller.php
*
* Last updated 17 August 2016
*
* Class WP_REST_Controller
*/
abstract class WP_REST_Controller {
/**
* The namespace of this controller's route.
*
* @since 2.4-beta-1
*
*
* @var string
*/
protected $namespace;
/**
* The base of this controller's route.
*
* @since 2.4-beta-1
*
*
* @var string
*/
protected $rest_base;
/**
* Register the routes for the objects of the controller.
*
* @since 2.4-beta-1
*
*/
public function register_routes() {
_doing_it_wrong( 'WP_REST_Controller::register_routes', __( 'The register_routes() method must be overriden' ), 'WPAPI-2.0' );
}
/**
* Check if a given request has access to get items.
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Get a collection of items.
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Check if a given request has access to get a specific item.
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Get one item from the collection.
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Check if a given request has access to create items.
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|boolean
*/
public function create_item_permissions_check( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Create one item from the collection.
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function create_item( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Check if a given request has access to update a specific item.
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|boolean
*/
public function update_item_permissions_check( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Update one item from the collection.
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Check if a given request has access to delete a specific item.
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|boolean
*/
public function delete_item_permissions_check( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Delete one item from the collection.
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Prepare the item for create or update operation.
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Request $request Request object.
*
* @return WP_Error|object $prepared_item
*/
protected function prepare_item_for_database( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Prepare the item for the REST response.
*
* @since 2.4-beta-1
*
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
*
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $item, $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Prepare a response for inserting into a collection.
*
* @since 2.4-beta-1
*
*
* @param WP_REST_Response $response Response object.
*
* @return array Response data, ready for insertion into collection data.
*/
public function prepare_response_for_collection( $response ) {
if ( ! ( $response instanceof WP_REST_Response ) ) {
return $response;
}
$data = (array) $response->get_data();
$server = rest_get_server();
if ( method_exists( $server, 'get_compact_response_links' ) ) {
$links = call_user_func( array( $server, 'get_compact_response_links' ), $response );
} else {
$links = call_user_func( array( $server, 'get_response_links' ), $response );
}
if ( ! empty( $links ) ) {
$data['_links'] = $links;
}
return $data;
}
/**
* Filter a response based on the context defined in the schema.
*
* @since 2.4-beta-1
*
*
* @param array $data
* @param string $context
*
* @return array
*/
public function filter_response_by_context( $data, $context ) {
$schema = $this->get_item_schema();
foreach ( $data as $key => $value ) {
if ( empty( $schema['properties'][ $key ] ) || empty( $schema['properties'][ $key ]['context'] ) ) {
continue;
}
if ( ! in_array( $context, $schema['properties'][ $key ]['context'] ) ) {
unset( $data[ $key ] );
}
if ( 'object' === $schema['properties'][ $key ]['type'] && ! empty( $schema['properties'][ $key ]['properties'] ) ) {
foreach ( $schema['properties'][ $key ]['properties'] as $attribute => $details ) {
if ( empty( $details['context'] ) ) {
continue;
}
if ( ! in_array( $context, $details['context'] ) ) {
if ( isset( $data[ $key ][ $attribute ] ) ) {
unset( $data[ $key ][ $attribute ] );
}
}
}
}
}
return $data;
}
/**
* Get the item's schema, conforming to JSON Schema.
*
* @since 2.4-beta-1
*
*
* @return array
*/
public function get_item_schema() {
return $this->add_additional_fields_schema( array() );
}
/**
* Get the item's schema for display / public consumption purposes.
*
* @since 2.4-beta-1
*
*
* @return array
*/
public function get_public_item_schema() {
$schema = $this->get_item_schema();
foreach ( $schema['properties'] as &$property ) {
if ( isset( $property['arg_options'] ) ) {
unset( $property['arg_options'] );
}
}
return $schema;
}
/**
* Get the query params for collections.
*
* @since 2.4-beta-1
*
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param(),
'page' => array(
'description' => __( 'Current page of the collection.' ),
'type' => 'integer',
'default' => 1,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
'minimum' => 1,
),
'per_page' => array(
'description' => __( 'Maximum number of items to be returned in result set.' ),
'type' => 'integer',
'default' => 10,
'minimum' => 1,
'maximum' => 100,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
),
'search' => array(
'description' => __( 'Limit results to those matching a string.' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
),
);
}
/**
* Get the magical context param.
*
* Ensures consistent description between endpoints, and populates enum from schema.
*
* @since 2.4-beta-1
*
*
* @param array $args
*
* @return array
*/
public function get_context_param( $args = array() ) {
$param_details = array(
'description' => __( 'Scope under which the request is made; determines fields present in response.' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
);
$schema = $this->get_item_schema();
if ( empty( $schema['properties'] ) ) {
return array_merge( $param_details, $args );
}
$contexts = array();
foreach ( $schema['properties'] as $attributes ) {
if ( ! empty( $attributes['context'] ) ) {
$contexts = array_merge( $contexts, $attributes['context'] );
}
}
if ( ! empty( $contexts ) ) {
$param_details['enum'] = array_unique( $contexts );
rsort( $param_details['enum'] );
}
return array_merge( $param_details, $args );
}
/**
* Add the values from additional fields to a data object.
*
* @since 2.4-beta-1
*
*
* @param array $object
* @param WP_REST_Request $request
*
* @return array modified object with additional fields.
*/
protected function add_additional_fields_to_object( $object, $request ) {
$additional_fields = $this->get_additional_fields();
foreach ( $additional_fields as $field_name => $field_options ) {
if ( ! $field_options['get_callback'] ) {
continue;
}
$object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type() );
}
return $object;
}
/**
* Update the values of additional fields added to a data object.
*
* @since 2.4-beta-1
*
*
* @param array $object
* @param WP_REST_Request $request
*/
protected function update_additional_fields_for_object( $object, $request ) {
$additional_fields = $this->get_additional_fields();
foreach ( $additional_fields as $field_name => $field_options ) {
if ( ! $field_options['update_callback'] ) {
continue;
}
// Don't run the update callbacks if the data wasn't passed in the request.
if ( ! isset( $request[ $field_name ] ) ) {
continue;
}
call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
}
}
/**
* Add the schema from additional fields to an schema array.
*
* The type of object is inferred from the passed schema.
*
* @since 2.4-beta-1
*
*
* @param array $schema Schema array.
*
* @return array
*/
protected function add_additional_fields_schema( $schema ) {
if ( empty( $schema['title'] ) ) {
return $schema;
}
/**
* Can't use $this->get_object_type otherwise we cause an inf loop.
*/
$object_type = $schema['title'];
$additional_fields = $this->get_additional_fields( $object_type );
foreach ( $additional_fields as $field_name => $field_options ) {
if ( ! $field_options['schema'] ) {
continue;
}
$schema['properties'][ $field_name ] = $field_options['schema'];
}
return $schema;
}
/**
* Get all the registered additional fields for a given object-type.
*
* @since 2.4-beta-1
*
*
* @param string $object_type
*
* @return array
*/
protected function get_additional_fields( $object_type = null ) {
if ( ! $object_type ) {
$object_type = $this->get_object_type();
}
if ( ! $object_type ) {
return array();
}
global $wp_rest_additional_fields;
if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
return array();
}
return $wp_rest_additional_fields[ $object_type ];
}
/**
* Get the object type this controller is responsible for managing.
*
* @since 2.4-beta-1
*
*
* @return string
*/
protected function get_object_type() {
$schema = $this->get_item_schema();
if ( ! $schema || ! isset( $schema['title'] ) ) {
return null;
}
return $schema['title'];
}
/**
* Get an array of endpoint arguments from the item schema for the controller.
*
* @since 2.4-beta-1
*
*
* @param string $method HTTP method of the request. The arguments
* for `CREATABLE` requests are checked for required
* values and may fall-back to a given default, this
* is not done on `EDITABLE` requests. Default is
* WP_REST_Server::CREATABLE.
*
* @return array $endpoint_args
*/
public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
$schema = $this->get_item_schema();
$schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
$endpoint_args = array();
foreach ( $schema_properties as $field_id => $params ) {
// Arguments specified as `readonly` are not allowed to be set.
if ( ! empty( $params['readonly'] ) ) {
continue;
}
$endpoint_args[ $field_id ] = array(
'validate_callback' => 'rest_validate_request_arg',
'sanitize_callback' => 'rest_sanitize_request_arg',
);
if ( isset( $params['description'] ) ) {
$endpoint_args[ $field_id ]['description'] = $params['description'];
}
if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
$endpoint_args[ $field_id ]['default'] = $params['default'];
}
if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
$endpoint_args[ $field_id ]['required'] = true;
}
foreach ( array( 'type', 'format', 'enum' ) as $schema_prop ) {
if ( isset( $params[ $schema_prop ] ) ) {
$endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
}
}
// Merge in any options provided by the schema property.
if ( isset( $params['arg_options'] ) ) {
// Only use required / default from arg_options on CREATABLE endpoints.
if ( WP_REST_Server::CREATABLE !== $method ) {
$params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '' ) );
}
$endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
}
}
return $endpoint_args;
}
/**
* Retrieves post data given a post ID or post object.
*
* This is a subset of the functionality of the `get_post()` function, with
* the additional functionality of having `the_post` action done on the
* resultant post object. This is done so that plugins may manipulate the
* post that is used in the REST API.
*
* @since 2.4-beta-1
*
*
* @see get_post()
* @global WP_Query $wp_query
*
* @param int|WP_Post $post Post ID or post object. Defaults to global $post.
*
* @return WP_Post|null A `WP_Post` object when successful.
*/
public function get_post( $post ) {
$post_obj = get_post( $post );
/**
* Filter the post.
*
* Allows plugins to filter the post object as returned by `\WP_REST_Controller::get_post()`.
*
* @param WP_Post|null $post_obj The post object as returned by `get_post()`.
* @param int|WP_Post $post The original value used to obtain the post object.
*/
$post = apply_filters( 'rest_the_post', $post_obj, $post );
return $post;
}
}
if ( ! function_exists( 'rest_sanitize_request_arg' ) ) {
/**
* Sanitize a request argument based on details registered to the route.
*
* @since 2.4-beta-1
*
*
* @param mixed $value
* @param WP_REST_Request $request
* @param string $param
*
* @return mixed
*/
function rest_sanitize_request_arg( $value, $request, $param ) {
$attributes = $request->get_attributes();
if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
return $value;
}
$args = $attributes['args'][ $param ];
if ( 'integer' === $args['type'] ) {
return (int) $value;
}
if ( isset( $args['format'] ) ) {
switch ( $args['format'] ) {
case 'string' :
return sanitize_text_field( $value );
case 'email' :
/*
* sanitize_email() validates, which would be unexpected
*/
return sanitize_text_field( $value );
case 'uri' :
return esc_url_raw( $value );
}
}
return $value;
}
}
if ( ! function_exists( 'rest_validate_request_arg' ) ) {
/**
* Validate a request argument based on details registered to the route.
*
* @since 2.4-beta-1
*
*
* @param mixed $value
* @param WP_REST_Request $request
* @param string $param
*
* @return WP_Error|boolean
*/
function rest_validate_request_arg( $value, $request, $param ) {
$attributes = $request->get_attributes();
if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
return true;
}
$args = $attributes['args'][ $param ];
if ( ! empty( $args['enum'] ) ) {
if ( ! in_array( $value, $args['enum'] ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not one of %s' ), $param, implode( ', ', $args['enum'] ) ) );
}
}
if ( 'integer' === $args['type'] && ! is_numeric( $value ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $param, 'integer' ) );
}
if ( 'string' === $args['type'] && ! is_string( $value ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $param, 'string' ) );
}
if ( isset( $args['format'] ) ) {
switch ( $args['format'] ) {
case 'date-time' :
if ( ! rest_parse_date( $value ) ) {
return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ) );
}
break;
case 'email' :
if ( ! is_email( $value ) ) {
return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) );
}
break;
}
}
if ( in_array( $args['type'], array( 'numeric', 'integer' ) ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be greater than %d (exclusive)' ), $param, $args['minimum'] ) );
} else if ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be greater than %d (inclusive)' ), $param, $args['minimum'] ) );
}
} else if ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be less than %d (exclusive)' ), $param, $args['maximum'] ) );
} else if ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be less than %d (inclusive)' ), $param, $args['maximum'] ) );
}
} else if ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be between %d (exclusive) and %d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
}
} else if ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be between %d (inclusive) and %d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
}
} else if ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be between %d (exclusive) and %d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
}
} else if ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be between %d (inclusive) and %d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
}
}
}
}
return true;
}
}

View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

View File

@@ -0,0 +1,58 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Loads the Gravity Forms REST API add-on.
*
* Includes the main class, registers it with GFAddOn, and initialises.
*
* @since 2.4-beta-1
*/
class GF_REST_API_Bootstrap {
/**
* Loads the required files.
*
* @since 2.4-beta-1
*
*/
public static function load_rest_api() {
$dir = plugin_dir_path( __FILE__ );
// Requires the class file
require_once $dir . 'class-gf-rest-api.php';
require_once $dir . 'includes/class-results-cache.php';
if ( ! class_exists( 'WP_REST_Controller' ) ) {
require_once $dir . 'includes/controllers/class-wp-rest-controller.php';
}
require_once $dir . 'includes/controllers/class-gf-rest-controller.php';
require_once $dir . 'includes/controllers/class-controller-form-entries.php';
require_once $dir . 'includes/controllers/class-controller-form-results.php';
require_once $dir . 'includes/controllers/class-controller-form-submissions.php';
require_once $dir . 'includes/controllers/class-controller-form-submissions-validation.php';
require_once $dir . 'includes/controllers/class-controller-form-feeds.php';
require_once $dir . 'includes/controllers/class-controller-feeds.php';
require_once $dir . 'includes/controllers/class-controller-entries.php';
require_once $dir . 'includes/controllers/class-controller-entry-notes.php';
require_once $dir . 'includes/controllers/class-controller-notes.php';
require_once $dir . 'includes/controllers/class-controller-entry-notifications.php';
require_once $dir . 'includes/controllers/class-controller-entry-properties.php';
require_once $dir . 'includes/controllers/class-controller-forms.php';
require_once $dir . 'includes/controllers/class-controller-form-field-filters.php';
require_once $dir . 'includes/controllers/class-controller-feed-properties.php';
return GF_REST_API::get_instance();
}
}
GF_REST_API_Bootstrap::load_rest_api();

File diff suppressed because it is too large Load Diff