diff --git a/.gitignore b/.gitignore index 380e437..259f07e 100755 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ composer.lock .*.sw[a-z] *.un~ Session.vim +/nbproject/private/ +nbproject/project.properties +nbproject/project.xml diff --git a/Datatables/Datatable.php b/Datatables/Datatable.php old mode 100755 new mode 100644 index a65fdf1..944ed02 --- a/Datatables/Datatable.php +++ b/Datatables/Datatable.php @@ -1,4 +1,5 @@ setParameters(); $this->qb = $em->createQueryBuilder(); $this->echo = $this->request['sEcho']; - $this->search = $this->request['sSearch']; + $this->search = isset($this->request['sSearch']) ? : ''; $this->offset = $this->request['iDisplayStart']; $this->amount = $this->request['iDisplayLength']; @@ -271,7 +277,7 @@ public function setParameters() if (is_numeric($this->request['iColumns'])) { $params = array(); $associations = array(); - for ($i=0; $i < intval($this->request['iColumns']); $i++) { + for ($i = 0; $i < intval($this->request['iColumns']); $i++) { $fields = explode('.', $this->request['mDataProp_' . $i]); $params[] = $this->request['mDataProp_' . $i]; $associations[] = array('containsCollections' => false); @@ -293,7 +299,8 @@ public function setParameters() * @param array Association information for a column (by reference) * @param array The column fields from dotted notation */ - protected function setRelatedEntityColumnInfo(array &$association, array $fields) { + protected function setRelatedEntityColumnInfo(array &$association, array $fields, $multipleJoin = false) + { $mdataName = implode('.', $fields); $lastField = Container::camelize(array_pop($fields)); $joinName = $this->tableName; @@ -311,24 +318,29 @@ protected function setRelatedEntityColumnInfo(array &$association, array $fields $association['containsCollections'] = true; } $metadata = $this->em->getClassMetadata( - $metadata->getAssociationTargetClass($entityName) + $metadata->getAssociationTargetClass($entityName) ); + + if ($metadata->hasField(lcfirst($lastField))) { + $association['type'] = $metadata->getTypeOfField(lcfirst($lastField)); + } + $joinName .= '_' . $this->getJoinName( - $metadata, - Container::camelize($metadata->getTableName()), - $entityName + $metadata, Container::camelize($metadata->getTableName()), $entityName ); // The join required to get to the entity in question + if ($multipleJoin) { + $joinName .= '_' . $this->count++; + } + error_log($joinName); if (!isset($this->assignedJoins[$joinName])) { $this->assignedJoins[$joinName]['joinOn'] = $joinOn; $this->assignedJoins[$joinName]['mdataColumn'] = $columnName; $this->identifiers[$joinName] = $metadata->getIdentifierFieldNames(); } - } - else { + } else { throw new Exception( - "Association '$entityName' not found ($mdataName)", - '404' + "Association '$entityName' not found ($mdataName)", '404' ); } } @@ -336,8 +348,7 @@ protected function setRelatedEntityColumnInfo(array &$association, array $fields // Check the last field on the last related entity of the dotted notation if (!$metadata->hasField(lcfirst($lastField))) { throw new Exception( - "Field '$lastField' on association '$entityName' not found ($mdataName)", - '404' + "Field '$lastField' on association '$entityName' not found ($mdataName)", '404' ); } $association['entityName'] = $entityName; @@ -352,19 +363,54 @@ protected function setRelatedEntityColumnInfo(array &$association, array $fields * @param array The association information as a reference * @param string The field name on the main entity */ - protected function setSingleFieldColumnInfo(array &$association, $fieldName) { + protected function setSingleFieldColumnInfo(array &$association, $fieldName) + { $fieldName = Container::camelize($fieldName); if (!$this->metadata->hasField(lcfirst($fieldName))) { throw new Exception( - "Field '$fieldName' not found.)", - '404' + "Field '$fieldName' not found.)", '404' ); } $association['fieldName'] = $fieldName; $association['entityName'] = $this->tableName; $association['fullName'] = $this->tableName . '.' . lcfirst($fieldName); + $association['type'] = $this->metadata->getTypeOfField(lcfirst($fieldName)); + } + + /** + * reverses and hyphens date time string + * @param strng $str + * @return string + */ + private function formatDateForDb($str) + { + + // allow for hyphens instead of slashes + $str = str_replace('-', '/', trim($str)); + + // no hyphens - nothing to do + if (false === strpos($str, '/')) { + return $str; + } + + // extract date time + $parts = preg_split('/ /', $str); + + // reverse date part + $a = explode('/', $parts[0]); + $a = array_reverse($a); + + // glue back together with hyphens + $out = implode('-', $a); + + // add time part back on + if (count($parts) > 1) { + $out .= ' ' . $parts[1]; + } + + return $out; } /** @@ -380,7 +426,7 @@ protected function getJoinName(ClassMetadata $metadata, $tableName, $entityName) // If it is self-referencing then we must avoid collisions if ($metadata->getName() == $this->metadata->getName()) { - $joinName .= "_$entityName"; + $joinName .= "_$entityName"; } return $joinName; @@ -457,10 +503,9 @@ public function setOrderBy(QueryBuilder $qb) { if (isset($this->request['iSortCol_0'])) { for ($i = 0; $i < intval($this->request['iSortingCols']); $i++) { - if ($this->request['bSortable_'.intval($this->request['iSortCol_'. $i])] == "true") { + if ($this->request['bSortable_' . intval($this->request['iSortCol_' . $i])] == "true") { $qb->addOrderBy( - $this->associations[$this->request['iSortCol_'.$i]]['fullName'], - $this->request['sSortDir_'.$i] + $this->associations[$this->request['iSortCol_' . $i]]['fullName'], $this->request['sSortDir_' . $i] ); } } @@ -477,14 +522,17 @@ public function setWhere(QueryBuilder $qb) // Global filtering if ($this->search != '') { $orExpr = $qb->expr()->orX(); - for ($i=0 ; $i < count($this->parameters); $i++) { - if (isset($this->request['bSearchable_'.$i]) && $this->request['bSearchable_'.$i] == "true") { + for ($i = 0; $i < count($this->parameters); $i++) { + if (isset($this->request['bSearchable_' . $i]) && $this->request['bSearchable_' . $i] == "true") { $qbParam = "sSearch_global_{$this->associations[$i]['entityName']}_{$this->associations[$i]['fieldName']}"; $orExpr->add($qb->expr()->like( - $this->associations[$i]['fullName'], - ":$qbParam" + $this->associations[$i]['fullName'], ":$qbParam" )); - $qb->setParameter($qbParam, "%" . $this->request['sSearch'] . "%"); + if('datetime' == $this->associations[$i]['type']) { + $qb->setParameter($qbParam, "%" . $this->formatDateForDb($this->request['sSearch']) . "%"); + } else { + $qb->setParameter($qbParam, "%" . $this->request['sSearch'] . "%"); + } } } $qb->where($orExpr); @@ -492,14 +540,13 @@ public function setWhere(QueryBuilder $qb) // Individual column filtering $andExpr = $qb->expr()->andX(); - for ($i=0 ; $i < count($this->parameters); $i++) { - if (isset($this->request['bSearchable_'.$i]) && $this->request['bSearchable_'.$i] == "true" && $this->request['sSearch_'.$i] != '') { + for ($i = 0; $i < count($this->parameters); $i++) { + if (isset($this->request['bSearchable_' . $i]) && $this->request['bSearchable_' . $i] == "true" && $this->request['sSearch_' . $i] != '') { $qbParam = "sSearch_single_{$this->associations[$i]['entityName']}_{$this->associations[$i]['fieldName']}"; $andExpr->add($qb->expr()->like( - $this->associations[$i]['fullName'], - ":$qbParam" + $this->associations[$i]['fullName'], ":$qbParam" )); - $qb->setParameter($qbParam, "%" . $this->request['sSearch_'.$i] . "%"); + $qb->setParameter($qbParam, "%" . $this->request['sSearch_' . $i] . "%"); } } if ($andExpr->count() > 0) { @@ -513,6 +560,19 @@ public function setWhere(QueryBuilder $qb) } } + /** + * Adds a manual association + * + * @param type $name - the dotted notation like in mData of the field you need adding + */ + public function addManualAssociation($name, $multipleJoin = false) + { + $newAssociation = array('containsCollections' => false); + $fields = explode('.', $name); + $this->setRelatedEntityColumnInfo($newAssociation, $fields, $multipleJoin); + $this->associations[] = $newAssociation; + } + /** * Configure joins for entity associations * @@ -522,7 +582,7 @@ public function setAssociations(QueryBuilder $qb) { foreach ($this->assignedJoins as $joinName => $joinInfo) { $joinType = isset($this->joinTypes[$joinInfo['mdataColumn']]) ? - $this->joinTypes[$joinInfo['mdataColumn']] : $this->defaultJoinType; + $this->joinTypes[$joinInfo['mdataColumn']] : $this->defaultJoinType; call_user_func_array(array($qb, $joinType . 'Join'), array( $joinInfo['joinOn'], $joinName @@ -576,7 +636,7 @@ public function setSelect(QueryBuilder $qb) * Method to execute after constructing this object. Configures the object before * executing getSearchResults() */ - public function makeSearch() + public function makeSearch() { $this->setSelect($this->qb); $this->setAssociations($this->qb); @@ -594,8 +654,9 @@ public function makeSearch() * @param array An arrray to check * @return bool true if associative */ - protected function isAssocArray(array $array) { - return (bool)count(array_filter(array_keys($array), 'is_string')); + protected function isAssocArray(array $array) + { + return (bool) count(array_filter(array_keys($array), 'is_string')); } /** @@ -607,7 +668,7 @@ public function executeSearch() $query = $this->qb->getQuery()->setHydrationMode(Query::HYDRATE_ARRAY); $items = $this->useDoctrinePaginator ? - new Paginator($query, $this->doesQueryContainCollections()) : $query->execute(); + new Paginator($query, $this->doesQueryContainCollections()) : $query->execute(); foreach ($items as $item) { if ($this->useDtRowClass && !is_null($this->dtRowClass)) { @@ -617,7 +678,7 @@ public function executeSearch() $item['DT_RowId'] = $item[$this->rootEntityIdentifier]; } // Go through each requested column, transforming the array as needed for DataTables - for ($i = 0 ; $i < count($this->parameters); $i++) { + for ($i = 0; $i < count($this->parameters); $i++) { // Results are already correctly formatted if this is the case... if (!$this->associations[$i]['containsCollections']) { continue; @@ -690,8 +751,7 @@ public function getSearchResults($resultType = '') { if (empty($resultType) || !defined('self::RESULT_' . strtoupper($resultType))) { $resultType = $this->defaultResultType; - } - else { + } else { $resultType = constant('self::RESULT_' . strtoupper($resultType)); } @@ -736,9 +796,10 @@ public function getSearchResultsResponse() public function getCountAllResults() { $qb = $this->repository->createQueryBuilder($this->tableName) - ->select('count(' . $this->tableName . '.' . $this->rootEntityIdentifier . ')'); + ->select('count(' . $this->tableName . '.' . $this->rootEntityIdentifier . ')'); + $this->setAssociations($qb); - if (!empty($this->callbacks['WhereBuilder']) && $this->hideFilteredCount) { + if (!empty($this->callbacks['WhereBuilder']) && $this->hideFilteredCount) { foreach ($this->callbacks['WhereBuilder'] as $callback) { $callback($qb); } @@ -746,7 +807,7 @@ public function getCountAllResults() return (int) $qb->getQuery()->getSingleScalarResult(); } - + /** * @return int Total query results after searches/filtering */ @@ -762,7 +823,8 @@ public function getCountFilteredResults() /** * @param object A callback function to be used at the end of 'setWhere' */ - public function addWhereBuilderCallback($callback) { + public function addWhereBuilderCallback($callback) + { if (!is_callable($callback)) { throw new \Exception("The callback argument must be callable."); } @@ -788,11 +850,12 @@ public function getAmount() public function getSearch() { - return "%" . $this->search . "%"; + return "%" . $this->search . "%"; } public function getQueryBuilder() { - return $this->qb; + return $this->qb; } + } diff --git a/Datatables/DatatableManager.php b/Datatables/DatatableManager.php old mode 100755 new mode 100644 index b7edf4f..2dd9837 --- a/Datatables/DatatableManager.php +++ b/Datatables/DatatableManager.php @@ -1,68 +1,71 @@ -doctrine = $doctrine; - $this->container = $container; - $this->useDoctrinePaginator = $useDoctrinePaginator; - } - - /** - * Given an entity class name or possible alias, convert it to the full class name - * - * @param string The entity class name or alias - * @return string The entity class name - */ - protected function getClassName($className) { - if (strpos($className, ':') !== false) { - list($namespaceAlias, $simpleClassName) = explode(':', $className); - $className = $this->doctrine->getManager()->getConfiguration() - ->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; - } - return $className; - } - - /** - * @param string An entity class name or alias - * @return object Get a DataTable instance for the given entity - */ - public function getDatatable($class) - { - $class = $this->getClassName($class); - - $metadata = $this->doctrine->getManager()->getClassMetadata($class); - $repository = $this->doctrine->getRepository($class); - - $datatable = new Datatable( - $this->container->get('request')->query->all(), - $this->doctrine->getRepository($class), - $this->doctrine->getManager()->getClassMetadata($class), - $this->doctrine->getManager(), - $this->container->get('lankit_datatables.serializer') - ); - return $datatable->useDoctrinePaginator($this->useDoctrinePaginator); - } -} - +doctrine = $doctrine; + $this->container = $container; + $this->useDoctrinePaginator = $useDoctrinePaginator; + } + + /** + * Given an entity class name or possible alias, convert it to the full class name + * + * @param string The entity class name or alias + * @return string The entity class name + */ + protected function getClassName($className) { + if (strpos($className, ':') !== false) { + list($namespaceAlias, $simpleClassName) = explode(':', $className); + $className = $this->doctrine->getManager($this->manager)->getConfiguration() + ->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; + } + return $className; + } + + /** + * @param string An entity class name or alias + * @return object Get a DataTable instance for the given entity + */ + public function getDatatable($class, $manager = "default") + { + $this->manager = $manager; + $class = $this->getClassName($class); + + $datatable = new Datatable( + $this->container->get('request')->query->all(), + $this->doctrine->getManager($this->manager)->getRepository($class), + $this->doctrine->getManager($this->manager)->getClassMetadata($class), + $this->doctrine->getManager($this->manager), + $this->container->get('lankit_datatables.serializer') + ); + return $datatable->useDoctrinePaginator($this->useDoctrinePaginator); + } +} +