From a1711afecccd5f4ccb16cd7d15006b38e68b7fc0 Mon Sep 17 00:00:00 2001 From: boris Date: Wed, 4 Dec 2024 00:21:29 +0000 Subject: [PATCH] (fix): holy shit filters and searching actually works. fix before was completely schizo i think i was just zynbrained --- Models/FacilityDataSet.php | 296 ++++++++++++++++--------------------- paginationcontroller.php | 144 +++++------------- 2 files changed, 161 insertions(+), 279 deletions(-) diff --git a/Models/FacilityDataSet.php b/Models/FacilityDataSet.php index edde6b2..cdbdb06 100644 --- a/Models/FacilityDataSet.php +++ b/Models/FacilityDataSet.php @@ -2,10 +2,12 @@ require_once ('Database.php'); require_once ('FacilityData.php'); -class FacilityDataSet { +class FacilityDataSet +{ protected $_dbHandle, $_dbInstance; - public function __construct() { + public function __construct() + { $this->_dbInstance = Database::getInstance(); $this->_dbHandle = $this->_dbInstance->getDbConnection(); } @@ -14,7 +16,8 @@ class FacilityDataSet { * @param $data * @return bool */ - public function addFacility($data) : bool { + public function addFacility($data): bool + { $sqlQuery = " INSERT INTO ecoFacilities (title, @@ -45,7 +48,8 @@ class FacilityDataSet { return !($stmt->rowCount()); } - public function deleteFacility($id) : bool { + public function deleteFacility($id): bool + { $sqlQuery = "DELETE FROM ecoFacilities WHERE id = ?"; $stmt = $this->_dbHandle->prepare($sqlQuery); $stmt->setFetchMode(\PDO::FETCH_ASSOC); @@ -54,206 +58,158 @@ class FacilityDataSet { return !($stmt->rowCount() == 0); } - /** - * @param $filterArray - * @param $sortArray - * @return array - * Function to allow fetching of facility data. Data objects are created and held in an array - * Count of rows for pagination returned alongside data objects. - */ + public function fetchAll($filterArray, $sortArray): array { - $direction = ''; - // Set direction, if not found in array, set to ascending. - (in_array('desc', $sortArray)) ? $direction = 'DESC' : $direction = 'ASC'; - // Default to title - // Note: I am very sorry, i am well aware this is horrible, im running out of time - $sortBy = 1; - switch (array_search('desc', $sortArray) ?? array_search('asc', $sortArray)) { - case (0) : - $sortBy = 'ecoFacilityStatus.statusComment'; - break; - case (1) : - $sortBy = 'ecoFacilities.title'; - break; - case (2) : - $sortBy = 'ecoCategories.name'; - break; - case (3) : - $sortBy = 'ecoFacilities.description'; - break; - case (4) : - $sortBy = 'ecoFacilities.streetName'; - break; - case (5) : - $sortBy = 'ecoFacilities.county'; - break; - case (6) : - $sortBy = 'ecoFacilities.town'; - break; - case (7) : - $sortBy = 'ecoFacilities.postcode'; - break; - case (8) : - $sortBy = 'ecoUser.username'; - break; - } + // Define columns for filtering and sorting + $filterColumns = [ + 0 => 'ecoFacilityStatus.statusComment', + 1 => 'ecoFacilities.title', + 2 => 'ecoCategories.name', + 3 => 'ecoFacilities.description', + 4 => 'ecoFacilities.streetName', + 5 => 'ecoFacilities.county', + 6 => 'ecoFacilities.town', + 7 => 'ecoFacilities.postcode', + 8 => 'ecoUser.username' + ]; - /** - * COUNT(DISTINCT ecoFacilities.id) required due to multiple status comments possible. - */ - $sqlCount = "SELECT COUNT(DISTINCT ecoFacilities.id) AS total FROM ecoFacilities"; + $sortColumns = [ + 0 => 'ecoFacilityStatus.statusComment', + 1 => 'ecoFacilities.title', + 2 => 'ecoCategories.name', + 3 => 'ecoFacilities.description', + 4 => 'ecoFacilities.streetName', + 5 => 'ecoFacilities.county', + 6 => 'ecoFacilities.town', + 7 => 'ecoFacilities.postcode', + 8 => 'ecoUser.username' + ]; - /** - * DISTINCT used again for prior reasoning, although data is handled properly regardless later. - * GROUP_CONCAT is used to handle multiple status comments under one facility. Without this, DISTINCT - * drops the additional comment. - */ - $sqlData = "SELECT DISTINCT ecoFacilities.id, - title, + // Validate and select the filter column + $selectedFilterColumn = $filterColumns[$filterArray['category']] ?? 'ecoFacilities.title'; + // Validate and select the sort column + $selectedSortColumn = $sortColumns[$sortArray['sort']] ?? 'ecoFacilities.title'; + // Validate sort direction + $direction = strtolower($sortArray['dir']) === 'desc' ? 'DESC' : 'ASC'; + // Base query for filtering and sorting + $baseQuery = " + FROM ecoFacilities + LEFT JOIN ecoCategories ON ecoCategories.id = ecoFacilities.category + LEFT JOIN ecoUser ON ecoUser.id = ecoFacilities.contributor + LEFT JOIN ecoFacilityStatus ON ecoFacilityStatus.facilityid = ecoFacilities.id + WHERE {$selectedFilterColumn} LIKE :term + "; + + + // Query to count total results + $countQuery = "SELECT COUNT(DISTINCT ecoFacilities.id) AS total {$baseQuery}"; + + // Query to fetch filtered and sorted results + $dataQuery = " + SELECT DISTINCT ecoFacilities.id, + ecoFacilities.title, GROUP_CONCAT(ecoFacilityStatus.statusComment, ', ') AS status, ecoCategories.name AS category, - description, - houseNumber, - streetName, - county, - town, - postcode, - lng, - lat, - ecoUser.username AS contributor - FROM ecoFacilities"; + ecoFacilities.description, + ecoFacilities.houseNumber, + ecoFacilities.streetName, + ecoFacilities.county, + ecoFacilities.town, + ecoFacilities.postcode, + ecoFacilities.lng, + ecoFacilities.lat, + ecoUser.username AS contributor + {$baseQuery} + GROUP BY ecoFacilities.id, ecoFacilities.title, ecoCategories.name, + ecoFacilities.description, ecoFacilities.streetName, + ecoFacilities.county, ecoFacilities.town, ecoFacilities.postcode, + ecoUser.username + ORDER BY {$selectedSortColumn} {$direction}; + "; + $filterArray['term'] = '%' . $filterArray['term'] . '%' ?? '%'; + var_dump($filterArray); + // Prepare and execute the count query + $countStmt = $this->_dbHandle->prepare($countQuery); + $countStmt->bindValue(':term', $filterArray['term'], PDO::PARAM_STR); + $countStmt->execute(); + $totalResults = (int)$countStmt->fetchColumn(); - /** - * ? Parameters used here over named parameters so logic can be modular, more - * columns can be added in the future - */ - $sqlWhere = " - LEFT JOIN ecoCategories ON ecoCategories.id = ecoFacilities.category - LEFT JOIN ecoUser ON ecoUser.id = ecoFacilities.contributor - LEFT JOIN ecoFacilityStatus ON ecoFacilityStatus.facilityid = ecoFacilities.id - WHERE (ecoFacilityStatus.statusComment LIKE ? OR ? IS NULL) - AND ecoFacilities.title LIKE ? - AND ecoCategories.name LIKE ? - AND ecoFacilities.description LIKE ? - AND ecoFacilities.streetName LIKE ? - AND ecoFacilities.county LIKE ? - AND ecoFacilities.town LIKE ? - AND ecoFacilities.postcode LIKE ? - AND ecoUser.username LIKE ? - "; + // Prepare and execute the data query + $dataStmt = $this->_dbHandle->prepare($dataQuery); + $dataStmt->bindValue(':term', $filterArray['term'], PDO::PARAM_STR); + $dataStmt->execute(); - /** - * GROUP BY required to ensure status comments are displayed under the same ID - * Named parameters used here for prior reasoning, columns can be added above without - * effecting the bindIndex. - * I unfortunately HAVE to do the ORDER BY statement like this, since PDO doesn't allow - * binding of column names to placeholders for some reason. I could have used the column - * order and passed an integer, but its too much hassle and I have enough sanitisation, - * this is fine. - */ - $sqlLimits = " - GROUP BY ecoFacilities.id, ecoFacilities.title, ecoCategories.name, ecoFacilities.description, ecoFacilities.streetName, - ecoFacilities.county, ecoFacilities.town, ecoFacilities.postcode, ecoUser.username - ORDER BY {$sortBy} {$direction} - ;"; - - // Concatenate query snippets for data and row count - $dataQuery = $sqlData . $sqlWhere . $sqlLimits; - $countQuery = $sqlCount . $sqlWhere . ";"; - - // Prepare, bind and execute data query - $stmt = $this->populateFields($dataQuery, $filterArray, $sortBy, $direction); - $stmt->execute(); - - // Create data objects + // Fetch results into FacilityData objects $dataSet = []; - while ($row = $stmt->fetch()) { + while ($row = $dataStmt->fetch()) { $dataSet[] = new FacilityData($row); } - // Prepare, bind then execute count query - $stmt = $this->populateFields($countQuery, $filterArray, null, null); - $stmt->execute(); - $totalCount = $stmt->fetch()['total']; - return [ 'dataset' => $dataSet, - 'count' => $totalCount - ]; + 'count' => $totalResults + ]; } /** * @param $sqlQuery * @param $filterArray - * @param $sortBy - * @param $direction * @return false|PDOStatement * Function for fetchAll() to de-dupe code. Performs binding on PDO statements to facilitate * filtering of facilities. Returns a bound PDO statement. */ - private function populateFields($sqlQuery, $filterArray, $sortBy, $direction) - { - $stmt = $this->_dbHandle->prepare($sqlQuery); - // Ensures only one value is returned per column name - $stmt->setFetchMode(\PDO::FETCH_ASSOC); +// private function populateFields($sqlQuery, $filterArray, $sortBy, $direction) +// { +// $stmt = $this->_dbHandle->prepare($sqlQuery); +// $stmt->setFetchMode(\PDO::FETCH_ASSOC); +// +// // Initialize index for binding +// $bindIndex = 1; +// +// // Bind statusComment filter, required due to comments not being so. +// $statusComment = !empty($filterArray[0]) ? "%" . $filterArray[0] . "%" : null; +// $stmt->bindValue($bindIndex++, $statusComment ?? "%", \PDO::PARAM_STR); // First ? +// $stmt->bindValue($bindIndex++, $statusComment, $statusComment === null ? \PDO::PARAM_NULL : \PDO::PARAM_STR); // Second ? +// +// // Bind other filters +// for ($i = 1; $i <= 8; $i++) { // Assuming 8 other filters +// $value = !empty($filterArray[$i]) ? "%" . $filterArray[$i] . "%" : "%"; +// $stmt->bindValue($bindIndex++, $value, \PDO::PARAM_STR); +// } +// return $stmt; +// } - // Initialize index for binding - $bindIndex = 1; - - // Bind statusComment filter, required due to comments not being so. - $statusComment = !empty($filterArray[0]) ? "%" . $filterArray[0] . "%" : null; - $stmt->bindValue($bindIndex++, $statusComment ?? "%", \PDO::PARAM_STR); // First ? - $stmt->bindValue($bindIndex++, $statusComment, $statusComment === null ? \PDO::PARAM_NULL : \PDO::PARAM_STR); // Second ? - // So i worked on trying to get this to work for 30 minutes and it turns out you - // can never bind column name values to placeholders, and must use column orders - // as integers..... what + // So i worked on trying to get this to work for 30 minutes and it turns out you + // can never bind column name values to placeholders, and must use column orders + // as integers..... what // if(isset($sortBy) && isset($direction)) { // $stmt->bindValue(':sortBy', $sortBy, \PDO::PARAM_STR); // $stmt->bindValue(':direction', $direction, \PDO::PARAM_STR); // } + private function populateFields($sqlQuery, $filterArray) + { + $stmt = $this->_dbHandle->prepare($sqlQuery); + $stmt->setFetchMode(\PDO::FETCH_ASSOC); + + $bindIndex = 1; + + // Bind statusComment (two placeholders required) + $statusComment = $filterArray[0] ?? '%'; + $stmt->bindValue($bindIndex++, $statusComment, \PDO::PARAM_STR); + $stmt->bindValue($bindIndex++, $statusComment, \PDO::PARAM_STR); // Bind other filters - for ($i = 1; $i <= 8; $i++) { // Assuming 8 other filters - $value = !empty($filterArray[$i]) ? "%" . $filterArray[$i] . "%" : "%"; + for ($i = 1; $i < count($filterArray); $i++) { + $value = $filterArray[$i] ?? '%'; + print_r($i . ":" . $value . "||\n"); $stmt->bindValue($bindIndex++, $value, \PDO::PARAM_STR); } + + // Debugging + //$stmt->debugDumpParams(); return $stmt; } - // UNUSED REPLACED - public function setFilterUri($term, $category) - { - $uri = $_SERVER['REQUEST_URI']; - $uriComp = parse_url($uri); - $params = []; - - // Parse existing query parameters - if (isset($uriComp['query'])) { - parse_str($uriComp['query'], $params); - } else { - $params = array(); - } - // Avoid unnecessary redirection if the filter is already correct - if ((isset($params['category']) && $params['category'] === $category) && (isset($params['term']) && $params['term'] === $term)) { - exit; // Do nothing if filter already applied - } - - // Update the 'page' parameter - $params['category'] = $category; - $params['term'] = $term; - - // Rebuild the query string - $newUri = http_build_query($params); - var_dump($newUri); - - // Redirect to the updated URI - // Use the current path or root - return - [ - 'newUri' => $newUri, - 'path' => $uriComp['path'] ?? '/' - ]; } - } diff --git a/paginationcontroller.php b/paginationcontroller.php index 75847e7..210d42c 100644 --- a/paginationcontroller.php +++ b/paginationcontroller.php @@ -2,79 +2,47 @@ require_once('Models/FacilityDataSet.php'); require_once("Models/Paginator.php"); // If page loads empty, set initial headers -if(empty($_GET)) { - header("Location: ?sort=1&dir=asc&category=1&term=&page=0"); - exit; -} +//if(empty($_GET)) { +// header("Location: ?sort=1&dir=asc&category=1&term=&page=0"); +// exit; +//} -$filterArray = [ - 0 => "", - 1 => "", - 2 => "", - 3 => "", - 4 => "", - 5 => "", - 6 => "", - 7 => "", - 8 => "" -]; -$sortArray = [ - 0 => "", - 1 => "", - 2 => "", - 3 => "", - 4 => "", - 5 => "", - 6 => "", - 7 => "", - 8 => "" +$filters = [ + 'category' => $_GET['category'] ?? '1', // Default category + 'term' => $_GET['term'] ?? '', // Default term + 'sort' => $_GET['sort'] ?? '1', // Default sort + 'dir' => $_GET['dir'] ?? 'asc', // Default direction + 'page' => $_GET['page'] ?? 0 // Default to first page ]; $rowLimit = 5; - -$filterArray[$_GET['category'] ?? null] = $_GET['term'] ?? null; -$sortArray[$_GET['sort'] ?? null] = $_GET['dir'] ?? null; - $facilityDataSet = new FacilityDataSet(); -if ((isset($_POST['filter']) && isset($_POST['filterCat']) && (isset($_POST['dir'])) && (isset($_POST['sort'])))) { - $filter = filter_input(INPUT_POST, 'filter', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? ''; - $filterKey = filter_input(INPUT_POST, 'filterCat', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? ''; - $direction = filter_input(INPUT_POST, 'dir', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? 'asc'; // Default to 'asc' - $sortKey = filter_input(INPUT_POST, 'sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? '1'; // Default to 'title' - $page = filter_input(INPUT_POST, 'paginationButton', FILTER_SANITIZE_NUMBER_INT) ?? 0; // Default page to 0 on new filter - $filterArray[$filterKey] = $filter; - $sortArray[$sortKey] = $direction; - var_dump($filterArray, $filterKey); - redirect($filter, $filterArray, $direction, $sortArray, $page); -} -function redirect($filter, $filterArray, $direction, $sortArray, $page) : void { - // Set the filter and generate the new URI - //$filterSet = $facilityDataSet->setFilterUri($applyFilters, array_search($applyFilters, $filterArray)); - $filterSet = setUri($filter, array_search($filter, $filterArray), $direction, array_search($direction, $sortArray), $page); - // Parse the existing query string - $queryParams = []; - parse_str($filterSet["newUri"], $queryParams); +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // Check if filters/sorting changed + $filtersChanged = ( + $filters['category'] !== ($_POST['filterCat'] ?? $filters['category']) || + $filters['term'] !== ($_POST['filter'] ?? $filters['term']) || + $filters['sort'] !== ($_POST['sort'] ?? $filters['sort']) || + $filters['dir'] !== ($_POST['dir'] ?? $filters['dir']) + ); - // Add or overwrite the 'page' parameter - $queryParams['page'] = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT, [ - 'options' => ['default' => 0, 'min_range' => 0] // Default to 0 for the first page - ]); + $filters['category'] = filter_input(INPUT_POST, 'filterCat', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? $filters['category']; + $filters['term'] = filter_input(INPUT_POST, 'filter', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? $filters['term']; + $filters['sort'] = filter_input(INPUT_POST, 'sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? $filters['sort']; + $filters['dir'] = filter_input(INPUT_POST, 'dir', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? $filters['dir']; - // Build the updated query string - $newQueryString = http_build_query($queryParams); - - // Redirect with the updated URI - //var_dump("Redirecting to: {$filterSet["path"]}?{$newQueryString}"); - header("Location: {$filterSet["path"]}?{$newQueryString}"); - exit; + // Reset page if filters changed + $filters['page'] = $filtersChanged ? 0 : $_POST['paginationButton'] ?? $filters['page']; + redirectWithFilters($filters); } -var_dump($filterArray); -$view->pageData = $facilityDataSet->fetchAll($filterArray, $sortArray); +$view->pageData = $facilityDataSet->fetchAll( + ['category' => $filters['category'], 'term' => $filters['term']], + ['sort' => $filters['sort'], 'dir' => $filters['dir']] +); + $view->paginator = new Paginator($rowLimit, $view->pageData); - -// Initialize paginator $view->pageNumber = $view->paginator->getPageFromUri(); $view->pageData = $view->paginator->getPage($view->pageNumber); @@ -83,51 +51,9 @@ $view->dbMessage = $view->paginator->countPageResults($view->pageNumber) == 0 ? "No results" : $view->paginator->countPageResults($view->pageNumber) . " result(s)"; -function setUri($filter, $category, $direction, $sort, $page) -{ - $uri = $_SERVER['REQUEST_URI']; - $uriComp = parse_url($uri); - $params = []; - - // Parse existing query parameters - if (isset($uriComp['query'])) { - parse_str($uriComp['query'], $params); - } else { - $params = array(); - } - // Avoid unnecessary redirection if sort and filter is already correct - if ( - (isset($params['sort']) && $params['sort'] === (string)$sort && isset($params['dir']) && $params['dir'] === $direction) && - (isset($params['category']) && $params['category'] === (string)$category && isset($params['term']) && $params['term'] === $filter) - ) { - exit; - } - - // Update parameters - if (!empty($category)) { - $params['category'] = $category; - } - if (!empty($filter)) { - $params['term'] = $filter; - } - if (!empty($sort)) { - $params['sort'] = $sort; - } - if (!empty($direction)) { - $params['dir'] = $direction; - } - if (!empty($filter)) { - $params['page'] = $page; - } - - // Rebuild the query string - $newUri = http_build_query($params); - var_dump($newUri); - // Redirect to the updated URI - // Use the current path or root - return - [ - 'newUri' => $newUri, - 'path' => $uriComp['path'] ?? '/' - ]; +// Redirect function +function redirectWithFilters($filters) { + $queryString = http_build_query($filters); + header("Location: ?" . $queryString); + exit; }