Laravel-Backpack / CRUD

Build custom admin panels. Fast!
https://backpackforlaravel.com
MIT License
3.08k stars 886 forks source link

Laravel Backpack FIlter Not Called #701

Closed aryamaharta closed 7 years ago

aryamaharta commented 7 years ago

I'm currently working on project using laravel backpack and try to use crud filter on the data. But the function that filtering data not being called after dropdown selected. Here is my

$this->crud->addFilter([ // select2 filter
            'name' => 'id_periode',
            'type' => 'dropdown',
            'label'=> 'Periode'
            ], function() {
                return \App\Models\Periode::all()->pluck('periode', 'id')->toArray();
            }, function($value) {
                dd($value);
                $this->crud->addClause('where', 'id_periode', $value);
        });

Is there something wrong with my code or am i missing something ?

lloy0076 commented 7 years ago

It's working for me - what do you see in the developer tools? Any JavasScript errors?

tabacitu commented 7 years ago

@aryamaharta - also tested this and syntax seems fine, so I'll close the issue, as it doesn't seem to be a bug in the software. But please reply to get to the bottom of this.

Cheers!

axelzuzek commented 6 years ago

Same problem here. We deployed the application from staging to production, same environment (aws server clones) the filters are called in staging environment but not at the production system. Everything else works fine. Any ideas?

tabacitu commented 6 years ago

Sorry @axelzuzek , no idea, no. There shouldn't be any difference between staging and production. Filters should work fine. So the AJAX request is sent&received but the filter is not applied? Can you check if the POST parameters have been sent in the AJAX request?

axelzuzek commented 6 years ago

Yes I debuged this already, the params are present, also the fulltext search is working but non of the filters defined in the crudcontroller setup are called.

axelzuzek commented 6 years ago

the filters seem to be set too:

public function search()
    {

        print_r($this->crud->filters());
>>>
Backpack\CRUD\PanelTraits\FiltersCollection Object
(
    [items:protected] => Array
        (
            [0] => Backpack\CRUD\PanelTraits\CrudFilter Object
                (
                    [name] => active
                    [type] => toggleArchive
                    [label] => Aktiv
                    [placeholder] => 
                    [values] => 
                    [options] => Array
                        (
                            [type] => toggleArchive
                            [name] => active
                            [label] => Aktiv
                        )

                    [currentValue] => true
                    [view] => crud::filters.toggleArchive
                )

            [1] => Backpack\CRUD\PanelTraits\CrudFilter Object
                (
                    [name] => archived
                    [type] => toggleArchive
                    [label] => Archiviert
                    [placeholder] => 
                    [values] => 
                    [options] => Array
                        (
                            [type] => toggleArchive
                            [name] => archived
                            [label] => Archiviert
                        )

                    [currentValue] => 
                    [view] => crud::filters.toggleArchive
                )

        )

)
tabacitu commented 6 years ago

Hmm... Well them I'm pretty sure you make some mistake in your filtering code. In the closure, I mean.

You can see if that's the case by moving the code that actually does the filtering (whatever is inside the closure) outside the addFilter() method, with some manual data.

Ex:

$this->crud->addFilter([ // select2 filter
            'name' => 'id_periode',
            'type' => 'dropdown',
            'label'=> 'Periode'
            ], function() {
                return \App\Models\Periode::all()->pluck('periode', 'id')->toArray();
            }, function($value) {
-                $this->crud->addClause('where', 'id_periode', $value);
        });
+ $this->crud->addClause('where', 'id_periode', '2');
axelzuzek commented 6 years ago

The source of the problem: the https termination of the production system is done at the loadbalancer, the laravel server only gets http requests instead of https

The Filters Trait checks:

        $route = $this->route;

        switch ($this->request->url()) {
            case url($this->route):
                if ($this->request->getMethod() == 'POST' ||
                    $this->request->getMethod() == 'PATCH') {
                    return false;
                }
                return true;
                break;

url($this->route.'/search') => https://domain/admin/ad/search $this->request->url() => http://domain/admin/ad/search

pxpm commented 6 years ago

It might have something to do with TrustedProxies ?

trusted proxies documentation

Can you give it a look ?

Thanks

axelzuzek commented 6 years ago

Thx that solved the problem for me:

Solution (working for Laravel >= 5.5)

1) Configure App\Http\Middleware\TrustProxies;

protected $proxies = '**';
protected $headers = Request::HEADER_X_FORWARDED_ALL;

(For older Laravel Versions see https://github.com/fideloper/TrustedProxy)

2) AppServiceProvider function boot() Make sure that \URL::forceScheme('https') must NOT be set.

tabacitu commented 6 years ago

Glad you figured it out @axelzuzek . Thanks for the solution @pxpm .

ziming commented 6 years ago

Is there a way to do this while forcing URL::forceScheme('https') ?

sinallcom commented 6 years ago

Thanks, @axelzuzek! Worked for me with protected $proxies = '**'; and SET(!!!) \URL::forceScheme('https') Laravel 5.6

jankapusta commented 5 years ago

Same problem here. We deployed the application from staging to production, same environment (aws server clones) the filters are called in staging environment but not at the production system. Everything else works fine. Any ideas?

aceArt-GmbH commented 5 years ago

Is there a way to do this while forcing URL::forceScheme('https') ?

We must have URL::forcmeScheme('https') acitve and we have the same problem with the filters as described.

The solution for us was to overwrite the Trait from Backpack. It works by adding an alias at the register-method of your AppServiceProvider (\App\Providers\AppServiceProvider.php. If you don't have this class you can create it with artisan).

/**
   * Register any application services.
   *
   * @return void
   */
   public function register()
   {
      $this->app->booting(function() {
         $loader = AliasLoader::getInstance();
         $loader->alias('Backpack\CRUD\PanelTraits\Filters', 'App\Vendor\Backpack\CRUD\PanelTraits\Filters');
      });
   }

Overwrite the trait with this functions:

 /**
     * Determine if the current CRUD action is a list operation (using standard or ajax DataTables).
     * @return bool
     */
    public function doingListOperation()
    {
        $route = $this->route;
        switch ($this->removeProtocol($this->request->url())) {
            case $this->removeProtocol(url($this->route)):
                if ($this->request->getMethod() == 'POST' ||
                    $this->request->getMethod() == 'PATCH') {
                    return false;
                }

                return true;
                break;

            case $this->removeProtocol(url($this->route.'/search')):
                return true;
                break;

            default:
                return false;
                break;
        }
    }

   public function removeProtocol($url) {
      $disallowed = array('http://', 'https://');
      foreach($disallowed as $d) {
         if(strpos($url, $d) === 0) {
            return str_replace($d, '', $url);
         }
      }
      return $url;
   }

It removes the protocol from the urls. Now it works for any protocol.

ThomasVaRo commented 5 years ago

Is there a way to do this while forcing URL::forceScheme('https') ?

We must have URL::forcmeScheme('https') acitve and we have the same problem with the filters as described.

The solution for us was to overwrite the Trait from Backpack. It works by adding an alias at the register-method of your AppServiceProvider (\App\Providers\AppServiceProvider.php. If you don't have this class you can create it with artisan).

/**
   * Register any application services.
   *
   * @return void
   */
   public function register()
   {
      $this->app->booting(function() {
         $loader = AliasLoader::getInstance();
         $loader->alias('Backpack\CRUD\PanelTraits\Filters', 'App\Vendor\Backpack\CRUD\PanelTraits\Filters');
      });
   }

Overwrite the trait with this functions:

 /**
     * Determine if the current CRUD action is a list operation (using standard or ajax DataTables).
     * @return bool
     */
    public function doingListOperation()
    {
        $route = $this->route;
        switch ($this->removeProtocol($this->request->url())) {
            case $this->removeProtocol(url($this->route)):
                if ($this->request->getMethod() == 'POST' ||
                    $this->request->getMethod() == 'PATCH') {
                    return false;
                }

                return true;
                break;

            case $this->removeProtocol(url($this->route.'/search')):
                return true;
                break;

            default:
                return false;
                break;
        }
    }

   public function removeProtocol($url) {
      $disallowed = array('http://', 'https://');
      foreach($disallowed as $d) {
         if(strpos($url, $d) === 0) {
            return str_replace($d, '', $url);
         }
      }
      return $url;
   }

It removes the protocol from the urls. Now it works for any protocol.

This worked for us, thanks!

FrittenKeeZ commented 5 years ago

This is by far the most annoying bug to fix in Backpack. I followed @aceArt-GmbH's example, but had to copy/paste the entire trait to make it work. I also reduced the trim method to a simple preg replace:

/**
 * Strip http/s protocols from URL.
 *
 * @param string $url
 *
 * @return string
 */
public function removeProtocol(string $url) {
    return preg_replace('%^https?://%i', '', $url);
}
aceArt-GmbH commented 5 years ago

We also had to copy/paste the entire code to make it work. I only posted the changes I made. It is not an optimal solution but it's one for now. Please let us know when it will be fixed in the core. I would like to remove that code. @FrittenKeeZ i like your function better :)

tabacitu commented 5 years ago

@FrittenKeeZ , @aceArt-GmbH , doesn't this fix it for you, in a more general way? https://github.com/Laravel-Backpack/CRUD/issues/701#issuecomment-402462045

As far as I understand this, the problem is that url() is not outputting the right protocol for you guys. If so, this problem could happen elsewhere in your app too. Not just here.

FrittenKeeZ commented 5 years ago

@tabacitu the problem is that the proxy sends the request as http, but the base URL does have https, so when you do url('whatever') it will have https as protocol - so essentially you'll end up comparing https://mydomain.com/whatever with http://mydomain.com/whatever. For it to work without the overrides, the base URL needs to be with http, but that will break all links and assets requests through https - so no, the comment you refer to won't fix it unfortunately.