diff --git a/scripts/pi-hole/php/groups.php b/scripts/pi-hole/php/groups.php index b594f565..f0778a99 100644 --- a/scripts/pi-hole/php/groups.php +++ b/scripts/pi-hole/php/groups.php @@ -504,15 +504,30 @@ if ($_POST['action'] == 'get_groups') { $before = intval($db->querySingle("SELECT COUNT(*) FROM domainlist;")); $total = count($domains); $added = 0; - $stmt = $db->prepare('REPLACE INTO domainlist (domain,type,comment) VALUES (:domain,:type,:comment)'); - if (!$stmt) { + + // Prepare INSERT INTO statement + $insert_stmt = $db->prepare('REPLACE INTO domainlist (domain,type) VALUES (:domain,:type)'); + if (!$insert_stmt) { throw new Exception('While preparing statement: ' . $db->lastErrorMsg()); } - $delstmt = null; + // Prepare UPDATE statement + $update_stmt = $db->prepare('UPDATE domainlist SET comment = :comment WHERE domain = :domain AND type = :type'); + if (!$update_stmt) { + throw new Exception('While preparing statement: ' . $db->lastErrorMsg()); + } + + $check_stmt = null; + $delete_stmt = null; if($_POST['action'] == 'replace_domain') { - $delstmt = $db->prepare('DELETE FROM domainlist WHERE domain = :domain'); - if (!$delstmt) { + // Check statement will reveal any group associations for a given (domain,type) which do NOT belong to the default group + $check_stmt = $db->prepare('SELECT EXISTS(SELECT * FROM domainlist_by_group WHERE domainlist_id = (SELECT id FROM domainlist WHERE domain = :domain) AND group_id != 0)'); + if (!$check_stmt) { + throw new Exception('While preparing check statement: ' . $db->lastErrorMsg()); + } + // Delete statement will remove this domain from any type of list + $delete_stmt = $db->prepare('DELETE FROM domainlist WHERE domain = :domain'); + if (!$delete_stmt) { throw new Exception('While preparing delete statement: ' . $db->lastErrorMsg()); } } @@ -525,7 +540,8 @@ if ($_POST['action'] == 'get_groups') { $type = ListType::blacklist; } - if (!$stmt->bindValue(':type', $type, SQLITE3_TEXT)) { + if (!$insert_stmt->bindValue(':type', $type, SQLITE3_TEXT) || + !$update_stmt->bindValue(':type', $type, SQLITE3_TEXT)) { throw new Exception('While binding type: ' . $db->lastErrorMsg()); } @@ -534,7 +550,7 @@ if ($_POST['action'] == 'get_groups') { // Store NULL in database for empty comments $comment = null; } - if (!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) { + if (!$update_stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) { throw new Exception('While binding comment: ' . $db->lastErrorMsg()); } @@ -556,7 +572,7 @@ if ($_POST['action'] == 'get_groups') { } } - if(strlen($_POST['type']) === 2 && $_POST['type'][1] === 'W') + if(isset($_POST['type']) && strlen($_POST['type']) === 2 && $_POST['type'][1] === 'W') { // Apply wildcard-style formatting $domain = "(\\.|^)".str_replace(".","\\.",$domain)."$"; @@ -579,27 +595,62 @@ if ($_POST['action'] == 'get_groups') { } } - // First try to delete any occurrences of this domain if we're in replace mode + // First try to delete any occurrences of this domain if we're in + // replace mode. Only do this when the domain to be replaced is in + // the default group! Otherwise, we would shuffle group settings and + // just throw an error at the user to tell them to change this + // domain manually. This ensures user's will really get what they + // want from us. if($_POST['action'] == 'replace_domain') { - if (!$delstmt->bindValue(':domain', $domain, SQLITE3_TEXT)) { + if (!$check_stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) { + throw new Exception('While binding domain to check: ' . $db->lastErrorMsg() . '
'. + 'Added ' . $added . " out of ". $total . " domains"); + } + + $check_result = $check_stmt->execute(); + if (!$check_result) { + throw new Exception('While executing check: ' . $db->lastErrorMsg() . '
'. + 'Added ' . $added . " out of ". $total . " domains"); + } + + // Check return value of CHECK query (0 = only default group, 1 = special group assignments) + $only_default_group = (($check_result->fetchArray(SQLITE3_NUM)[0]) == 0) ? true : false; + if(!$only_default_group) { + throw new Exception('Domain ' . $domain . 'is configured with special group settings.
'. + 'Please modify the domain on the respective group management pages.'); + } + + if (!$delete_stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) { throw new Exception('While binding domain: ' . $db->lastErrorMsg() . '
'. 'Added ' . $added . " out of ". $total . " domains"); } - if (!$delstmt->execute()) { + if (!$delete_stmt->execute()) { throw new Exception('While executing: ' . $db->lastErrorMsg() . '
'. 'Added ' . $added . " out of ". $total . " domains"); } } - // Add domain with specific type and comment (both were already bound above) - if (!$stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) { + + if (!$insert_stmt->bindValue(':domain', $domain, SQLITE3_TEXT) || + !$update_stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) { throw new Exception('While binding domain: ' . $db->lastErrorMsg() . '
'. 'Added ' . $added . " out of ". $total . " domains"); } - if (!$stmt->execute()) { - throw new Exception('While executing: ' . $db->lastErrorMsg() . '
'. + // First execute INSERT OR IGNORE statement to create a record for + // this domain (ignore if already existing) + if (!$insert_stmt->execute()) { + throw new Exception('While executing INSERT OT IGNORE: ' . $db->lastErrorMsg() . '
'. + 'Added ' . $added . " out of ". $total . " domains"); + } + + // Then update the record with a new comment (and modification date + // due to the trigger event) We are not using REPLACE INTO to avoid + // the initial DELETE event (loosing group assignments in case an + // entry did already exist). + if (!$update_stmt->execute()) { + throw new Exception('While executing UPDATE: ' . $db->lastErrorMsg() . '
'. 'Added ' . $added . " out of ". $total . " domains"); } $added++;