Laravel API can't update Microsoft Dynamic Nav Customer - laravel-5

I have a Laravel API hitting a MS Nav instance to carry out some data shuffling and migration between two systems. I am able to create Customer records in Nav just fine, but am running into some issues with updating.
I am able to perform a single PATCH request to update a customer record by any subsequent requests return the following error message.
{
"odata.error":{
"code":"",
"message":{
"lang":"en-US",
"value":"Another user has already changed the record."
}
}
}
Here's what my PHP code looks like if that makes a difference.
/**
* #param string $navNo
* #param string $eTag
* #param array $data
* #return array
*/
public function updateCustomer($navNo = '', $eTag = '', $data = []) {
$url = $this->config['uri'] . ':' . $this->config['port'] . '/' . $this->config['server'] . '/' . $this->config['service'] . '/CustomerCardPage';
$url .= "('$navNo')" . '?$format=json&company=' . $this->config['company'];
$options = [
'auth' => $this->config['auth'],
'headers' => [
'Content-Type' => 'application/json',
'If-Match' => 'W/"\'' . $eTag . '\'"',
],
'json' => $data,
];
return $this->makeRequest('PATCH', $url, $options);
}
/**
* #param $method
* #param $url
* #param $options
* #return array
*/
private function makeRequest($method, $url , $options) {
$response = ['success' => true, 'data' => null, 'error' => null];
try {
$res = $this->client->request($method, $url, $options);
$body = json_decode($res->getBody(), true);
$response['data'] = $body;
} catch (BadResponseException $e) {
$res = $e->getResponse()->getBody()->getContents();
$response['success'] = false;
$response['error'] = $res;
}
return $response;
}
I haven't been able to dig up anything helpful in the Nav support forums. Has anyone else run into this type of issue with Laravel/PHP or any other back-end language/framework?
Disclaimer: I have absolutely 0 experience with MS Dynamic Nav, nor do I have direct access to the Nav dashboard or whatever you would call it.
Here are the versions of the relevant framework/packages/services I am working with:
Laravel: 5.6
Guzzle: 6.3
Nginx: 1.13.6
Nav: ...? Can bug someone to find out if this would help.

Figured it out. The ETag is updated each time the customer updates since it acts as a version control. The error was basically telling me that I can't update the version because it was already updated previously.
To fix, just make sure to update the customer ETag after updating.

Related

Laravel count unique database value

I need to perform counting functionality:
The User can download only 5 files (total) per 24 hours. For example: User has 5 available downloads today, User downloaded myFile1.txt file and now he has 4 available downloads today, but if he wants to download myFile1.txt file again, he can do it as many times as he wants (still 4 available downloads), but if he downloaded new file myFile2.txt, now he has 3 available downloads.
My question, how to track, how many times a User downloaded a file.
This is my code:
/*
* $id {string} = file name;
* $dayStart (Timestamp) = example: 2020-03-25 00:00:00;
* $dayEnd (Timestamp) = example: 2020-03-26 00:00:00;
* $total (int) = how much User has downloaded;
* $response = file download functionality.
* auth()->user()->dailyDownloads (int) = default value 5.
*/
public function downloadFile(Request $request, $id){
$file = storage_path('app/files/') . $id .'.bin';
// Tracking operation date, find all records in this date interval.
$dayStart = Carbon::today();
$dayEnd = Carbon::tomorrow();
$total = DumpDownloadHistory::distinct('dataset')->where('user_id', auth()->user()->id)->
whereBetween('created_at', [$dayStart, $dayEnd])->count();
$findIfDownloaded = DumpDownloadHistory::distinct('dataset')->where('user_id', auth()->user()->id)->where('dataset', $id)->count();
if ($total >= auth()->user()->dailyDownloads) {
dd('To many downloads');
}
if (file_exists($file)) {
$headers = [
'Content-Type' => 'application/bin'
];
$response = response()->download($file, $id.'.bin', $headers, 'inline');
if ($response) {
// Storing download history to Database
auth()->user()->downloadHistorys()->create([
'user_id' => auth()->user()->id,
'dataset' => $id,
'user_ip'=> request()->ip()
]);
return $response;
}
} else {
abort(404, 'File not found!');
}
}

update data to database laragon using api

I want to update data to the database in ionic. how to make the data update. Here what I tried. I try using postmen to post the api and it appear success but the data does not change.
in api.php
public function update (Request $request)
{
$id = $request->id;
$medname = $request->medname;
$price = $request->price;
$stock = $request->stock;
$medno = $request->medno;
$ingredient = $request->ingredient;
$description = $request->description;
$addinfo = $request->addinfo;
AddMedicine:: where('medname',$medname)->update([
'id' =>$id,
'medname'=>$medname,
'price'=>$price,
'stock'=>$stock,
'medno'=>$medno,
'ingredient'=>$ingredient,
'description'=>$description,
'addinfo'=>$addinfo,
]);
$msg = "Data Updated";
$datamsg = response()->json([
'success' => $msg
]);
return $datamsg->content();
}
route
Route::put('/update','ApiController#update');
Are you sure use PUT request ? Because need to CSRF token please inspect
https://stackoverflow.com/questions/30756682/laravel-x-csrf-token-mismatch-with-postman

How do i redirect to an external url with headers?

How i have an application which is sitting on A server and i would like to allow user to get to another application which is in B server with a header of the user information. I have done some tries but i m not getting the header in B server. How can i achieve that ya?
Bellow are the codes which i have tried:-
return redirect()->away($apiUrl)->header('x-api-token', $token);
and
$client = new Client();
$request = $client->request('get', $apiUrl, [
'headers' => [
'x-api-user-token' => $userToken
]
]);
Is there a way for me to redirect to an external url with a header?
You might want to try the helper method provided by Laravel and it works like a charm for me.
return redirect('http://external.url/', 302, [
'custom-header' => 'custom value'
])
If you want to look at the source code please refer
/vendor/laravel/framework/src/Illuminate/Foundation/Helpers.php
/**
* Get an instance of the redirector.
*
* #param string|null $to
* #param int $status
* #param array $headers
* #param bool $secure
* #return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
*/
function redirect($to = null, $status = 302, $headers = [], $secure = null)
{
if (is_null($to)) {
return app('redirect');
}
return app('redirect')->to($to, $status, $headers, $secure);
}

Can't get POST data with AJAX

I would like to show 4 objects in a page, with a "load more" button which show 4 more object each time we click.
I try to adapt a PHP script which works(tested) to Symfony.
The problem is that I can't get the POST data (a page number) in my Symfony function, even if I can see it in the chrome developer toolbar...
My controller:
<?php
/**
* Content controller.
*
* #Route("content")
*/
class ContentController extends Controller
{
/**
* Lists all software.
*
* #Route("/software", name="content_software")
* #Method({"POST", "GET"})
*/
public function softwareAction(Request $request)
{
if ($request->request->get('page')){
$page_number = filter_var($_POST["page"], FILTER_SANITIZE_NUMBER_INT,
FILTER_FLAG_STRIP_HIGH);
$item_per_page = 4;
$position = (($page_number-1) * $item_per_page);
$contents = $this->getRepo()->findBy(array(),null,$item_per_page,$position);
}
else
{
$contents = "didn't work";
}
return $this->render('content/index.html.twig', array(
'contents' => $contents
));
}
}
index.html.twig :
{% extends 'loicCoreBundle::Default/layout.html.twig' %}
{% block body %}
{{ dump(contents) }}
<script type="text/javascript">
var track_page = 1; //track user click as page number, right now page number is 1
load_contents(track_page); //load content
$("#load_more_button").click(function (e) { //user clicks on button
track_page++; //page number increment everytime user clicks load button
load_contents(track_page); //load content
});
//Ajax load function
function load_contents(track_page){
$.post( "{{ path('content_software') }}", {'page': track_page}, function(data){
if(data.trim().length == 0){
//display text and disable load button if nothing to load
$("#load_more_button").text("No more records!").prop("disabled", true);
}
});
}
</script>
{% endblock %}
I'm not sure where your errors are coming from, I've done a similar test which just works (no problems with $request->request->get()).
You are however loading a full template (index.html) for each sub request as well. Usually you want to separate calls like this into api like methods, however for simple things its a bit silly.
Here is an expanded/ updated version of what I tested, I avoided post data all together and just used the method as switch. This worked fine so try and figure out where you went wrong using this as reflection (or whatever you like to do with it). Note this uses a simple PHP range array for test data, not entities but it should remain the same principle.
Controller
/**
* #Route(
* "software/{page}/{limit}",
* name="content_software",
* requirements = {
* "page": "[1-9]\d*",
* "limit": "[1-9]\d*"
* }
* )
*/
public function softwareAction(Request $request, $page = 1, $limit = 4) {
if ($request->isMethod('POST')) {
// replace these two lines with database logic (SELECT & LIMIT)
$start = ($page - 1) * $limit;
$items = array_slice(range(1, 10), $start, $limit); // Just using a simple item array [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as test data
$content = '';
foreach ($items as $item) {
$content .= $this->get('twig')->render('someItemTemplate.html.twig', ['item' => $item]);
}
return new Response($content);
} else {
// alternatively you can send out the default items here for the first get request
// and use the same item template above with {% embed %} to render them in this template
return $this->render('someTemplate.html.twig');
}
}
someTemplate.html.twig
<html>
<head>
<title>Cake or death?</title>
</head>
<body>
<ul id="put-the-things-here"></ul>
<button id="next_please">Ehh cake please!</button>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script>
var pageTracker = 1,
$next = $("#next_please"),
$target = $('#put-the-things-here');
load_contents(pageTracker);
$next.click(function (e) {
load_contents(++pageTracker);
});
function load_contents(page) {
// using post just for the request method (page is in the url)
$.post("{{ path('content_software') }}/" + page,
function (data) {
if (data) {
$target.append(data);
} else {
$target.append('<li>We are out of cake.</li>');
$next.attr('disabled', true);
}
}
);
}
</script>
</body>
</html>
someItemTemplate.twig
<li>Cake: {{ item }}</li>
try this:
$request->request->get('page')
Instead of this:
$request->request->has('page')
If it doesn't work try to:
var_dump($request->request->all());
And check the variables
I think too the problem comes from routes.
Error message when I use only "POST" method :
"No route found for "GET /content/software": Method Not Allowed (Allow: POST, DELETE)".
Because when I arrive on my page by clicking a link , I arrive with a GET method.
However, when I click on my "load more" button the AJAX works and I can see in my symfony debugbar that I get POST variable:
The problem is when I first arrive on my page I think.
#Jenne van der Meer: Thank you but I would like not to use GET parameters.
I add my full controller code, in case of:
/**
* Content controller.
*
* #Route("content")
*/
class ContentController extends Controller
{
public function getRepo(){
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository('loicContentBundle:Content');
return $repo;
}
// private $theRepo = getDoctrine()->getManager()->getRepository('loicContentBundle:Content');
/**
* Lists all content entities.
*
* #Route("/", name="content_index")
* #Method("GET") */
public function indexAction(Request $request)
{
$contents = $this->getRepo()->findAll();
$this->denyAccessUnlessGranted('ROLE_USER', null, 'Unable to access this page!');
$formFilter = $this->createFormBuilder()
->add('_', EntityType::class,array(
'class' => 'loicFilterBundle:Filter',
'multiple' => true,
'expanded' => true,
'choice_label' => function($value) {
return ($value->getName());
},
))
->add('Appliquer filtres', SubmitType::class)
->getForm();
$formFilter->handleRequest($request);
$data = '';
if ($formFilter->isSubmitted() && $formFilter->isValid()) {
$data = $formFilter->getData();
$data = $data['_']->toArray();
$contents = $this->getRepo()->findAll();
}
else
{$contents = $this->getRepo()->findAll();
}
return $this->render('content/index.html.twig', array(
'contents' => $contents,'formFilter' => $formFilter->createView(),'request' => $request
));
}
public function contentAction($categoryId)
{
$contents= $this->getRepo()->findBy(
array('contentCategorycontentCategory' => $categoryId),
null,
4
);
return $contents;
}
/**
* Lists all software.
*
* #Route("/software", name="content_software")
*/
public function softwareAction(Request $request)
{
var_dump($request->request->all());
$bla = $request->request->get('page');
if ($request->request->get('page')){
$page_number = filter_var($_POST["page"], FILTER_SANITIZE_NUMBER_INT,
FILTER_FLAG_STRIP_HIGH);
$item_per_page = 4;
$position = (($page_number-1) * $item_per_page);
$contents = $this->getRepo()->findBy(array(),null,$item_per_page,$position);
}
else
{
$contents = "didn't work";
}
return $this->render('content/index.html.twig', array(
'contents' => $contents,'bla' => $bla
));
}
/**
* Lists all videos.
*
* #Route("/videos", name="content_videos")
* #Method("GET")
*/
public function videoAction()
{
$contents = $this->contentAction(5);
return $this->render('content/index.html.twig', array(
'contents' => $contents,
));
}
/**
* Lists all testimonies.
*
* #Route("/testimonies", name="content_testimonies")
* #Method("GET")
*/
public function testimoniesAction()
{
$contents = $this->contentAction(8);
return $this->render('content/index.html.twig', array(
'contents' => $contents,
));
}
/**
* Lists all whiteBooks.
*
* #Route("/whiteBooks", name="content_whiteBooks")
* #Method("GET")
*/
public function whiteBooksAction()
{
$contents = $this->contentAction(3);
return $this->render('content/index.html.twig', array(
'contents' => $contents,
));
}
/**
* Lists all actuality.
*
* #Route("/actuality", name="content_actuality")
* #Method("GET")
*/
public function actualityAction()
{
$contents = $this->contentAction(6);
return $this->render('content/index.html.twig', array(
'contents' => $contents,
));
}
/**
* Lists all webinar.
*
* #Route("/webinar", name="content_webinar")
* #Method("GET")
*/
public function webinarAction()
{
$contents = $this->contentAction(4);
return $this->render('content/index.html.twig', array(
'contents' => $contents,
));
}
/**
* Lists all blog posts.
*
* #Route("/blog", name="content_blog")
* #Method("GET")
*/
public function blogAction()
{
$contents = $this->contentAction(7);
return $this->render('content/index.html.twig', array(
'contents' => $contents,
));
}
/**
* Creates a new content entity.
*
* #Route("/new", name="content_new")
* #Method({"GET", "POST"})
*/
public function newAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$content = new Content();
$form = $this->createForm('loic\ContentBundle\Form\ContentType', $content);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($content);
$em->flush($content);
return $this->redirectToRoute('content_show', array('id' => $content->getIdcontent()));
}
return $this->render('content/new.html.twig', array(
'content' => $content,
'form' => $form->createView(),
));
}
/**
* Displays a form to edit an existing content entity.
*
* #Route("/{id}/edit", name="content_edit")
* #Method({"GET", "POST"})
*/
public function editAction(Request $request, Content $content)
{
$deleteForm = $this->createDeleteForm($content);
$editForm = $this->createForm('loic\ContentBundle\Form\ContentType', $content);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('content_edit', array('id' => $content->getIdcontent()));
}
return $this->render('content/edit.html.twig', array(
'content' => $content,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
/**
* Deletes a content entity.
*
* #Route("/{id}", name="content_delete")
* #Method("DELETE")
*/
public function deleteAction(Request $request, Content $content)
{
$form = $this->createDeleteForm($content);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->remove($content);
$em->flush();
}
return $this->redirectToRoute('content_index');
}
/**
* Creates a form to delete a content entity.
*
* #param Content $content The content entity
*
* #return \Symfony\Component\Form\Form The form
*/
private function createDeleteForm(Content $content)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('content_delete', array('id' => $content->getIdcontent())))
->setMethod('DELETE')
->getForm()
;
}
}
My Bundle/Resources/config/routing.yml file is empty.

Joomla 1.7 Authentication from external app

My aim is to check that a Joomla username and password is valid from my external application. It is not necessary that the user is logged into the system, just that their account exists.
I decided to create my own authentication plugin based on the Joomla Authentication (JOOMLA_PATH/plugins/authentication/joomla). I only changed the name:
<?php
/**
* #version $Id: joomla.php 21097 2011-04-07 15:38:03Z dextercowley $
* #copyright Copyright (C) 2005 - 2011 Open Source Matters, Inc. All rights reserved.
* #license GNU General Public License version 2 or later; see LICENSE.txt
*/
// No direct access
defined('_JEXEC') or die;
jimport('joomla.plugin.plugin');
/**
* Joomla Authentication plugin
*
* #package Joomla.Plugin
* #subpackage Authentication.Webservice
* #since 1.5
*/
class plgAuthenticationWebservice extends JPlugin
{
/**
* This method should handle any authentication and report back to the subject
*
* #access public
* #param array Array holding the user credentials
* #param array Array of extra options
* #param object Authentication response object
* #return boolean
* #since 1.5
*/
function onUserAuthenticate($credentials, $options, &$response)
{
jimport('joomla.user.helper');
$response->type = 'Webservice';
// Joomla does not like blank passwords
if (empty($credentials['password'])) {
$response->status = JAUTHENTICATE_STATUS_FAILURE;
$response->error_message = JText::_('JGLOBAL_AUTH_EMPTY_PASS_NOT_ALLOWED');
return false;
}
// Initialise variables.
$conditions = '';
// Get a database object
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->select('id, password');
$query->from('#__users');
$query->where('username=' . $db->Quote($credentials['username']));
$db->setQuery($query);
$result = $db->loadObject();
if ($result) {
$parts = explode(':', $result->password);
$crypt = $parts[0];
$salt = #$parts[1];
$testcrypt = JUserHelper::getCryptedPassword($credentials['password'], $salt);
if ($crypt == $testcrypt) {
$user = JUser::getInstance($result->id); // Bring this in line with the rest of the system
$response->email = $user->email;
$response->fullname = $user->name;
if (JFactory::getApplication()->isAdmin()) {
$response->language = $user->getParam('admin_language');
}
else {
$response->language = $user->getParam('language');
}
$response->status = JAUTHENTICATE_STATUS_SUCCESS;
$response->error_message = '';
} else {
$response->status = JAUTHENTICATE_STATUS_FAILURE;
$response->error_message = JText::_('JGLOBAL_AUTH_INVALID_PASS');
}
} else {
$response->status = JAUTHENTICATE_STATUS_FAILURE;
$response->error_message = JText::_('JGLOBAL_AUTH_NO_USER');
}
}
}
I added one more file to my plugin to access the authentication, I called it test_auth.php and it goes like this:
<?php
define('_JEXEC', 1 );
define('JPATH_BASE', 'C:\xampp\htdocs\joomla');
define( 'DS', DIRECTORY_SEPARATOR );
require_once ( JPATH_BASE .DS.'includes'.DS.'defines.php' );
require_once ( JPATH_BASE .DS.'includes'.DS.'framework.php' );
include("Webservice.php");
$credentials = array(
'username' => 'test',
'password' => 'test');
$options = array();
$response = array();
$auth = new plgAuthenticationWebservice();
$auth->onUserAuthenticate($credentials, $options, &$response);
var_dump($response);
But when I call it, it get these errors:
Warning: Missing argument 1 for JPlugin::__construct(), called in
C:\xampp\htdocs\joomla\plugins\authentication\Webservice\test_auth.php
on line 25 and defined in
C:\xampp\htdocs\joomla\libraries\joomla\plugin\plugin.php on line 57
Fatal error: Call to a member function attach() on a non-object in
C:\xampp\htdocs\joomla\libraries\joomla\base\observer.php on line 41
What am I doing wrong?
I think I could place all php scripts outside and independent from joomla and work with require_once(JPATH_BASE .DS.'includes'.DS.'defines.php') etc.
Or I could write a plugin, install it with the extension manager and won't struggle with an unavailable joomla framework. But in fact it won't work if I leave out defines.php and framework.php.
I think a guide for plugin creation in Joomla 1.7 would be helpful.
OK, i completely dropped my first try.
Instead I use JOOMLA_ROOT/libraries/joomla/user/authentication.php now (insprired by JOOMLA_ROOT/libraries/joomla/application/application.php).
My test_auth.php looks like this now:
<?php
define('_JEXEC', 1 );
define('DS', DIRECTORY_SEPARATOR);
define('JPATH_BASE', dirname(__FILE__) . DS . '..' . DS . '..' . DS . '..'); // assuming we are in the authorisation plugin folder and need to go up 3 steps to get to the Joomla root
require_once (JPATH_BASE .DS. 'includes' .DS. 'defines.php');
require_once (JPATH_BASE .DS. 'includes' .DS. 'framework.php');
require_once (JPATH_BASE .DS. 'libraries' .DS. 'joomla'. DS. 'user' .DS. 'authentication.php');
$mainframe =& JFactory::getApplication('site');
$mainframe->initialise();
$credentials = array(
'username' => 'test',
'password' => 'test');
$options = array();
$authenticate = JAuthentication::getInstance();
$response = $authenticate->authenticate($credentials, $options);
if ($response->status === JAUTHENTICATE_STATUS_SUCCESS) {
echo('<br />It works<br />');
}
var_dump($response);
For any improvements I would be deeply grateful!
EDIT: I dismissed the plugin installation. It is a simple external script, which wouldn't be called from Joomla itself. I simply moved it to a new folder in the Joomla root.

Resources