SugarCRM: How to get json data in REST endpoint - php

I have added an extra action to the recordlist view;
custom/modules/Opportunities/clients/base/views/recordlist/recordlist.js:
({
extendsFrom: 'RecordlistView',
initialize: function(options) {
this._super("initialize", [options]);
//add listener for custom button
this.context.on('list:opportunitiesexport2:fire', this.export2, this);
},
export2: function() {
//gets an array of ids of all selected opportunities
var selected = this.context.get("mass_collection").pluck('id');
if (selected) {
return App.api.call('read',
App.api.buildURL('Opportunities/Export2'),
{'selected_ids':selected},
{
success: function(response) {
console.log("SUCCESS");
console.log(response);
},
error: function(response) {
console.log('ERROR');
console.log(response);
},
complete: function(response){
console.log("COMPLETE");
console.log(response);
},
error: function(response){
console.log("ERROR");
console.log(response);
}
});
}
},
})
The tutorial here
http://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_7.7/Integration/Web_Services/v10/Extending_Endpoints/
Explains how to create an endpoint.
However it doesn't explain how to get the json data (the stringified array of selected ids);
custom/modules/Opportunities/clients/base/api/OpportunitiesApi.php:
class OpportunitiesApi extends SugarApi
{
public function registerApiRest()
{
return array(
//GET
'MyGetEndpoint' => array(
//request type
'reqType' => 'GET',
//set authentication
'noLoginRequired' => false,
//endpoint path
'path' => array('Opportunities', 'Export2'),
//endpoint variables
'pathVars' => array('', ''),
//method to call
'method' => 'Export2',
//short help string to be displayed in the help documentation
'shortHelp' => 'Export',
//long help to be displayed in the help documentation
'longHelp' => 'custom/clients/base/api/help/MyEndPoint_MyGetEndPoint_help.html',
),
);
}
/**
* Method to be used for my MyEndpoint/GetExample endpoint
*/
public function Export2($api, $args)
{
//how to access $args['selected_ids']?
}
}
$args contains
Array
(
[__sugar_url] => v10/Opportunities/Export2
)
Is it possible to access the json data?

I did the same but my rest api was coded in java. I used java #Path annotation to annotate my get method. I then deployed above rest api code to a server(Tomcat in my case). Starting the server and then hitting the URL formed by the #Path will give you the json data on the browser.

The solution was to change the call method to create and the endpoint method to POST; $args now contains
Array
(
[selected_ids] => Array
(
[0] => 0124a524-accc-11e6-96a8-005056897bc3
)
[__sugar_url] => v10/Opportunities/Export2
)
PUT vs POST in REST - I was using GET because I didn't plan to change anything, but the body is typically ignored in a GET request.

Related

Passing function parameter to ajax data return undefined index error

I'm working on a song site, where you can rate songs, singers etc, and you also have the option to add them to your favourites.
After a while I've noticed when I add/remove song/singer from my favourites, I pretty much repeate the same ajax request multiple times, only with a different feedback message, so I decided to create a separate function of that request, and in other methods I just call this.
For this I set things such as the route of the button, feedback, message etc in the parameter of the favourite function, as you can see below.
The feedback() function is not really important I believe, I just shared if someone would like to know how it looks like, or if they'd actually find it important anyway.
function feedback($feedback_route,$message_route,$alert_class,$message){
$($feedback_route).removeClass("d-none");
$($feedback_route).addClass($alert_class);
$($feedback_route).addClass("animated fadeIn");
$($message_route).html($message);
setTimeout(() => {
$($feedback_route).addClass("animated fadeOut");
}, 2000);
setTimeout(() => {
$($feedback_route).addClass("d-none");
$($feedback_route).removeClass($alert_class);
$($message_route).html("");
}, 2500);
}
function favourite($ajax_route,$post,$button_route,$event,$feedback_route,
$message_route,$success_message,$error_message){
$.ajax({
url: $ajax_route,
type: "post",
data: {
$post: $($button_route).attr("value"),
event:$event
},
success: (data) => {
feedback($feedback_route,$message_route,"alert-success",$success_message);
setTimeout(() => {
$($button_route).replaceWith(data);
}, 2500);
},
error: () => {
feedback($feedback_route,$message_route,"alert-danger",$error_message);
}
});
}
//-------------------------------ADD SONG TO FAVOURITES-----------------------------
$("#song-info-container #song-container").on("click","#add-to-favourites", () => {
favourite(
"ajax/song-favourite.php",
"songID","#song-info-container #song-container #add-to-favourites",
"add","#song-info-container #song-container .feedback",
"#song-info-container #song-container #message",
"Added!","Failed to add!"
);
$("#song-info-container #song-container .feedback").removeClass("animated fadeOut");
});
Here's how the original ajax request looks like:
$("#song-info-container #song-container").on("click","#add-to-favourites", () => {
$.ajax({
url: "ajax/song-favourite.php",
type: "post",
data: {
songID: $("#song-info-container #song-container #add-to-favourites").attr("value"),
event:"add"
},
success: (data) => {
feedback("#song-info-container #song-container .feedback",
"#song-info-container #song-container #message","alert-success","Added!");
setTimeout(() => {
$("#song-info-container #song-container #add-to-favourites").replaceWith(data);
}, 2500);
},
error: () => {
feedback("#song-info-container #song-container .feedback",
"#song-info-container #song-container #message","alert-danger","Failed to add!");
}
});
$("#song-info-container #song-container .feedback").removeClass("animated fadeOut");
});
And now the actual problem: first I thought it actually works, because the feedback appears with the correct message, but after it disappears(see in the setTimeOut function), the following message appears:
Notice: Undefined index: songID in G:\x\htdocs\AniSong\ajax\song-favourite.php on line 9
So the data which is also set in the parameter of the favourite function is not being passed to the other file.
Does someone know why this happens?
Or can I not do an ajax request like this in the first place?
PS: Sorry if the title doesn't make sense, didn't really know how it should be asked.
This doesn't do what you're expecting:
{
$post: $($button_route).attr("value"),
event: $event
}
This won't replace $post with the string value you're passing to the function. This gives the property the literal name $post. Check the network tab of your browser's debugging tools to see what you're actually sending to the server. There is no songID property.
A quick test to validate:
let $foo = 'test';
console.log({$foo: 'baz'});
If you want to dynamically send this information, you'll need to modify your data structure to do that. Similar to how you have a dynamic "event", you'll now need a dynamic "post". Something like:
{
post: $post,
value: $($button_route).attr("value"),
event: $event
}
Then in your server-side code you'd change your logic to examine the post parameter to see what field you're updating, or whatever such logic you're using server-side.
Alternatively, it just occurs to me that you could potentially create a dynamic property name. Consider something like this:
let $post = 'songID';
let data = {
event: 'add'
};
data[$post] = 'test value';
console.log(data);
It's up to you which you think is more complex vs. which is easier for you to support.

Can't call custom api in sugarcrm 8.0

Hello im trying to call a custom api through this code in sugarcrm:
({
extendsFrom: 'RowactionField',
defaultFirsName: 'first_name',
defaultLastName: 'last_name',
initialize: function (options) {
this._super('initialize', [options]);
this.def.first_name = _.isEmpty(this.def.first_name) ? this.defaultFirsName : this.def.first_name;
this.def.last_name = _.isEmpty(this.def.last_name) ? this.defaultLastName : this.def.last_name;
},
/** * Rowaction fields have a default event which calls rowActionSelect */
rowActionSelect: function () {
this.upper_name();
},
upper_name: function () {
var first = this.model.get(this.def.first_name);
var last = this.model.get(this.def.last_name);
var fullName = first + last;
if (fullName) {
app.alert.show('name-check-msg', {
level: 'success',
messages: 'Firstname and Lastname filled.',
autoClose: true
});
}
else {
app.alert.show('name-check-msg', {
level: 'error',
messages: 'First name and last name must be filled.',
autoClose: false
});
}
var self = this;
url = app.api.buildURL('Leads', 'UpperName', null, {
record: this.model.get('id')
});
app.api.call('GET', url, {
success: function (data) {
app.alert.show('itsdone', {
level: 'success',
messages: 'Confirmed to uppercase name.',
autoClose: true
});
},
error: function (error) {
app.alert.show('err', {
level: 'error',
title: app.lang.getAppString('ERR_INTERNAL_ERR_MSG'),
messages: err
});
},
});
}
})
the name is "uppernamebutton.js" its functions is, it checks if the firstname and lastname is blank and will show an error message to fill up the fields then calls the api to Uppercase the first letters of the names.
Here's the code for the custom api, i named it "UpperNameApi.php":
<?php
class UpperNameApi extends SugarApi
{
public function registerApiRest()
{
return array(
'UpperNameRequest' => array(
//request type
'reqType' => 'POST',
//endpoint path
'path' => array('Leads', 'UpperName'),
//endpoint variables
'pathVars' => array('module',''),
//method to call
'method' => 'UpperNameMethod',
//short help string to be displayed in the help documentation
'shortHelp' => 'Example endpoint',
//long help to be displayed in the help documentation
'longHelp' => 'custom/clients/base/api/help/MyEndPoint_MyGetEndPoint_help.html',
),
);
}
public function UpperNameMethod($api, $args)
{
if (isset($args['record']) && !empty($args['record'])) {
$bean = BeanFactory::getBean('Leads', $args['record']);
if (!empty($bean->id)) {
$first = $bean->first_name;
$first = ucwords($first);
$bean->first_name = $first;
$last = $bean->last_name;
$last = ucwords($last);
$bean->last_name = $last;
$bean->save();
}
return 'success';
}
return 'failed';
}
}
pls help to those genius coder out there.
From what I can see there are 2 problems with your app.api.call:
You got the first argument wrong: It should never be 'GET', but instead it should be 'read' for GET requests, 'update' for PUT requests, 'delete' for DELETE requests and 'create' for POST requests.As you specified reqType => 'POST' you should be using app.api.call('create', url,
If I'm not mistaken the callbacks go in the forth argument, not the third one (that one is for payload data), so you should add an empty object as third argument and pass the callbacks in the 4th, your resulting line should look like this: app.api.call('create', url, {}, {
EDIT:
Also I noticed that you use $args['record'] in your function.
You are currently using buildURL to pass that value, which means you set it via the query-string of the URL, which probably(?) works for requests other than GET, however usually one of the following 2 ways are used for non-GET calls, e.g. POST:
passing the record id via the endpoint path:
recommended way for single IDs
'path' => array('Leads', '?' 'UpperName'),
'pathVars' => array('module','record',''),
Notes:
path contains a placeholder ? which will be filled by the caller with the record id.
pathVars has record at the same (second) position as the placeholder int he path, which causes that part of the URL to be saved into $args['record'] (similar to first part getting saved into $args['module'], which will always be 'Leads' for this API).
In javascript you will have to adjust the API call URL accordingly:
url = app.api.buildURL('/Leads/' + this.model.get('id') + '/UpperName');
Notice how the ID goes into the second part of the URL (where the placeholder is defined in the API)
passing the record id via the request payload
recommended way for passing multiply IDs at once or for passing other arguments than record ID
Putting the record ID inot the data object of app.api.call, so that it will be written into $args.
app.api.call('create', url, {record: this.model.get('id')}, {

Why isn't my Wordpress custom route returning data out of the database?

The code below is my custom endpoint where I am trying to grab data out of my saic3_LibraryIndex database.
<?php
add_action('rest_api_init', function () {
register_rest_route('libraries/v1', '/all', array(
'methods' => 'GET',
'callback' => 'retrieve_libraries'
) );
} );
function retrieve_libraries( $data ) {
global $saic3_LibraryIndex;
$query = "SELECT * FROM `Library`";
$list = $saic3_LibraryIndex->get_results($query);
return $list;
}
In my javascript I am running an ajax call on page load to attempt to dynamically populate the page based off of the info in the db. For now I am just using this call to make sure that I am getting something back.
<script type="text/javascript">
jQuery(document).ready(function( $ ){
$.ajax({
url: "/wp-json/libraries/v1",
method: 'GET',
success: function(response) {
console.log(response);
},
failure: function(err) {
console.log(err);
}
});
});
</script>
The route is hitting as a success and not giving me a 404 but the response I am getting back has no data in it. The response is just
{namespace: "libraries/v1", routes: {…}, _links: {…}}
namespace
:
"libraries/v1"
routes
:
/libraries/v1
:
endpoints
:
[{…}]
methods
:
["GET"]
namespace
:
"libraries/v1"
_links
:
{self: "{`my-url-.com`/wp-json/libraries/v1"}
__proto__
:
Object
/libraries/v1/all
:
endpoints
:
[{…}]
methods
:
["GET"]
namespace
:
"libraries/v1"
_links
:
{self: "`my-url-.com`/wp-json/libraries/v1/all"}
__proto__
:
Object
__proto__
:
Object
_links
:
up
:
[{…}]
__proto__
:
Object
__proto__
:
Object
I am brand new to WordPress so any help on this would be greatly appreciated. From what I was told the database was named saic3_LibraryIndex which from my thinking would take the place of wpdb. I tried switching it to wpdb and just using this call to grab the local posts but that still gives me the same response in the console. I am almost certain that endpoint is written incorrectly just not sure how to write it correctly.
SOLVED
Since I was trying to connect to another database outside of wpdb I needed to create a new instance of wpdb like so...
add_action('rest_api_init', function () {
register_rest_route('libraries/v1', '/all', array(
'methods' => 'GET',
'callback' => 'retrieve_libraries'
) );
} );
function retrieve_libraries( $data ) {
$second_db = new wpdb(DB_USER, DB_PASSWORD, "saic3_LibraryIndex", DB_HOST);
$query = "SELECT * FROM `Library`";
$list = $second_db->get_results($query);
return $list;
}

dropzone not uploading, 400 bad request, token_not_provided

okay i've been trying this for like 2 hrs now and cant to make this work. dropzone cant upload any file. the server says "token not provided". im using laravel as backend and it uses jwt tokens for authentication and angular as front end. here's my dropzone config.
$scope.dropzoneConfig = {
options: { // passed into the Dropzone constructor
url: 'http://localhost:8000/api/attachments'
paramName: 'file'
},
eventHandlers: {
sending: function (file, xhr, formData) {
formData.append('token', TokenHandler.getToken());
console.log('sending');
},
success: function (file, response) {
console.log(response);
},
error: function(response) {
console.log(response);
}
}
};
and the route definition
Route::group(array('prefix' => 'api', 'middleware' => 'jwt.auth'), function() {
Route::resource('attachments', 'AttachmentController', ['only' => 'store']);
}));
and the controller method
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$file = Input::file('file');
return 'okay'; // just until it works
}
the token is correct and is actually getting to the server (because i tried returning the token using Input::get('token') in another controller function and it works). can someone tell me what im doing wrong? im getting "400 Bad Request" with "token_not_provided" message...
thanks for any help. and i apologize for my bad english..
I'm not sure why appending the token to the form isn't working, but you could try sending it in the authorization header instead.
Replace
formData.append('token', TokenHandler.getToken());
With
xhr.setRequestHeader('Authorization', 'Bearer: ' + TokenHandler.getToken());
make sure you add the token to your call: Example you can add the toke as a parameter in your dropzone url parameter.
//If you are using satellizer you can you this
var token = $auth.getToken();// remember to inject $auth
$scope.dropzoneConfig = {
options: { // passed into the Dropzone constructor
url: 'http://localhost:8000/api/attachments?token=token'
paramName: 'file'
},
eventHandlers: {
sending: function (file, xhr, formData) {
formData.append('token', TokenHandler.getToken());
console.log('sending');
},
success: function (file, response) {
console.log(response);
},
error: function(response) {
console.log(response);
}
}
};

Need help formulating a correct JSON response

I am a novice using Jquery ajax calls and json responses and have hit a bump that I need help to overcome.
I am using cleeng open API and I am wondering about the response from one of the api calls I am using – getRentalOffer().
I am using jquery $ajax() request and I want to make the getRentalOffer() api call and return the result in JSON format. My efforts so far is this (assume a POST request with id as parameter.)
Request:
<script type="text/javascript">
$(document).ready(function() {
$( "#getOfferButton" ).click(function() {
var offerId = document.frm.offerID.value;
$.ajax({
// the URL for the request
url: "ajax-get-offer.php",
// the data to send (will be converted to a query string)
data: {
id: offerId
},
// whether this is a POST or GET request
type: "POST",
// the type of data we expect back
dataType : "json",
// code to run if the request succeeds;
// the response is passed to the function
success: function(json) {
// $("#title").val = json.title;
/*
$.each(json, function(i, item){
$("#"+item.field).val(item.value);
});
*/
console.log(json);
},
// code to run if the request fails; the raw request and
// status codes are passed to the function
error: function( xhr, status, errorThrown ) {
alert( "Sorry, there was a problem!" );
console.log( "Error: " + errorThrown );
console.log( "Status: " + status );
console.dir( xhr );
},
// code to run regardless of success or failure
complete: function( xhr, status ) {
alert( "The request is complete!" );
}
});
});
});
</script>
ajax-get-offer.php:
<?php
include_once('Cleeng-cleeng-php-sdk-fe2a543/cleeng_api.php');
/*
Using FirePHP to log variables
*/
require_once('FirePHPCore/FirePHP.class.php');
ob_start();
$firephp = FirePHP::getInstance(true);
$offerID = $_POST['id'];
$firephp->log($offerID, 'offerID');//For debugging
$publisherToken = 'My super secret token goes here!';
$cleengApi = new Cleeng_Api();
$cleengApi->setPublisherToken($publisherToken);
$offerDetails = $cleengApi->getRentalOffer($offerID);
$firephp->log($offerDetails, 'offerDetails');//For debugging
echo $offerDetails;
?>
When I try this I get Internal server error. I tried to use echo json_encode($offerDetails); on that last echo statement and then I do not get the server error. However the response only seem to contain the last element of the JSON object.
I need help to understand what I need to do with the API response from getRentalOffer() in order to pass it as a proper JSON response to the $ajax() request.
I hope my question make sense. :-)
Edit: Using print_r insead of echo I do get a response text but sadly with an error. This is the text and it looks to me as if it need to be formatted correctly before using print_r.
"Cleeng_Entity_RentalOffer Object ( [id:protected] => R875937249_SE [publisherEmail:protected] => martin.xxxxxxxx#xxxxxxx.se [url:protected] => http://xxx.xxxxxx.xx/cleeng_tool [title:protected] => Tjohooo! [description:protected] => En skön rulle om Afrika. [price:protected] => 55 [applicableTaxRate:protected] => 0.21 [period:protected] => 48 [currency:protected] => EUR [socialCommissionRate:protected] => 0 [contentType:protected] => video [contentExternalId:protected] => xxxxxxxxxxx [contentExternalData:protected] => {"platform":"vimeo","dimWidth":"500","dimHeight":"369","hasPreview":false,"previewVideoId":"","backgroundImage":"https://i.vimeocdn.com/video/xxxxxxxxx_960.jpg"} [contentAgeRestriction:protected] => [tags:protected] => Array ( [0] => abo ) [active:protected] => 1 [createdAt:protected] => 1400588711 [updatedAt:protected] => 1400606512 [pending:protected] => [averageRating] => 4 ) "
You cannot echo arrays or object, try using
print_r($offerDetails);
or
var_dump($offerDetails);
Solved.
The object returned by getRentalOffer() contains protected members which will not be encoded by json_encode because it respects the access parameters in the object vars. I found a nice solution in this post: http://smorgasbork.com/component/content/article/34-web/65-json-encoding-private-class-members
It is not a robust solution as it relies on a loophole that one day might be shut so beware of that. But for my needs it will suffice.

Categories