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++;