Showing posts with label laravel. Show all posts
Showing posts with label laravel. Show all posts

Tuesday, October 16, 2018

How to connect two random people in a single thread using Laravel Broadcasting

Leave a Comment

I'm creating a random real-time chat, like Omegle.

I'm having trouble to connect two random people in a private thread using a wait list. What would be the best way to do it using Laravel Broadcasting and Laravel Job?

For example:

Route::get('/start', function () {     // add me to the wait list     // wait for another person     // find another person     // remove me and another person from the wait list      // dispatch event     App\Events\AnotherPersonFound::dispatch($anotherPerson, $threadId); }); 

0 Answers

Read More

Call to undefined function Illuminate\Filesystem\finfo_file()

Leave a Comment

I have the following error showing up in my laravel.log file on a website I have running. How can I pin down where the error originates from? As the stack trace is so short I am unsure where to start.

[2017-07-03 16:05:13] production.ERROR: exception 'Symfony\Component\Debug\Exception\FatalErrorException' with message 'Call to undefined function Illuminate\Filesystem\finfo_file()' in /home/uksacbor/laravel-projects/attestation/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php:254 Stack trace: #0 {main}

I've ran a search on the site's folder using sublime's global search for when finfo_file() is used and I've used it in a helper in a test...

private function prepareFileUpload($path, $name) {     TestCase::assertFileExists($path);      $pathInfo = pathinfo($path);      $copyPath = $pathInfo['dirname'] . $pathInfo['filename'] . '_copy' . $pathInfo['extension'];      \File::copy($path, $copyPath);      $finfo = finfo_open(FILEINFO_MIME_TYPE);      $mime = finfo_file($finfo, $copyPath);      return new \Illuminate\Http\UploadedFile($copyPath, $name, $mime, filesize($copyPath), null, true); } 

Currently, my tests are all passing.

Any ideas?

5 Answers

Answers 1

As Michael Hamisi said, it's a method that is declared by a PECL PHP Extension named fileinfo that is commonly present on PHP installations.

it is used in Laravel to get information about files especially in upload cases.

you should check that the extension is enabled on your installations. usually, when you do composer install, an error will be triggered telling you to activated the missing extension.

/**  * (PHP >= 5.3.0, PECL fileinfo >= 0.1.0)  * Return information about a file  * @link http://php.net/manual/en/function.finfo-file.php 

Answers 2

When you are managing the server yourself you should run

sudo pecl install fileinfo 

from the commandline and edit php.ini (probably located at /etc/php.ini)

to contain the line

extension=fileinfo.so

don't forget to restart the web server. Depending on your os and web stack this is something like

  • service apache restart
  • service httd restart
  • service nginx restart

When using a shared hosting, you probably have an option in the webinterface to enable it from there. For example in directadmin

Advanced features > Select PHP version

enter image description here

And then

Tick the checkbox next to fileinfo

enter image description here

Don't forget to click save

Answers 3

You are supposed to activate finfo_file()

[check this link]

Answers 4

As others have already pointed out how to fix the error itself I'm going to answer your question about how you can find out where the error originates from.

The error message tells us that you are not calling finfo_file() directly but that you are calling a method of Illuminate/Filesystem/Filesystem which uses it at line 254. So you need to search for where you are using this method from Illuminate/Filesystem/Filesystem.

If you are not using this method directly you could be using it indirectly through a dependency of yours. In this case you would need to search in your vendor directory for the usage of the method from Illuminate/Filesystem/Filesystem.

Answers 5

You need to enable fileinfo extension.

please refer to this : https://stackoverflow.com/a/24565508/7171624

Read More

Friday, September 14, 2018

Laravel shared cookie detection issue in domain and subdomain

Leave a Comment

I am working on Laravel 5.4.30.

Imagine that we have a domain example.com and a subdomain of dev.example.com. The main domain is for master branch and the dev subdomain is for develop branch. We have cookie notice system that will be hidden after clicking on Hide Cookie Notice button. This works by setting a cookie forever. We have set the SESSION_DOMAIN configs to each domain for each environment.

For main domain:

SESSION_DOMAIN=example.com 

For dev subdomain:

SESSION_DOMAIN=dev.example.com 

Now the issue comes from here. If we go to the example.com and click on hiding the cookie notice, a cookie will be set forever for main domain. After that we go to the dev.example.com and do the same. So a cookie will be set for subdomain as well. But this cookie has been set after previous one. (The order is important) Now if we refresh the subdomain, we will see that notice again! (not hidden) The browser has read the main cookie because of .example.com set in domain parameter of cookie in the browser, so every subdomain will be affected. But the view still shows the notice because it cannot read any cookie for hiding.

Anyway I don't want to share that cookie across all subdomains. How can I achieve that? I think I should add a prefix for cookie name. But I don't know how to do it, that laravel automatically adds prefix to cookie name.

Any solutions?

3 Answers

Answers 1

You need to implement your own "retrieving" and "setting" a cookie.

Retrieving (has, get) cookies

Create yourself new class (anywhere you like, but I would do app/Foundation/Facades/) with name Cookie.

use \Illuminate\Support\Facades\Cookie as CookieStock;  class Cookie extends CookieStock {      //implement your own has(...);     public static function has($key)     {         return ! is_null(static::$app['request']->cookie(PREFIX . $key, null)); //get the prefix from .env file for your case APP_ENV     }      //implement your own get(...);     public static function get($key = null, $default = null) {...} } 

Now open up config/app.php and change corresponding alias (cookie).

Setting (make) cookies

Create yourself new provider (use artisan), and copy-paste code from Illuminate\Cookie\CookieServiceProvider.php and change namespaces. Again open up config/app.php and change corresponding service provider with the new one.

Create yourself new class (anywhere you like, but I would do app/Foundation/Cookie/) with name CookieJar.

use \Illuminate\Cookie\CookieJar as CookieJarStock;  class CookieJar extends CookieJarStock {  //Override any method you think is relevant (my guess is make(), I am not sure at the moment about queue related methods)      public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = true)     {         // check before applying the PREFIX         if (!empty($name)) {             $name = PREFIX . $name; // get the PREFIX same way as before         }          return parent::make($name, $value, $minutes, $path, $domain, $secure, $httpOnly);     } } 

Update the code in your own cookie service provider to use your implementation of CookieJar (line 19).

Run $ composer dump-autoload, and you should be done.

Update

Since BorisD.Teoharov brought up, that if framework changes signature of CookieJarStocks make() (or any other cookie related function) in between the major versions, I made a example repository, that includes a test that can be used as is and it will fail if signature change happens.

It is as simple as this:

public function test_custom_cookie_jar_can_be_resolved() {     resolve(\App\Foundation\Cookie\CookieJar::class);     $this->assertTrue(true); } 

Detailed how to can be inspected in the corresponding commit diff.

Answers 2

I've setup test environments to make sure, I'm not missing any details.

As in my former answer, I thought invalidating cookies will be sufficient for that case, but as @BorisD suggested it is not, and I've confirmed that on my tests.

So there are a few important notes, coming from my experiences...

  1. Don't mix Laravel versions in subdomains - If using SESSION_DOMAIN you need to make sure your Laravel version matches (between root and subdomains), cause I've experimented with 5.4 under example.com domain and 5.6 under dev.example.com. This showed me some inconsistency in dealing with Cookies, so some important changes have been done, between these versions, and you can be sure it will not work correctly if you mix versions. I finally ended up with Laravel 5.6 on both domains, so I'm not 100% sure if that works on Laravel 5.4, but I think it should.
  2. Make sure all your subdomains use the same APP_KEY - otherwise, Laravel will be unable to decrypt the Cookie, returning null value, cause all encryption/decryption in Laravel uses this app key...
  3. SESSION_DOMAIN. In SESSION_DOMAIN I've pointed the same root domain like example.com for both domains. With this setting, I can create a cookie on root domain, and retrieve it correctly on both domains. After that setting, creating a cookie on subdomain forces root domain to receive new value from subdomains cookie also, and they are overridden. So I guess everything works here as requested in the original question.
  4. Cookie make parameters - In case you want to use a subdomain in SESSION_DOMAIN, you can safely do that also. However, you need to make sure, important let's call them global cookies are defined in a bit different way. Cookie make syntax:

    Cookie make(string $name, string $value, int $minutes, string $path = null, string $domain = null, bool $secure = false, bool $httpOnly = true)

    So what's important here, you need to put your root domain for this particular cookie on creation like this for example: return response($content)->cookie('name','value',10,null,'example.com')

Conclusions:

  1. With this config, you should be able to access your Cookies properly under subdomains and your root domain.
  2. You may probably need to update your Laravel installations to 5.6, which will force you to upgrade to PHP 7.1 at least (there were some changes to cookies in php also)
  3. And finally, in your code, don't rely on Cookie existence, but on its values only (I don't know if that's in your case).

Answers 3

You could set a prefix for the cookie name depending on the environment.

First, add COOKIE_PREFIX to your env file.

COOKIE_PREFIX=dev 

Then, use it when setting your cookie

$cookie = cookie(env('COOKIE_PREFIX', 'prod') . '_name', 'value', $minutes); 

Then, retrieve it like so

$value = $request->cookie(env('COOKIE_PREFIX', 'prod') . '_name'); 
Read More

Tuesday, September 11, 2018

Collecting multiple data and send by ajax in laravel

Leave a Comment

I'm using ajax to send my data to controller and save it in database, before my code was working then I needed to sort my data when they append in blade after sorting them it stop working by %50.

Good to know

Here is my old code and solution of sorting my data (which caused this issue that i have now)

Logic

  1. I select set
  2. Set childs will append in blade (sorted by custom column)
  3. I choose single or multiple options and hit save button
  4. Data saves to database

More to know

My appended data (based on selected set) are include 2 types of data

  1. Custom inputs (text field & text-area field) which i can manually fill and save (still working with no issue)
  2. Dynamic select option which returns from database and i can select and save their id's (this is the issue dynamics)

Code

Script of appending data

<script defer> $(document).ready(function() {     $('select[name="selectset"]').on('change', function() {         var id = $(this).val();         if(id) {             $.ajax({                 url: '{{ url('admin/selectset') }}/'+encodeURI(id),                 type: "GET",                 dataType: "json",                 success:function(result) {                     $('div#dataaamsg').empty();                     $('div#dataaamsg').append('Use <kbd>CTRL</kbd> or <kbd>SHIFT</kbd> button to select multiple options');                     result.sort(function(a,b) {                         return (a.position > b.position) ? 1 : ((b.position > a.position) ? -1 : 0);                     });                      $.each(result, function(key1, value1) {                          var vvvid = value1.id;                          if(value1['type'] == 'textfield'){                             var my_row = $('<div class="row mt-20 ccin">');                             $('div#dataaa').append(my_row);                         }else if(value1['type'] == 'textareafield'){                             var my_row = $('<div class="row mt-20 ccin">');                             $('div#dataaa').append(my_row);                         }else{                             var my_row = $('<div class="row mt-20">');                             $('div#dataaa').append(my_row);                         }                          // second data                         $.ajax({                             url: '{{ url('admin/findsubspecification') }}/'+value1['id'],                             type: "GET",                             dataType: "json",                             success:function(data) {                                 // Check result isnt empty                                 var helpers = '';                                 $.each(data, function(key, value) {                                     helpers += '<option value="'+value.id+'">'+value.title+'</option>';                                 });                                  if(value1['type'] == 'textfield'){                                     var my_html = '{{ Form::open() }}<input name="product_id" id="product_id" type="hidden" value="{{$product->id}}"><input name="specification_id" id="specification_id" type="hidden" value="'+vvvid+'"><div class="col-md-4">'+value1.title+'</div>';                                     my_html += '<div class="col-md-6"><input id="text_dec" name="text_dec[]" placeholder="text field" class="text_dec form-control"></div>';                                     my_html += '<div class="col-md-2"><button type="button" id="custmodalsavee" class="custmodalsavee btn btn-xs btn-success">Save</button>{{Form::close()}}</div>';                                     my_row.html(my_html);                                 }else if(value1['type'] == 'textareafield'){                                     var my_html = '{{ Form::open() }}<input name="product_id" id="product_id" type="hidden" value="{{$product->id}}"><input name="specification_id" id="specification_id" type="hidden" value="'+vvvid+'"><div class="col-md-4">'+value1.title+'</div>';                                     my_html += '<div class="col-md-6"><textarea id="longtext_dec" name="longtext_dec[]" placeholder="text area field" class="longtext_dec form-control"></textarea></div>';                                     my_html += '<div class="col-md-2"><button type="button" id="custmodalsavee" class="custmodalsavee btn btn-xs btn-success">Save</button>{{Form::close()}}</div>';                                     my_row.html(my_html);                                 }else{                                     var my_html = '{{ Form::open() }}<input name="product_id" id="product_id" type="hidden" value="{{$product->id}}"><div class="col-md-4">'+value1.title+'</div>';                                     my_html += '<div class="col-md-6"><select class="subspecifications form-control tagsselector" id="subspecifications" name="subspecifications[]" multiple="multiple">'+helpers+'</select></div>';                                     my_html += '<div class="col-md-2"><button type="button" id="savedynspecto" class="savedynspecto btn btn-xs btn-success">Save</button>{{Form::close()}}</div>';                                     my_row.html(my_html);                                 }                               }                         });                         // second data                      });                 }             });         }else{             $('div#dataaa').empty();         }     }); }); </script> 

script of saving data (issue part)

<script defer>   $(document).ready(function() {    $("body").on("click", ".savedynspecto", function(e){       var form = $(this).closest('form');       var id = form.find('input[name="product_id"]').val();       // e.preventDefault();       $.ajax({         type: "post",         url: '{{ url('admin/spacssendto') }}',         data: {           '_token': $('input[name=_token]').val(),           'product_id': id,           'subspecifications': $(this).closest('form').find('select.subspecifications').val()         },         success: function (data) {           alert('Specifications added successfully.');           console.log($(this));         },         error: function (data) {           console.log(data);         }       });     });   }); </script> 

Issue

  1. When I try to save my dynamic values i cannot get id of selected option/options

    //returned data in network params _token g1GnKZvzXDztR1lqgDdjI5QOg67SfmmBhjm80fKu product_id 18 subspecifications

Ps1

I've tried to change val() to serialize() and I got

_token g1GnKZvzXDztR1lqgDdjI5QOg67SfmmBhjm80fKu product_id 18 subspecifications subspecifications%5B%5D=20&subspecifications%5B%5D=21&subspecifications%5B%5D=23&subspecifications%5B%5D=32" 

All I needed was 21,23,32 instead i got subspecifications%5B%5D= before each of them.

Ps2

I've tried to change $("body").on("click", ".savedynspecto", function(e){ that would not send any data to back-end (nothing prints in network not even error codes)

Any idea?

3 Answers

Answers 1

After the button... in the string to append, you have {{Form::close()}}</div>.

I think the </div> should come before the {{Form::close()}}.

A messed-up HTML structure can lead to strangenesses quickly.
I'm not 100% sure that is the issue... But it could.

Answers 2

Hi change this line in your code

'subspecifications': $(this).closest('form').find('select.subspecifications').val()

to

'subspecifications': $(this).closest('form').find('select.subspecifications option:selected').map(function(){ return this.value }).get()

It should help

Answers 3

You have MANY select with class subspecifications... So you have to loop through them to get their values.

<script defer>   $(document).ready(function() {    $("body").on("click", ".savedynspecto", function(e){       var form = $(this).closest('form');       var id = form.find('input[name="product_id"]').val();        // An array to store the subspecifications values.       var spec_array = [];        // A loop to go through all them.       form.find('select.subspecifications').each(function(){         spec_array.push($(this).val());       });        // e.preventDefault();       $.ajax({         type: "post",         url: '{{ url('admin/spacssendto') }}',         data: {           '_token': $('input[name=_token]').val(),           'product_id': id,           'subspecifications': spec_array  // The array containing each SELECT values         },         success: function (data) {           alert('Specifications added successfully.');           console.log($(this));         },         error: function (data) {           console.log(data);         }       });     });   }); </script> 
Read More

Friday, August 24, 2018

Laravel : Increase time on second time login attempts

Leave a Comment

Currently five login attempts blocks user for 1 minute and its working fine with the following code :

if ($this->hasTooManyLoginAttempts($request)) {     $this->fireLockoutEvent($request);     return $this->sendLockoutResponse($request); } 

What i want is that, When a user gets unblocked again after the first attempts, On the second attempts the block time should increase to 3 minutes.

I searched around, But could not found anything, Is there any way around it ?

3 Answers

Answers 1

I would suggest you try the following code. Please ask if anything is unclear.

$minutes = 3; $key = $this->throttleKey($request); $rateLimiter = $this->limiter();  if ($this->hasTooManyLoginAttempts($request)) {      $attempts = $rateLimiter->attempts($key);      if ($attempts > 1) {         $attempts === 2 && $rateLimiter->clear($key);         $this->decayMinutes = ($attempts - 1) * $minutes;         $attempts === 2 && $this->incrementLoginAttempts($request);         $this->incrementLoginAttempts($request);     }      $this->fireLockoutEvent($request);     return $this->sendLockoutResponse($request); } 

Code for incremental blocking:

$minutes = 3; $key = $this->throttleKey($request); $rateLimiter = $this->limiter();  if ($this->hasTooManyLoginAttempts($request)) {      $attempts = $rateLimiter->attempts($key);     $rateLimiter->clear($key);     $this->decayMinutes = $attempts === 1 ? 1 : ($attempts - 1) * $minutes;      for ($i = 0; $i < $attempts; $i++) {         $this->incrementLoginAttempts($request);     }      $this->fireLockoutEvent($request);     return $this->sendLockoutResponse($request); } 

Code for incremental blocking with cache:

$minutes = 3; $key = $this->throttleKey($request); $rateLimiter = $this->limiter();  if ($this->hasTooManyLoginAttempts($request)) {      $attempts = $rateLimiter->attempts($key);     $rateLimiter->clear($key);      $reflection = new \ReflectionClass($rateLimiter);     $property = $reflection->getProperty('cache');     $property->setAccessible(true);     $cache = $property->getValue($rateLimiter);      $blockMinutes = $attempts === 1 ? 1 : ($attempts - 1) * $minutes;     $cache->add($key.':timer', $rateLimiter->availableAt($blockMinutes * 60), $blockMinutes);     $added = $cache->add($key, 0, $blockMinutes);     $hits = (int) $cache->increment($key, $attempts);     if (! $added && $hits === 1) {         $cache->put($key, 1, $blockMinutes);     }     $property->setAccessible(false);      $this->fireLockoutEvent($request);     return $this->sendLockoutResponse($request); } 

Answers 2

I think you need to set property in LoginController:

public $decayMinutes = 1; // minutes to lockout 

Also you can controll numbers of attempts:

public $maxAttempts = 5; 

For more information you can investigate: trait AuthenticatesUsers - which has method "login" and code from your description. And this trait uses another trait: "ThrottlesLogins" -> this traits has method named "decayMinutes". It returns number of minutes.

Hope it will help you!

Answers 3

I think laravel default doesn't provide what your need, So you need to save in (cache, session or database) if user had blocked once on your own, and increase decayMinutes as you want.

if ($this->hasTooManyLoginAttempts($request)) {     if(Cache::has($this->throttleKey($request))){         $this->decayMinutes = 3;     }      Cache::put($this->throttleKey($request), true);     $this->fireLockoutEvent($request);      return $this->sendLockoutResponse($request); } 
Read More

Monday, August 20, 2018

How to properly get the name, surname and answers to custom questions, for each participant, so is possible to introduce all info in DB?

Leave a Comment

I have a context where a conference can have 1 or more registration types and the user can do a registration at a conference in 1 or more registration types of the conference.

So for example, the conference with id 1 has two registration types associated, the registration type "general" with id 1 and "plus" with id 2. The registration type "general" has 6 custom questions associated with it, the registration type "plus" don't have any custom question associated with it.

When a user is doing a registration in a conference and select quantity "2" for the registration type "plus" and click "Next" the user goes to the registration page. In this page, there is the registration form and the user only needs to enter his name and surname and click "Register". The $request->all shows like below, the participant's array stores the name, surname, and registration type of each participant:

array:4 [▼   "participant" => array:2 [▼     1 => array:3 [▼       "name" => "John"       "surname" => "W"       "rtypes" => "2"     ]     2 => array:3 [▼       "name" => "Jake"       "surname" => "K"       "rtypes" => "2"     ]   ] ] 

And it works fine all info is correctly stored using the storeRegistrationInfo() method below.

Doubt:

But if the user is doing a registration and select quantity "2" for the registration type "general" and click "Next", the registration type "general" has 6 custom questions associated with it. So in the registration form the user needs to enter his name and surname but the user also needs to answer the 6 required custom questions. My doubt is how to store for each participant the answers to the custom questions, do you know how to properly achieve that? Because as it is, after the user enter the name, surname and the answer to the 6 custom questions and click "Next", the $request->all() dont shows the correct info, it shows like:

array:4 [▼   "participant" => array:4 [▼     1 => array:5 [▼       "name" => "John"       "surname" => "W"       "answer" => "test.jpg"       "question_id" => "6"       "rtypes" => "1"     ]     " 1" => array:1 [▼       "answer" => "option 1"     ]     2 => array:5 [▼       "name" => "Jake"       "surname" => "K"       "answer" => "test.jpg"       "question_id" => "6"       "rtypes" => "1"     ]     " 2" => array:1 [▼       "answer" => "option 2"     ]   ]   "participant_question_required" => array:12 [▼     0 => "1"     1 => "1"     2 => "1"     3 => "1"     4 => "1"     5 => "1"     6 => "1"     7 => "1"     8 => "1"     9 => "1"     10 => "1"     11 => "1"   ] ] 

And so instead of the storeRegistration() stores all necessary info it appear always 3 validation errors:

The field name is mandatory. The field surname is mandatory. Please answer the custom questions. 

Do you know why? The user anwers all this fields but it appear that validation errors.

Below there is the RegistrationController storeRegistration() method that stores the registration info:

class RegistrationController extends Controller {      public function storeRegistration(Request $request, $id, $slug = null)     {         $rules = [             'participant.*.name' => 'required',             'participant.*.surname' => 'required',         ];          $customMessages = [             'participant.*.name.required' => 'The field name is required.',             'participant.*.surname.required' => 'The field surname is required.'         ];          if (isset($request->participant_question_required)) {              foreach ($request->participant_question_required as $key => $value) {                 $rule = 'string|max:255';                  // if this was required, ie 1, prepend "required|" to the rule                 if ($value) {                     $rule = 'required|' . $rule;                 }                  // add the individual rule for this array key to the $rules array                 $rules["participant_question.{$key}"] = $rule;                  $customMessages += [                     'participant_question.*.required' => 'Please answer to the required custom questions.',                 ];             }         }          $this->validate($request, $rules, $customMessages);          $user = Auth::user();           // insert registration in DB         $registration = Registration::create([             'conference_id' => $id,             'user_that_did_the_registration' => $user->id,             'status' => ($total > 0) ? 'I' : 'C'         ]);          // list of all participants (a registration can have multiple participants)         $participants_list = $request->get('participant');          // add all participants to DB         foreach ($participants_list as $participant) {              $name = $participant['name'];             $surname = $participant['surname'];             $participant_result = Participant::create([                 'name' => $name,                 'surname' => $surname,                 'registration_id' => $registration->id,                 'registration_type_id' => $participant['rtypes']             ]);               // store all answers to the custom questions in DB              if (isset($participant['question_id'])) {                 $answer = Answer::create([                     'question_id' => $participant['question_id'],                     'participant_id' => $participant_result->id,                     'answer' => $participant['answer'],                 ]);             }         }          Session::flash('registration_success', 'You are registered in the conference');         return redirect(route('user.index', ['user' => Auth::id()]) . '#myTickets');     } } 

Registration Form:

<form method="post"       action="https://proj.test/conference/1/conference-test/registration/storeRegistration">           <h6>Participant - 1 - general</h6>          <div class="form-group font-size-sm">             <label for="namegeneral_1"                    class="text-gray">Name</label>             <input type="text" required id="namegeneral_1"                    name="participant[name]"                    class="form-control" value="">         </div>         <div class="form-group font-size-sm">             <label for="surnamegeneral_1"                    class="text-gray">Surname</label>             <input type="text" required id="surnamegeneral_1"                    class="form-control"                    name="participant[surname]" value="">         </div>          <div class="form-group">             <label for="participant_question">Input text custom question</label>              <input type='text' name='participant[1][answer]' class='form-control' required>             <input type="hidden"                    name="participant_question_required[]"                    value="1">             <input type="hidden"                    value="1"                    name="participant[1][question_id]"/>         </div>         <div class="form-group">             <label for="participant_question">Long text custom question</label>             <textarea name='participant[1][answer]' class='form-control' rows='3' required></textarea>             <input type="hidden"                    name="participant_question_required[]"                    value="1">             <input type="hidden"                    value="2"                    name="participant[1][question_id]"/>         </div>         <div class="form-group">             <label for="participant_question">Checkbox custom question</label>             <div class='checkbox-group  required'>                 <div class='form-check'>                     <input type='checkbox' name='participant[1][answer]' value='check1' class='form-check-input'>                     <label class='form-check-label' for='exampleCheck1'>check1</label>                 </div>                 <div class='form-check'>                     <input type='checkbox' name='participant[1][answer]' value='check2' class='form-check-input'>                     <label class='form-check-label' for='exampleCheck1'>check2</label>                 </div>             </div>             <input type="hidden"                    name="participant_question_required[]"                    value="1">             <input type="hidden"                    value="3"                    name="participant[1][question_id]"/>         </div>         <div class="form-group">             <label for="participant_question">Radio button custom question</label>             <div class='form-check'>                 <input type='radio' name='participant[1][answer]' value='radio button 1' class='form-check-input'                        required> <label class="form-check-label" for="exampleCheck1">radio button 1</label></div>             <div class='form-check'>                 <input type='radio' name='participant[1][answer]' value='radio button 2' class='form-check-input'                        required> <label class="form-check-label" for="exampleCheck1">radio button 2</label></div>             <input type="hidden"                    name="participant_question_required[]"                    value="1">             <input type="hidden"                    value="4"                    name="participant[1][question_id]"/>         </div>         <div class="form-group">             <label for="participant_question">Select menu custom question</label>             <select name='participant[ 1][answer]' class='form-control' required>                 <option value='option 1'>option 1</option>                 <option value='option 2'>option 2</option>             </select>             <input type="hidden"                    name="participant_question_required[]"                    value="1">             <input type="hidden"                    value="5"                    name="participant[1][question_id]"/>         </div>         <div class="form-group">             <label for="participant_question">File custom question</label>             <input type='file' name='participant[1][answer]' class='form-control' required>             <input type="hidden"                    name="participant_question_required[]"                    value="1">             <input type="hidden"                    value="6"                    name="participant[1][question_id]"/>         </div>         <input type="hidden" name="participant[1][rtypes]"                value="1"/>           <h6>Participant - 2 - general</h6>           <div class="form-group font-size-sm">             <label for="namegeneral_2"                    class="text-gray">Name</label>             <input type="text" required id="namegeneral_2"                    name="participant[name]"                    class="form-control" value="">         </div>         <div class="form-group font-size-sm">             <label for="surnamegeneral_2"                    class="text-gray">Surname</label>             <input type="text" required id="surnamegeneral_2"                    class="form-control"                    name="participant[surname]" value="">         </div>          <div class="form-group">             <label for="participant_question">Input type text custom question</label>             <input type='text' name='participant[2][answer]' class='form-control' required>             <input type="hidden"                    name="participant_question_required[]"                    value="1">             <input type="hidden"                    value="1"                    name="participant[2][question_id]"/>         </div>          <div class="form-group">             <label for="participant_question">Long text custom question</label>             <textarea name='participant[2][answer]' class='form-control' rows='3' required></textarea>             <input type="hidden"                    name="participant_question_required[]"                    value="1">             <input type="hidden"                    value="2"                    name="participant[2][question_id]"/>         </div>         <div class="form-group">             <label for="participant_question">Checkbox custom question</label>             <div class='checkbox-group  required'>                 <div class='form-check'>                     <input type='checkbox' name='participant[2][answer]' value='check1' class='form-check-input'>                     <label class='form-check-label' for='exampleCheck1'>check1</label>                 </div>                 <div class='form-check'>                     <input type='checkbox' name='participant[2][answer]' value='check2' class='form-check-input'>                     <label class='form-check-label' for='exampleCheck1'>check2</label>                 </div>             </div>             <input type="hidden"                    name="participant_question_required[]"                    value="1">             <input type="hidden"                    value="3"                    name="participant[2][question_id]"/>         </div>         <div class="form-group">             <label for="participant_question">Radio button custom question</label>              <div class='form-check'>                 <input type='radio' name='participant[2][answer]' value='radio button 1' class='form-check-input'                        required> <label class="form-check-label" for="exampleCheck1">radio button 1</label></div>             <div class='form-check'>                 <input type='radio' name='participant[2][answer]' value='radio button 2' class='form-check-input'                        required> <label class="form-check-label" for="exampleCheck1">radio button 2</label></div>             <input type="hidden"                    name="participant_question_required[]"                    value="1">             <input type="hidden"                    value="4"                    name="participant[2][question_id]"/>         </div>         <div class="form-group">             <label for="participant_question">Select menu custom question</label>             <select name='participant[ 2][answer]' class='form-control' required>                 <option value='option 1'>option 1</option>                 <option value='option 2'>option 2</option>             </select>             <input type="hidden"                    name="participant_question_required[]"                    value="1">             <input type="hidden"                    value="5"                    name="participant[2][question_id]"/>         </div>         <div class="form-group">             <label for="participant_question">File custom question</label>              <input type='file' name='participant[2][answer]' class='form-control' required>             <input type="hidden"                    name="participant_question_required[]"                    value="1">             <input type="hidden"                    value="6"                    name="participant[2][question_id]"/>         </div>          <input type="hidden" name="participant[2][rtypes]"                value="1"/>         <input type="submit" class="btn btn-primary" value="Register"/> </form> 

2 Answers

Answers 1

Update your registration form using the following form fields :

  • change participant[name] to participant_name[user_index]. [eg. participan_name[1]], where user_index is the index of quantity.

  • change participant[surname] to participant_surname[user_index]. [eg. participan_surname[1]], where user_index is the index of quantity.

  • change participant[1][answer] to answer[user_index][question_id]. [eg. participan_surname[1][5]], where user_index is the index of quantity and question_id is the primary_key of the question.

  • change participant_question_required[] to participant_question_required[question_id]. [eg. participant_question_required[5]], where question_id is the primary_key of the question. set it's value to 1 if required else set to 0.

  • You may ignore participant[1][question_id].[In this method this field is not required]

Update controller- validator using the following method:

$rules = [     'participant_name.*'     => 'required',     'participant_surname.*'  => 'required', ];  $customMessages = [     'participant_name.*.required'    => 'The field name is required.',     'participant_surname.*.required' => 'The field surname is required.' ];  //second section if (isset($request->participant_question_required)) {      foreach ($request->participant_question_required as $questionId => $value) {         $answerRule = 'string|max:255';          // if this was required, ie 1, prepend "required|" to the rule         if ($value == 1) {             // add the individual rule for this array key to the $rules array             $rules['answer.*.$questionId'] = 'required|' . $answerRule;              $customMessages += [                 'answer.*.$questionId'' => [                     'unique' => 'Please answer to the required custom questions.',                 ],             ];         }     } }  $this->validate($request, $rules, $customMessages); 

Hope it helps..

Answers 2

Change name="participant[{{$counter}}][surname]" to name="participant[surname]"  and Change name="participant[{{$counter}}][name]" to name="participant[name]" 

and validations will work.

Understand what you are sending

If you recheck your $request then you will see that $participent is multidimentional array and only first array is having name index but second array does not contain name so it is throwing error back.

validation participent.*.name want only one dimensional array of names like this:

$participent[0] = 'name1' $participent[1] = 'name2' $participent[2] = 'name3' $participent[3] = 'name4'  

that means every index of $participent array must have name index, but you are sending it wrong as below.

$participent[0] = [name=> somename, ...] $participent[1] = [question=>'abc', answer => 'abc'] 

So second index of $participent array does not have name index and your validations wants it.

Read More

Friday, August 10, 2018

Why the info of the registration types associated with the registration are appearing incorrectly?

Leave a Comment

I have a PaymentController where I return to the view the registrationTypeDetails, the total and type_counts. So is possible to show a summary of the registration like below. In this case the user did a registration with two participants in the registration type "general" so it should appear a summary like:

+-------------------+----------+---------+--------------+ | Registration Type | Quantity |  Price  |   Subtotal   |       +-------------------+----------+---------+--------------+ | general           | 2        | 10.00 € |      20.00 € | +-------------------+----------+---------+--------------+ | TOTAL             |          |         |      20.00 € | +-------------------+----------+---------+--------------+ 

But it's appearing like below, with repeated info:

+-------------------+----------+---------+--------------+ | Registration Type | Quantity |  Price  |   Subtotal   |       +-------------------+----------+---------+--------------+ | general           | 2        | 10.00 € |      20.00 € | | general           | 2        | 10.00 € |      20.00 € | +-------------------+----------+---------+--------------+ | TOTAL             |          |         |      20.00 € | +-------------------+----------+---------+--------------+ 

Do you know why the info of the registration types associated with the registration are appearing twice?

In the payment.blade.php view to show the summary of the registration:

<div>     <ul>         <li>             <span>Registration Type</span>             <span>Quantity</span>             <span>Price</span>             <span>Subtotal</span>         </li>          @foreach( $registrationTypeDetails->participants as $participant )             <li>                 <span>{{$participant->registration_type->name}}</span>                 <span>{{$type_counts[$name]}}</span>                 <span>{{ number_format($participant->registration_type->price, 2)}}$</span>                 <span>{{ number_format($participant->registration_type->price * $type_counts[$name], 2)}}$</span>             </li>         @endforeach          <li>             <span>TOTAL</span>             <span>{{ number_format($total, 2)}}$</span>         </li>     </ul> </div> 

Complete code of the PaymentController payment() method where I get the registration types info associated with the registration and redirect the user to the payment page with this info to show a summary of the registration in the payment page.

class PaymentController extends Controller {  // method that shows the payment page public function payment($id, $slug, $regID) {     $regPaid = Registration::where('id', $regID)->pluck('status')->first();      // if the registration is incomplete because the user didnt pay yet      if ($regPaid == "I") {          // get the current user         $user_id = Auth::id();          $conferenceDetails = Registration::with([             'conference' => function ($query) {                 $query->select('id', 'name', 'start_date');             }         ])->find($regID);          $registrationTypeDetails = Registration::with(['participants.registration_type',             'participants' => function ($query) use ($regID) {                 $query->select('id', 'registration_type_id', 'registration_id')->where('registration_id', $regID);             }         ])->find($regID);          $price = $registrationTypeDetails->participants->sum(function ($participant) {             return $participant->registration_type->price;         });          $total = $price;         $totalStripe = $total * 100;          if ($registrationTypeDetails->main_participant_id != $user_id) {             return redirect('/');         } else {             $type_counts = [];             foreach ($registrationTypeDetails->participants as $p) {                 $name = $p->registration_type->name;                 if (!isset($type_counts[$name])) {                     $type_counts[$name] = 0;                 }                 $type_counts[$name]++;             }              Session::put('total', $total);             Session::put('totalStripe', $totalStripe);             Session::put('registrationID', $regID);              $conferenceDetails = [                 'name' => $conferenceDetails->conference->name,                 'start_date' => $conferenceDetails->conference->start_date             ];              Session::put('conference_name', $conferenceDetails['name']);             Session::put('date', $conferenceDetails['end_date']);              return view('conferences.payment', compact('conferenceDetails', 'name', 'total', 'type_counts', 'registrationTypeDetails', 'id', 'slug'));         }     } else {          Session::flash('registration_complete', 'Your registration is already paid.');         return redirect(route('user.index', ['user' => Auth::id()]) . '#myTickets');     }  } 

1 Answers

Answers 1

I believe the problems is coming from the way you construct your data. Use left join and methods from Model would strongly make your code more readable and helps you to debug. I don't know whether it's good or not to use with() like how you did.

Some of the functions really need to go into your model's file to make it OOP.

The way of gathering data into that conferenceDetails is a whole bunch of mess to me. I would suggest you to not only think about gathering all data you need, but also think about how the data is structured.

In your case, Conference, Registration, Participant are 3 major model. Your code messed up the relationships between each other. If your program is focused on Conference, then use Conference as the major model and get data base on it. Like:

$registrations=$conference->registrations; $participants=$conference->participants; 

Not using $conference->participants() is because the participants() should return a relation not a collection whereas participants returns the collection you will need. This is done by Laravel

Now you can see, conference data is conference data, do not mess it up with participants and registrations. In your Conference model, write a function called particiants(), and in there do the work of getting data through a many to many relationship. You can find example code in Laravel docs.

In addition, I wouldn't suggest you to touch on payments if your are a starter. A payment system requires much more than what I can see from you code. The transactions, the refunds and a lot more. Even if you are using Stripe, I can still see you don't have a solid understanding of MVC programming. If I was writing this program, I would get many steps done over Ajax and provide a more user friendly interactive interface with more detailed errors and messages to help the users.

Anyway, check the code below and adjust it to fit your needs. Do not use your program on a production program, it definitely will cause nightmares. Especially when payments are related.

public function payment($id, $slug, $regID){ $reg=Registration::find($regID); //It's always good to check if the instance exists if(!$reg){     return back()->with(['error'=>'Could not find the Registration with the given ID']); }  if($reg->status=='I'){ //By the way, if the status is an integer, it would be better for database design in MySQL     $isRegPaid=false; } else {     $isRegPaid=true; }  // get the current user $user_id = Auth::id(); //remember you can also use $request->user() if the function provides Request $request as a parameter  // if the registration is incomplete because the user didnt pay yet  if ($isRegPaid) {     $conferenceDetails = Registration::with([         'conference' => function ($query) {             $query->select('id', 'name', 'start_date');         }     ])->find($regID);      $conferences=Conference::where('registration_id',$regID)->get();     //        $registrationTypeDetails = Registration::with(['participants.registration_type',     //            'participants' => function ($query) use ($regID) {     //                $query->select('id', 'registration_type_id', 'registration_id')->where('registration_id', $regID);     //            }     //        ])->find($regID);      $total=0;     //As I don't know the actual structure of your model, I could only provide you the most basic way to sum the price     //If I was writing my own code, I could do a lot more than this like left join etc.     //And you also need to remember to put some of the functions into your Model, to make it more Object Oriented     foreach($conferences as $conference){         //I'm assuming participants is actually a function is Conference, if it's in Registration, you will need to rewrite this part         foreach($conference->participants as $participant){             $total+=$participant->registration_type->price;         }     }      $totalStripe = $total * 100;      if($reg->main_participant_id != $user_id){ //I'm guessing this main_participant_id property belongs to Registration         return back()->with(['error'=>'You do not have the permission to process this payment']); //Let the user know what error they have encountered, it's good for debugging     } else {         $type_counts = [];         foreach($conferences as $conference){             foreach($conference->participants as $participant){                 $name = $participant->registration_type->name; // I don't know how you could get this name property, assuming registration_type should be a function                 if (!isset($type_counts[$name])) {                     $type_counts[$name] = 0;                 }                 $type_counts[$name]++;             }         }          Session::put('total', $total);         Session::put('totalStripe', $totalStripe);         Session::put('registrationID', $regID);          //You will need to rewrite this part to format your data         //            $conferenceDetails = [         //                'name' => $conferenceDetails->conference->name,         //                'start_date' => $conferenceDetails->conference->start_date         //            ];          Session::put('conference_name', $conferenceDetails['name']);         Session::put('date', $conferenceDetails['end_date']);          //            return view('conferences.payment', compact('conferences', 'name', 'total', 'type_counts', 'registrationTypeDetails', 'id', 'slug'));         //ID should not need to be rendered into the view as it should be able to be accessed from Request         //I don't like using compact, I usually do this:         return view('conferences.payment')->with(['conferences'=>$conferences,'name'=>$name,'total'=>$total,'type_counts'=>$type_counts,             'slug'=>$slug]);     } } else {     Session::flash('registration_complete', 'Your registration is already paid.');     return redirect(route('user.index', ['user' => Auth::id()]) . '#myTickets'); } } 
Read More

Tuesday, August 7, 2018

Get info by javascript in laravel

Leave a Comment

I'm trying to get specific column from second table by javascript and return data of it to my view but i'm not sure how it can be done.

Logic

  1. Products table has price column
  2. Discount table has product_id, min, max and amount columns
  3. I input number as quantity, if have product id in my discount table base on min and max return the amount as new price

Code

so far this is my codes (I am aware that specially my controller method has identifying issue to find te right data)

JavaScript

<script>   $(document).ready(function() {     // if quantity is not selected (default 1)     $('#newprice').empty();     var quantity =  parseInt(document.getElementById("quantity").value);     var shipingcost = parseFloat(quantity);     var shipingcostnumber = shipingcost;     var nf = new Intl.NumberFormat('en-US', {         maximumFractionDigits:0,          minimumFractionDigits:0     });     $('#newprice').append('Rp '+nf.format(shipingcostnumber)+'');      // if quantity is changed     $('#quantity').on('change', function() {       var quantity = parseInt(document.getElementById("quantity").value);       var qtyminmax = $(this).val();       if(qtyminmax) {         $.ajax({           url: '{{ url('admin/qtydisc') }}/'+encodeURI(qtyminmax),           type: "GET",           dataType: "json",           success:function(data) {             $('#totalPriceInTotal').empty();             var shipingcost = parseFloat(data)+parseFloat(quantity);             var shipingcostnumber = shipingcost;             var nf = new Intl.NumberFormat('en-US', {                 maximumFractionDigits:0,                  minimumFractionDigits:0             });             $('#totalPriceInTotal').append('Rp '+nf.format(shipingcostnumber)+'');           }         });       }else{         //when quantity backs to default (1)         $('#newprice').empty();         var quantity = parseInt(document.getElementById("quantity").value);         var shipingcost = parseFloat(quantity);         var shipingcostnumber = shipingcost;         var nf = new Intl.NumberFormat('en-US', {             maximumFractionDigits:0,              minimumFractionDigits:0         });         $('#newprice').append('Rp '+nf.format(shipingcostnumber)+'');       }     });   }); </script> 

Route

Route::get('qtydisc/{id}', 'ProductController@qtydisc'); 

Controller

public function qtydisc($id){       return response()->json(QtyDiscount::where('min', '>=', $id)->orWhere('max', '<=', $id)->pluck('min'));     } 

Question

  1. What should I change in my controller method to get the right data?
  2. What should I change in my JavaScript code? should I add product ID in my route as well or...?

thanks in advanced.

UPDATE

I'm trying some changes in my code but I can't get right amount

controller

public function qtydisc($id, $qty){       $price = DB::table('qty_discounts')               ->where('product_id', $id)               ->where([                   ['min', '>=', $qty],                   ['max', '<=', $qty],               ])               // ->where('min', '>=', $qty)               // ->where('max', '<=', $qty)               ->select('amount')               ->first();       return response()->json($price);     } 

route

Route::get('qtydisc/{id}/{qty}', 'ProductController@qtydisc'); 

javascript

//as before...  $('#quantity').on('change', function() {       var idofproduct = ["{{$product->id}}"]; //added       var quantity = parseInt(document.getElementById("quantity").value);       var qtyminmax = $(this).val();       if(qtyminmax) {         $.ajax({           url: '{{ url('admin/qtydisc') }}/'+idofproduct+'/'+encodeURI(qtyminmax), //changed           type: "GET",           dataType: "json",           success:function(data) {             $('#totalPriceInTotal').empty();             var shipingcost = parseFloat(data)+parseFloat(quantity);             var shipingcostnumber = shipingcost;             var nf = new Intl.NumberFormat('en-US', {                 maximumFractionDigits:0,                  minimumFractionDigits:0             });             $('#totalPriceInTotal').append('Rp '+nf.format(shipingcostnumber)+'');           }         });       }else{ //rest of it as before 

Screenshot

screenshotdb

that's how my database look like and as results for quantity between 2 to 6 i get 7000 while i have to get 5000 from 2 to 5.

From number 7 to 9 i get no results at all.

From number 10 to up all i get is 7000

Any idea?

5 Answers

Answers 1

The main problem comes from your eloquent statement, you should use the >= in the both conditions with OR operator using the orWhere helper, so it should be like :

$price = QtyDiscounts::where('product_id', $id)     ->where('min', '>=', $qty)     ->orWhere('max', '>=', $qty)     ->pluck('amount')     ->first(); 

But you need really to take a look to your JS structure, I suggest to split your code to function for the DRY concept, first the event listener like :

$('body').off('input', '#quantity').on('input', '#quantity', function () {     var qty = parseInt( $(this).val() );      if(qty) {         getAmount(qty);     }else{         getAmount(1);     } }); 

Then the helper functions to call :

function setValue(quantity, amount) {     var newPrice = $('#newprice');     var totalPrice = $('#totalPriceInTotal');      newPrice.empty();     totalPrice.empty();      var nf = new Intl.NumberFormat('en-US', {         maximumFractionDigits:0,         minimumFractionDigits:0     });      newPrice.val('Rp '+nf.format(amount)+'');     totalPrice.val('Rp '+nf.format(parseFloat(amount)*parseFloat(quantity))+''); };  function getAmount(quantity) {     var url = $('#qty_url').val();     var productId = $('#product_id').val();      $.ajax({         url: url+'/'+productId+'/'+encodeURI(quantity),         type: "GET",         dataType: "json",         success:function(amount) {             setValue(quantity, amount);         }     }); }; 

I guess you've a simple HTML structure, so it should work this way, why I suggest here if never using the php variable like url & product_id directly inside your JS code instead attach the values you want to hidden inputs and get the value of these input's using the JS :

<input type="hidden" id="qty_url" value="{{ url('admin/qtydisc') }}" /> <input type="hidden" id="product_id" value="{{ $product->id }}"/> 

Answers 2

Use the opposite condition in the backend controller:

$price = DB::table('qty_discounts')           ->where('product_id', $id)           ->where('min', '<=', $qty)           ->where('max', '>=', $qty)           ->select('amount')           ->first();  

So for $id = 2 and $qty = 5 you will get

product id = 2  and `min` <= 5 and `max` >= 5 

selecting the first row (row_id = 1)

Answers 3

Stop mixing JavaScript with jQuery, instead of

var quantity = parseInt(document.getElementById("quantity").value); 

You could just do

var quantity = +$('#quantity').val(); 

And expect this to work even in old Internet Explorer

Going further

Routes

Route::get('/ajax/product/{product}/distcounts', 'AjaxController@productDiscounts); 

Html

<input type="number" name="quantity" data-product_id="{{ $product->getKey() }}"> 

PHP

public function productDiscounts(Product $product, Request $request) {     // $product is just a product you are interested in     // $request->quantity containts passed amount      $discounts = DiscountModel::product(     // Scope         $product->getKey()     )     ->get()     ->filter(function($element) use($request) {         return ($element->min <= $request->quantity && $element->max >= $request->quantity);     })     ->values();      return response->json(discounts); } 

JavaScript

(function($) {     var getDiscountAmount(e) {         var $input      = $(e.target);         var product_id  = $input.data('product_id');          $.ajax({             method: 'GET',             url: '/ajax/product/' + product_id + '/discount',             data: {                 quantity: $input.val()             },             beforeSend: function() {                 $input.prop('disabled', true);             },             success: function(discounts) {                 $input.prop('disabled', false);                  // Dunno what you want to do with list of discounts right there                 // It should be an array containing 1 object, but who knows             }         })     }      $(input[name="quantity"]).on('change', getDiscountAmount); })(jQuery); 

Hope it helps, didn't test the code so you may need to add some tweaks

Answers 4

If you ever in a situation to share data from controller to JS, then I suggest using this package JsTransformers. It makes you do something like this

// in controller public function index() {     JavaScript::put([         'foo' => 'bar',         'user' => User::first(),         'age' => 29     ]);      return view('index'); }  // in view scripts console.log(foo); // bar console.log(user); // User Obj console.log(age); // 29 

Answers 5

You should write your query like this:

public function qtydisc($id, $qty){       $price = DB::table('qty_discounts')               ->where('product_id', $id)               ->where($qty, '>=', 'min')               ->where($qty, '<=', 'max')               ->select('amount')               ->first();       return response()->json($price);     } 
Read More

Saturday, August 4, 2018

single sign on (sso) laravel

Leave a Comment

I have three different laravel websites, I want to make user sign in at one website then he will be automatically logged in to the other two websites. eg. if you logged in at your stackoverflow then open stackexchange you will be logged in with StackOverflow account. I have tried many packages but they end with infinite exceptions or they simply not working. Most of the packages based on SAML, I have no idea why it did not work with me? I do not know what I miss? Is there any config for this to work? I am using laravel 5.6. All the apps are on the same server.

I have tried many solutions based on SAML, OpenID and share session, but all of them did not work with me. I do not know if I miss something. this is the last example I tried and it did not work

this is my code

SITE A

$site_b = 'http://s_sesstion_2.test/'; Route::get('/', function (Request $request) use ($site_b) {     $session_id = Session::getId();     try {         $http = new Client();         $response = $http->post($site_b . 'api/sessions/server', [             'form_params' => [                 'session_id' => $session_id,             ],             'headers' => [                 'Accept' => 'application/json',             ]         ]);     } catch (Exception $e) {         dd($e->getMessage());     }     return view('welcome'); }); 

SITE B (route/api.php)

    Route::post('/sessions/server', function (Request $request) {     Storage::disk('local')->put('file.txt', $request->get('session_id')); }); 

SITE B (route/web.php)

    Route::get('/', function () {     $session_id = Storage::disk('local')->get('file.txt');     Session::setId($session_id);     Session::start();     //return Session::getId();// will return the same session id     return \auth()->user();//this should return the auth user but it did not!! }); 

All I want is to sign in at site A then open site B I will be signed in. I will accept any solution achieve that purpose

2 Answers

Answers 1

I implemented an SSO solution without using SAML. I'll share my solution here, hope it helps.

Single Sign On

One application runs as the main authentication server at auth.domain. Other applications run in different domains app1.domain, app2.domain, ...

Every user is linked with SSO tokens. These tokens have very short expiration times. All authentication processes (signing in, resetting passwords, registering, ...) happen only in auth.domain application.

When a user visits any applications, for example, app-1.domain:

  1. Redirect user to auth.domain/login.
  2. If the user logged in our system before, continue at step 6.
  3. Show the sign in form, waiting for valid input.
  4. Generate a new SSO token with the expiration time less than 3 minutes.
  5. Attach the auth.domain remember me cookie to the response.
  6. Return a redirection response to the app-1.domain/sso/{sso_token}.
  7. app-1.domain application read the database. If the SSO token is valid and does not expire, find the user associated to that token.
  8. app-1.domain authenticates the user found in the previous step with Auth::login($user) method.
  9. app-1.domain clear the received SSO token from the database.

After this step, the user is authenticated to app-1.domain.

Session sharing

All shared session variables should be saved to databases. I implemented a new session driver:

  • Keep the list of shared session variable names
  • When reading/writing to sessions, check the name of the session variable. If that name is the previous list, read/write the value from the database. Otherwise, use the private session of each own application.

Answers 2

If your both applications share the same databases then you can follow the approach :

-> In your database , create a default session id that will be marked as false initially

-> Now as soon as user login to any of the site, generate a new hash and replace it with the default value.


optionally

-> You can also save the hash on browser local storage with hash as a key and null as value.


-> Now when user is logging into/switching to any of the site, check that hash -> If the hash matches the default, show the login page else show the profile page.


My answer is valid only if you are using common database for login else you need mapping for this.


Alternatively you can use cookies to store hash and can access them in cross domain. Can find example at Cross-Domain Cookies By @ludovic

Read More

Sunday, July 29, 2018

Laravel: How to update the MySQL by Eloquent in child process?

Leave a Comment

I write a Laravel Command, and it will fork some child process. Child process will update the DB by Eloquent.

Code:

<?php  namespace App\Console\Commands;  use App\Console\BaseCommand; use App\Item; use Illuminate\Console\Command;  class Test extends Command {     /**      * The name and signature of the console command.      *      * @var string      */     protected $signature = 'test';      /**      * The console command description.      *      * @var string      */     protected $description = 'Command description';      /**      * Create a new command instance.      *      * @return void      */     public function __construct()     {         parent::__construct();     }      /**      * Execute the console command.      *      * @return mixed      */     public function handle()     {         Item::first();         $children = [];         for($i = 0; $i < 5; $i++) {             $pid = pcntl_fork();             if ($pid == -1) {                 die('pmap fork error');             } else {                 if ($pid) {                     $children[] = $pid;                 } else {                     Item::first(); exit;                 }             }         }         foreach ($children as $child) {             pcntl_waitpid($child, $status);         }     } } 

Run my code:

vagrant@homestead:~/ECAME$ php artisan test     [Illuminate\Database\QueryException]   Packets out of order. Expected 1 received 116. Packet size=6255201 (SQL: select * from `items` where `items`.`deleted_at` is null limit 1)      [Illuminate\Database\QueryException]   Packets out of order. Expected 1 received 100. Packet size=6238815 (SQL: select * from `items` where `items`.`deleted_at` is null limit 1)      [Illuminate\Database\QueryException]   Packets out of order. Expected 1 received 0. Packet size=2816 (SQL: select * from `items` where `items`.`deleted_at` is null limit 1)      [Illuminate\Database\QueryException]   Packets out of order. Expected 1 received 116. Packet size=6381412 (SQL: select * from `items` where `items`.`deleted_at` is null limit 1)      [ErrorException]   Packets out of order. Expected 1 received 100. Packet size=6238815      [ErrorException]   Packets out of order. Expected 1 received 116. Packet size=6381412      [ErrorException]   Packets out of order. Expected 1 received 116. Packet size=6255201      [ErrorException]   Packets out of order. Expected 1 received 0. Packet size=2816 

What's the reason behind that? And How to update the MySQL by Eloquent in child process?

PS:

I think the reason for that problem is, all child processes use the same MySQL connection which forked from the parent process.

If I don't call Item::first() in the parent process before call fork(), it works well. (In my real use case, I can't do that... The parent process will do a lot with MySQL before fork child process.)

Because in that case, the MySQL connection doesn't initialize in the parent process, so every child process will initialize a connection on their own.

So, if it's the case, how to initialize a new MySQL connection for each child process after forked?

2 Answers

Answers 1

Since it's all about the connection dying, you can solve this by simply reconnecting to the database.

use Illuminate\Support\Facades\DB;  [...]  public function handle() {     User::first();      $children = [];      for ($i = 0; $i < 5; $i++)      {         $pid = pcntl_fork();          if ($pid == -1)          {             die('pmap fork error');         }          else          {             if ($pid)              {                 $children[] = $pid;             }              else             {                 DB::connection()->reconnect(); // <----- add this                 User::first(); exit;             }         }     }      foreach ($children as $child)      {         pcntl_waitpid($child, $status);     } } 

I tested this in Laravel 5.6 and it works.

Answers 2

And if you define a second ddbb with equal parameters in your database.php , and you launch your Item::first based in the second connection ?

# Primary database connection             'mysql' => [                 'driver'    => 'mysql',                 'host'      => 'localhost',                 'database'  => 'myddbb',                 'username'  => 'root',                 'password'  => '',                 'charset'   => 'utf8',                 'collation' => 'utf8_unicode_ci',                 'prefix'    => '',             ],   # Secondary database connection         'mysql_forConnectChildren' => [             'driver'    => 'mysql',             'host'      => 'localhost',             'database'  => 'myddbb',             'username'  => 'root',             'password'  => '',             'charset'   => 'utf8',             'collation' => 'utf8_unicode_ci',             'prefix'    => '',         ], 

Later

 $item = \DB::connection('mysql_forConnectChildren')->select('select * from Item')->get(1); 

I not tested but i think can works

Read More

Sunday, July 1, 2018

Adding currency picker to laravel

Leave a Comment

I want to have a drop-down in my navbar where I can select a currency and all the prices in my app convert to selected currency, I know I should use middle-ware for this matter but I don't know how to begin. I am using Fixer with laravel-swap as a package for exchange rates.

What I've done

I have made a middleware named it Currancy and it's content:

<?php  namespace App\Http\Middleware;  use Closure; use Swap\Swap; use Swap\Builder;  class Currancy {     /**      * Handle an incoming request.      *      * @param  \Illuminate\Http\Request  $request      * @param  \Closure  $next      * @return mixed      */     public function handle($request, Closure $next)     {         if (Session::has('appcurrency') AND array_key_exists(Session::get('appcurrency'), Config::get('currencies'))) {             $currency = Session::get('appcurrency');              App::setLocale($currency);         }         else {           App::setLocale(Config::get('app.currency'));         }         return $next($request);     } } 

I also made a currencies.php in config folder:

<?php  return [   'IDR' => [       'name' => 'Indunesian Rupiah',   ],   'USD' => [       'name' => 'U.S Dollar',   ],   'EUR' => [       'name' => 'Euro',   ], ]; 

I also added this to my config\app.php

'currency' => 'IDR', 

in that case my default currency is IDR unless user select others.

PS: for my middleware and config file I've got the idea of language translation and I don't have an idea how to join it to SWAP package in order to work! :\

Questions

  1. Is the way I try to handle currencies correct way?
  2. What else should I do for the next step?

thanks.

1 Answers

Answers 1

First, App::setLocale() is for language purpose.

Second Dont use a middle-ware to set a session default value it will bloat your route file. use the view composer for output. https://laravel.com/docs/4.2/responses#view-composers

Run this command if you dont have a view composer provider:

php artisan make:provider ComposerServiceProvider 

Then in the file "app/Providers/ComposerServiceProvider.php", in the "boot()" method

public function boot() {     View::composer(array('header','footer'), function($view)     {         if (!currentCurrency()) {             setCurrency(config('app.currency'));         }         $view->with('currencies', config('currencies');     }); } 

And define some helper functions. to load a helper, use the autoload feature of the composer. in the file "composer.json" in the "autoload" attribute tight after the "psr-4" one: here it will load the file "app/Support/helpers.php" as an example.

    "psr-4": {         "App\\": "app/"     },     "files": [         "app/Support/helpers.php"     ] 

After jou change the "composer.json" file, dont forget to regenerate the autoload file with the command:

composer dump-autoload 

then in the file "app/Support/helpers.php" (create it) put these functions:

<?php  if (!function_exists('currentCurrency')) {     /**      * @return string      */     function currentCurrency(){         if (Session::has('appcurrency') AND array_key_exists(Session::get('appcurrency'), config('currencies'))) {             return Session::get('appcurrency');         }         return '';     } }  if (!function_exists('setCurrency')) {     /**      * @param string $currency      * @return void      * @throws \Exception      */     function setCurrency($currency){         if (array_key_exists($currency, config('currencies'))) {             Session::set('appcurrency', $currency);         } else {             throw new \Exception('not a valid currency');         }     } } 

if you need to set the locale to one of the currency, juste change the "setCurrency" method (since you only need to set the locale once in a session.

/** * @param string $currency * @return void * @throws \Exception */ function setCurrency($currency){     if (array_key_exists($currency, config('currencies'))) {         Session::set('appcurrency', $currency);         App::setLocale($currency);     } else {         throw new \Exception('not a valid currency');     } } 
Read More

Friday, June 22, 2018

How to organize this context to collect properly the answers of each participant?

Leave a Comment

I have a form for a user register in a conference. But when the user clicks in "Store Registration" it shows an undefined offset error and the issue should be because I'm not organizing correctly this context. The form to register in a conference is different depending on if the "all_participants" column of the conferences table is "1" or "0".

If all_participants is 1 in the conferences table

If the conferences table has the column "all_participants" with value "1" that means that is necessary to collect the name and surname of each participant (about each selected registration type). So the form will show form fields to collect the name and surname of each participant. Also if some of the selected registration type(s) have custom questions associated is necessary to collect the answers to that questions for each participant if "all_participants" is 1. In the image above the registration type "General" has a custom question associated "Phone", so its necessary to collect the answers of the two participants there are being registered with the registration type "General".

Image that shows the form of all_participants as "1" in the conferences table

enter image description here

After the user click in "Store Registration" it should be stored in the tables like below:

enter image description here

If all_participants is 0 in the conferences table

If "all_participants" is "0" the form would have only one field "Phone" because the name and surname are got directly from the authenticated user info. And because "all_participants" is "0" is only necessary also to collect the phone of the user that is doing the registration (the authenticated user), for the other participants is not necessary because "all_participants" is "0". So the form if "all_participants" is "0" would have only one field (Phone) in this case. And the participants table is only necessary to store the name and surname of the user that did the registration, for the other participants can be stored empty "".

Form if all_participants is "0":

enter image description here

After the user click in "Store Registration" it should be stored in the tables like below:

enter image description here

HTML of this image context for the case of "all_participants" is 1:

<form method="post" id="registration_form" action="http://proj.test/conference/1/conference-title/registration/storeRegistration">    <h6>Participant - 1 - general</h6>   <div class="form-group">     <label for="namegeneral_1">Name</label>     <input type="text" id="namegeneral_1" name="participant_name[]" required="" class="form-control" value="">   </div>    <div class="form-group">     <label for="surnamegeneral_1">Surname</label>     <input type="text" id="surnamegeneral_1" required="" class="form-control" name="participant_surname[]" value="">   </div>    <div class="form-group">     <label for="participant_question">Phone?</label>     <input type="text" name="participant_question[]" class="form-control" required="">     <input type="hidden" name="participant_question_required[]" value="1">     <input type="hidden" value="1" name="participant_question_id[]">   </div>    <input type="hidden" name="rtypes[]" value="1">    <h6> Participant - 2 - general</h6>    <div class="form-group">     <label for="namegeneral_2">Name</label>     <input type="text" id="namegeneral_2" name="participant_name[]" required="" class="form-control" value="">   </div>    <div class="form-group">     <label for="surnamegeneral_2">Surname</label>     <input type="text" id="surnamegeneral_2" required="" class="form-control" name="participant_surname[]" value="">   </div>    <div class="form-group">     <label for="participant_question">Phone?</label>     <input type="text" name="participant_question[]" class="form-control" required="">     <input type="hidden" name="participant_question_required[]" value="1">     <input type="hidden" value="1" name="participant_question_id[]">   </div>    <input type="hidden" name="rtypes[]" value="1">    <h6> Participant - 1 - plus</h6>    <div class="form-group font-size-sm">     <label for="nameplus_1">Name</label>     <input type="text" id="nameplus_1" name="participant_name[]" required="" class="form-control" value="">   </div>    <div class="form-group font-size-sm">     <label for="surnameplus_1">Surname</label>     <input type="text" id="surnameplus_1" required="" class="form-control" name="participant_surname[]" value="">   </div>    <input type="hidden" name="rtypes[]" value="2">    <input type="submit" class="btn btn-primary" value="Store Registration"> </form> 

When user clicks in "Store Registration" the code goes to the StoreRegistration() to store all the registration info in database:

public function storeRegistration(Request $request, $id, $slug = null)   {       $allParticipants = Conference::where('id', $id)->first()->all_participants;       $user = Auth::user();        $rules = [];       $messages = [];        if ($allParticipants == 1) {            $rules["participant_name.*"] = 'required|max:255|string';           $rules["participant_surname.*"] = 'required|max:255|string';       }        $validator = Validator::make($request->all(), $rules);        if ($validator->passes()) {            $total = Session::get('total');            $registration = Registration::create([               'conferenec_id' => $id,               'main_participant_id' => $user->id,               'status' => ($total > 0) ? 'I' : 'C',           ]);            $participants = [];            for ($i = 0; $i < count($request->participant_name); $i++) {               $name = ($allParticipants) ? $request->participant_name[$i] : '';               $surname = ($allParticipants) ? $request->participant_surname[$i] : '';               $participants[] = Participant::create([                   'name' => $name,                   'surname' => $surname,                   'registration_id' => $registration->id,                   'registration_type_id' => $request->rtypes[$i]                ]);           }            if (isset($request->participant_question)) {               foreach( $request->participant_question as $key => $question ) {                   $answer = Answer::create([                       'question_id' => $request->participant_question_id[$key],                       'participant_id' => $participants[$key]->id, // undefined index error is here                       'answer' => $request->participant_question[$key],                   ]);               }           }           return redirect(route('user.index', ['user' => Auth::id()]).'#myTickets');       }       else{           dd($validator->errors());       }   } 

I have the Question model that has the getHtmlInput() to generate the HTML for the custom questions and add the required attribute to the field if the "required" column in pivot table "registration_type_questions" is "1":

class Question extends Model {     protected $fillable = [         'question', 'type', 'conference_id',     ];     public static $typeHasOptions = [         'radio_btn',         'select_menu',         'checkbox'     ];     public function registration_type()     {         return $this->belongsToMany('App\RegistrationType', 'registration_type_questions')             ->withPivot('required');     }     public function options()     {         return $this->hasMany('App\QuestionOption');     }     public function hasOptions()     {         return in_array($this->type, self::$typeHasOptions);     }     public function getHtmlInput($name = "", $options = "", $required = false, $customtype = false)     {         $html = '';         $html .= $customtype == 'checkbox' ? "<div class='checkbox-group ".($required ? " required" : "")."'>" : '';         $html .= $customtype == 'select_menu' ? "<select name='participant_question[]' class='form-control' " . ($required ? " required" : "")             . ">" : '';          if (empty($options)) {             switch ($customtype) {                 case "text":                      $html .= "                  <input type='text' name='participant_question[]' class='form-control'" . ($required ? " required" : "")                         . ">";                     break;                  case "file":                     $html .= "                  <input type='file' name='participant_question[]' class='form-control'" . ($required ? " required" : "") . ">";                     break;                  case "long_text":                     $html .= "             <textarea name='participant_question' class='form-control' rows='3'" . ($required ? " required" : "") . ">"                         . $name .                         "</textarea>";                     break;             }         } else {             foreach ($options as $option) {                 switch ($customtype) {                     case "checkbox":                         $html .= "          <div class='form-check'>             <input type='checkbox' name='participant_question[]' value='" . $option->value . "' class='form-check-input' >                 <label class='form-check-label' for='exampleCheck1'>" . $option->value . "</label>         </div>";                         break;                     case "radio_btn":                         $html .= "              <div class='form-check'>                 <input type='radio' name='participant_question[]' value='" . $option->value . "' class='form-check-input'" . ($required ? " required" : "") . ">" .                             '    <label class="form-check-label" for="exampleCheck1">' . $option->value . '</label>' .                             "</div>";                         break;                     case "select_menu":                         $html .= "<option value='" . $option->value . "'>" . $option->value . "</option>";                         break;                 }             }         }         $html .= $customtype == 'select_menu' ? "</select>" : '';         $html .= $customtype == 'checkbox' ? "</div>" : '';          return $html;     } } 

Then in the view the getHtmlInput() is used like:

@foreach($selectedRtype['questions'] as $customQuestion)   <div class="form-group">       <label for="participant_question">{{$customQuestion->question}}</label>       @if($customQuestion->hasOptions() && in_array($customQuestion->type, ['checkbox', 'radio_btn', 'select_menu']))           {!! $customQuestion->getHtmlInput(               $customQuestion->name,               $customQuestion->options,               ($customQuestion->pivot->required == '1'),               $customQuestion->type)           !!}        @else           {!! $customQuestion->getHtmlInput(               $customQuestion->name,               [],               ($customQuestion->pivot->required == '1'),               $customQuestion->type)           !!}       @endif       <input type="hidden"              name="participant_question_required[]"              value="{{ $customQuestion->pivot->required }}">       <input type="hidden"              value="{{ $customQuestion->id }}"              name="participant_question_id[]"/>   </div> @endforeach 

1 Answers

Answers 1

I've slightly modified the HTML file structure.

<form method="post" id="registration_form" action="http://proj.test/conference/1/conference-title/registration/storeRegistration">  {{csrf_field()}}  <h6>Participant - 1 - general</h6> <div class="form-group">     <label for="namegeneral_1">Name</label>     <input type="text" id="namegeneral_1" name="participant[1][name]" required="" class="form-control" value=""> </div>  <div class="form-group">     <label for="surnamegeneral_1">Surname</label>     <input type="text" id="surnamegeneral_1" required="" class="form-control" name="participant[1][surname]" value=""> </div>  <div class="form-group">     <label for="participant_question">Phone?</label>     <input type="text" name="participant[1][answer]" class="form-control" required="">     <input type="hidden" name="participant_question_required[]" value="1">     <input type="hidden" value="1" name="participant[1][question_id]"> </div>  <input type="hidden" name="participant[1][rtypes]" value="1">  <h6> Participant - 2 - general</h6>  <div class="form-group">     <label for="namegeneral_2">Name</label>     <input type="text" id="namegeneral_2" name="participant[2][name]" required="" class="form-control" value=""> </div>  <div class="form-group">     <label for="surnamegeneral_2">Surname</label>     <input type="text" id="surnamegeneral_2" required="" class="form-control" name="participant[2][surname]" value=""> </div>  <div class="form-group">     <label for="participant_question">Phone?</label>     <input type="text" name="participant[2][answer]" class="form-control" required="">     <input type="hidden" name="participant_question_required[]" value="1">     <input type="hidden" value="1" name="participant[2][question_id]"> </div>  <input type="hidden" name="participant[2][rtypes]" value="1">  <h6> Participant - 1 - plus</h6>  <div class="form-group font-size-sm">     <label for="nameplus_1">Name</label>     <input type="text" id="nameplus_1" name="participant[3][name]" required="" class="form-control" value=""> </div>  <div class="form-group font-size-sm">     <label for="surnameplus_1">Surname</label>     <input type="text" id="surnameplus_1" required="" class="form-control" name="participant[3][surname]" value=""> </div>  <input type="hidden" name="participant[3][rtypes]" value="2">  <input type="submit" class="btn btn-primary" value="Store Registration"> 

I will store all the ‍participants in an array named participant and send thme to Backend

output :

array:2 [▼   "_token" => "WDtDV0CL6OKVCsGSi5HNyi4HQ6Pmo6VAwzDsgYK1"   "participant" => array:3 [▼     1 => array:5 [▼       "name" => "ali"       "surname" => "shahabi"       "answer" => "0937"       "question_id" => "1"       "rtypes" => "1"     ]     2 => array:5 [▼       "name" => "danyal"       "surname" => "shahabi"       "answer" => "0938"       "question_id" => "1"       "rtypes" => "1"     ]     3 => array:3 [▼       "name" => "baba"       "surname" => "babaei"       "rtypes" => "2"     ]   ] ] 

storeRegistration method .

I removed validation from the code and focused on the logic of the program:

public function storeRegistration(Request $request, $id, $slug = null) {      # all_participants field     $allParticipants = Conference::where('id', $id)->first()->all_participants;      $total = Session::get('total');      # user object     $user = Auth::user();      # add registration to Database     $registration = Registration::create([         'conference_id' => $id,         'main_participant_id' => $user->id,         'status' => ($total > 0) ? 'I' : 'C',     ]);      # List of all participants     $participants_list=$request->get('participant');      #add all participants to Database     foreach ($participants_list as $participant)     {         $name = ($allParticipants) ? $participant['name'] : '';         $surname = ($allParticipants) ? $participant['surname'] : '';         $participant_result = Participant::create([             'name' => $name,             'surname' => $surname,             'registration_id' => $registration->id,             'registration_type_id' => $participant['rtypes']         ]);          # save answer to Database if exist         if(isset($participant['question_id']))         {             $answer = Answer::create([                 'question_id' => $participant['question_id'],                 'participant_id' => $participant_result->id,                 'answer' => $participant['answer'],             ]);}     }      return redirect(route('user.index', ['user' => Auth::id()]).'#myTickets'); } 
Read More