src/Diplix/KMGBundle/DataTables/DataTablesHelper.php line 274

Open in your IDE?
  1. <?php
  2. namespace Diplix\KMGBundle\DataTables;
  3. use Diplix\KMGBundle\DataTables\Expr\ExprStub;
  4. use Doctrine\ORM\Query\Expr\Andx;
  5. use Doctrine\ORM\Query\Expr\Orx;
  6. use Doctrine\ORM\QueryBuilder;
  7. use Symfony\Component\HttpFoundation\Request;
  8. use Symfony\Component\HttpFoundation\Response;
  9. /**
  10.  * (c) C.Schmidhuber, Version Nov2018
  11.  * Class DataTablesHelper
  12.  * @package Diplix\KMGBundle\DataTables
  13.  */
  14. class DataTablesHelper
  15. {
  16.     const T_RAW "";
  17.     const T_BUTTONS "action_buttons";
  18.     const T_SELECTOR "selectorcheckbox";
  19.     const T_CSSICON "cssicon";
  20.     const T_COMMENT "comment";
  21.     // the default setup for a single column
  22.     public static $defaultSetup = array(
  23.         "fieldName"=>null,  // the name of the database field
  24.         "caption"=>null,    // the head title for the html table
  25.         "virtual"=>false,   // set to true to exclude from default filter handling
  26.         "searchable"=>true// allow searching
  27.         "searchWithLike"=>true// use like for field-specific search
  28.         "sortable"=>true,   // allow sorting for the column
  29.         "visible"=>true,    // show/hide columns,
  30.         "footer"=>false,    // add .nofooter class to footer cell
  31.         "type"=>self::T_RAW,// display type
  32.         "headStyle"=>"",    // additional styles for the th-tag
  33.     );
  34.     /** @var array Our complete setup*/
  35.     protected $columnSetup = array(
  36.         "columns" => array(),
  37.         "ajaxUrl"=>null,
  38.         "ajaxData"=>null,
  39.         "ajaxType"=>null// GET / POST
  40.         "deferLoading"=>null,
  41.         "defaultSorting"=>null,
  42.         "searching"=>true,      // https://datatables.net/reference/option/searching
  43.         "autoCaptionPrefix"=>"th.",
  44.         "addTableClass" => "",
  45.         "addTableStyle" => "",
  46.         'stateSave' => false,
  47.         'stateContext' => null,
  48.     );
  49.     /**
  50.      * @var QueryBuilder
  51.      */
  52.     protected $queryBuilder;
  53.     protected $additionalOrX = [];
  54.     protected $additionalAndX = [];
  55.     protected $additionalParams = [];
  56.     protected $additionalOrdering = [];
  57.     protected $primaryOrdering = [];
  58.     /**
  59.      * DataTablesHelper constructor.
  60.      * @param array $columSetup see self::$defaultSetup for parameters
  61.      * @throws \Exception
  62.      */
  63.     public function __construct(array $columnSetup,$options=array())
  64.     {
  65.         // take column settings
  66.         foreach ($columnSetup as $idx=>$arr)
  67.         {
  68.             // check for unknown fields
  69.             if (count(array_diff_key($arr,self::$defaultSetup))>0)
  70.             {
  71.                 throw new \Exception(sprintf("columnSetup[%d] includes unknown fields",$idx));
  72.             }
  73.             // diverging default values for special types
  74.             if (isset($arr["type"]))
  75.             {
  76.                 if ($arr["type"]==self::T_BUTTONS$arr array_merge(array("sortable"=>false,"searchable"=>false),$arr);
  77.                 if ($arr["type"]==self::T_SELECTOR$arr array_merge(array("sortable"=>false,"searchable"=>false),$arr);
  78.                 if ($arr["type"]==self::T_COMMENT)  $arr array_merge(array("searchable"=>false),$arr);
  79.             }
  80.             // fill missing fields with default values
  81.             $this->columnSetup["columns"][$idx] = array_merge(self::$defaultSetup,$arr);
  82.         }
  83.         // take other settings
  84.         if (count($options)>0)
  85.         foreach ($options as $k=>$o)
  86.         {
  87.             if (array_key_exists($k,$this->columnSetup))
  88.             {
  89.                 $this->columnSetup[$k] = $o;
  90.             }
  91.             else
  92.             {
  93.                 throw new \Exception(sprintf("Unknown parameter: %s",$k));
  94.             }
  95.         }
  96.     }
  97.     public function setQueryBuilder(QueryBuilder $queryBuilder)
  98.     {
  99.         $this->queryBuilder $queryBuilder;
  100.     }
  101.     public function processRequest(Request $request)
  102.     {
  103.         $req array_merge($request->query->all() , $request->request->all());
  104.         $this->addRequestParametersToDbQuery($this->queryBuilder,$this->columnSetup["columns"],$req);
  105.     }
  106.     public function getColumnSetup()
  107.     {
  108.         return $this->columnSetup;
  109.     }
  110.     protected function makeCaptionFromFieldName($fieldName)
  111.     {
  112.         $caption "";
  113.         // remove entity reference part before the dot
  114.         $dp strrpos($fieldName,".");
  115.         if ($dp!==false$fieldName substr($fieldName,$dp+1);
  116.         // transform
  117.         $len strlen($fieldName);
  118.             for ($i 0$i $len; ++$i) {
  119.                 if (ctype_upper($fieldName[$i])) {
  120.                     $caption .= ' '.$fieldName[$i];
  121.                 } else {
  122.                     $caption .= strtolower($fieldName[$i]);
  123.                 }
  124.             }
  125.         return ucfirst($caption);
  126.     }
  127.     public function addOr($expr,$params=[])
  128.     {
  129.         if (!is_array($expr)) $expr = [ $expr ];
  130.         foreach ($expr as $x)
  131.         {
  132.             $this->additionalOrX[]= $x;
  133.         }
  134.         $this->additionalParams array_merge($this->additionalParams,$params);
  135.     }
  136.     public function addAnd($expr,$params=[])
  137.     {
  138.         if (!is_array($expr)) $expr = [ $expr ];
  139.         foreach ($expr as $x)
  140.         {
  141.             $this->additionalAndX[]= $x;
  142.         }
  143.         $this->additionalParams array_merge($this->additionalParams,$params);
  144.     }
  145.     public function addOrderBy($expr,$order)
  146.     {
  147.         $this->additionalOrdering[$expr] = $order;
  148.     }
  149.     public function addPrimaryOrderBy($expr,$order)
  150.     {
  151.         $this->primaryOrdering[$expr] = $order;
  152.     }
  153.     /**
  154.      * @param \Doctrine\ORM\QueryBuilder $cb
  155.      * @param $knownColumns array
  156.      * @param $req array array containing the request parameters ($request->query->all())
  157.      */
  158.     protected function addRequestParametersToDbQuery(\Doctrine\ORM\QueryBuilder $cb$knownColumns$req )
  159.     {
  160.         $dtColumns = (array)$req['columns'];    // the columns known to datatables js
  161.         $params = array();
  162.         // filtering (global search)
  163.         $orLikes = array();
  164.         if ((isset($req['search']['value']))&&(strlen(trim($req['search']['value']))>0))
  165.         {
  166.             foreach ($dtColumns as $k=>$e)
  167.             {
  168.                 if ($knownColumns[$k]["virtual"]) continue; // virtual fields have to processed manually
  169.                 if (($e["searchable"]=="true")&&($knownColumns[$k]["searchable"]))
  170.                 {
  171.                     $orLikes[] =  $cb->expr()->like($knownColumns[$k]["fieldName"], ":orlike".$k );
  172.                     $params["orlike".$k] = "%".addcslashes($req['search']['value'], "%_")."%";
  173.                 }
  174.             }
  175.         }
  176.         $orLikes2 array_merge$orLikes $this->additionalOrX  );
  177.         if (count($orLikes2)>0$cb->andWhere(new Orx($orLikes2));
  178.         // filtering (field-wise search, not taking into account the actual field type)
  179.         $andLikes = array();
  180.         foreach($dtColumns as $k=>$e)
  181.         {
  182.             if ($knownColumns[$k]["virtual"]) continue; // virtual fields have to processed manually
  183.             if (($e['searchable']=="true") && ($knownColumns[$k]["searchable"]) && (trim($e['search']['value'])!=""))
  184.             {
  185.                 if ($knownColumns[$k]["searchWithLike"])
  186.                 {
  187.                     $andLikes[] = $cb->expr()->like($knownColumns[$k]["fieldName"], ":andlike".$k );
  188.                     $params["andlike".$k] = "%".addcslashes($e['search']['value'], "%_")."%";
  189.                 }
  190.                 else
  191.                 {
  192.                 $andLikes[]= $cb->expr()->eq($knownColumns[$k]["fieldName"], ":andlike".$k);
  193.                 $params["andlike".$k] = $e['search']['value'];
  194.             }
  195.         }
  196.         }
  197.         $andLikes2 array_merge($andLikes$this->additionalAndX);
  198.         if (count($andLikes2)>0$cb->andWhere(new Andx($andLikes2));
  199.         foreach ( array_merge($params,$this->additionalParams) as $k=>$e)
  200.         {
  201.             $cb->setParameter($k,$e);
  202.         }
  203.         // ordering
  204.         foreach ($this->primaryOrdering as $expr => $order)
  205.         {
  206.             $cb->addOrderBy($expr,$order);
  207.         }
  208.         if (isset($req['order']))
  209.         {
  210.             $order = (array)$req['order'];
  211.             if (count($order)>0)
  212.             foreach ($order as $o)
  213.             if ( (array_key_exists($o["column"],$knownColumns)) && ($knownColumns[$o["column"]]["sortable"]) )
  214.             {
  215.                 if ($knownColumns[$o['column']]["virtual"]) continue; // virtual fields have to processed manually
  216.                 $cb->addOrderBy$knownColumns[$o['column']]["fieldName"] , $o['dir']);
  217.             }
  218.         }
  219.         foreach ($this->additionalOrdering as $expr => $order)
  220.         {
  221.             $cb->addOrderBy($expr,$order);
  222.         }
  223.          // paging
  224.         if (isset($req['start'])) $cb->setFirstResult($req['start']);
  225.         if ( (isset($req['length'])) && ($req['length']>0)) $cb->setMaxResults($req['length']);
  226.     }
  227.     public function getRawColumnOrder($req)
  228.     {
  229.         $raw = [];
  230.         $knownColumns $this->columnSetup["columns"];
  231.         if (isset($req['order']))
  232.         {
  233.             $order = (array)$req['order'];
  234.             if (count($order) > 0)
  235.                 foreach ($order as $o)
  236.                     if ((array_key_exists($o["column"], $knownColumns)))
  237.                     {
  238.                         $raw[$o['column']] = $o["dir"];
  239.                     }
  240.         }
  241.         return $raw;
  242.     }
  243.     protected function getUniqueName()
  244.     {
  245.         return "param".rand(1000,9999);
  246.     }
  247.     public function addSimpleFilter($colIndex,$req,$order,$orX=[],$orderExpr,$weak=false,$includedToGlobal=true)
  248.     {
  249.         if (count($orX)>0)
  250.         {
  251.             // column specific search means the search term has to be there (AND)
  252.             if ($colIndex>=0)
  253.             {
  254.                 $column $req["columns"][$colIndex];
  255.                 $reqFilter = (isset($column["search"]["value"]) ? $column["search"]["value"] : "");
  256.                 if ($reqFilter!="")
  257.                 {
  258.                     if ($weak$reqFilter "%".addcslashes($reqFilter"%_")."%";
  259.                     $u $this->getUniqueName();
  260.                     $parts = [];
  261.                     /** @var ExprStub[] $orX  */
  262.                     for ($i=0;$i<count($orX);$i++) $parts[]= $orX[$i]->resurrect(":".$u);
  263.                     $this->addAnd(new OrX($parts), [$u=>$reqFilter]);
  264.                 }
  265.             }
  266.             if ($includedToGlobal)
  267.             {
  268.                 // global search filter just looks for matches everywhere (OR)
  269.                 $optFilter $req["search"]["value"];
  270.                 if ($optFilter!="")
  271.                 {
  272.                     if ($weak$optFilter "%".addcslashes($optFilter"%_")."%";
  273.                     $u $this->getUniqueName();
  274.                     $parts = [];
  275.                     /** @var ExprStub[] $orX  */
  276.                     for ($i=0;$i<count($orX);$i++) $parts[] = $orX[$i]->resurrect(":".$u);
  277.                     $this->addOr(new OrX($parts), [$u=>$optFilter]);
  278.                 }
  279.             }
  280.         }
  281.         if (array_key_exists($colIndex,$order))
  282.         {
  283.             $this->addOrderBy($orderExpr,$order[$colIndex]);
  284.         }
  285.     }
  286.     public function output($draw,$totalRecordCount,$filteredRecordCount, array $data, ?Request $request$httpResponseCode Response::HTTP_OK ): Response
  287.     {
  288.         $output = array(
  289.             "draw" => $draw,
  290.             "recordsTotal"=>$totalRecordCount,
  291.             "recordsFiltered"=>$filteredRecordCount,
  292.             "data" => $data// TODO: security update -- $this->filterTableDataResult($data)
  293.         );
  294.         $resp = new Response(
  295.             json_encode($outputJSON_THROW_ON_ERROR),
  296.             $httpResponseCode,
  297.             array(
  298.                 'content-type'=> 'text/plain',// Use plain browser-wide and let the js assume that the format will be json - this seems to work with every browser
  299.                 'cache-control'=>'no-store, no-cache, must-revalidate',
  300.                 'pragma'=>'no-cache'));
  301.         if ($request!==null)
  302.         {
  303.             $resp->prepare($request);
  304.         }
  305.         $resp->setCache(array('last_modified' => new \DateTime()));
  306.         $resp->expire();
  307.         return $resp;
  308.     }
  309. }