Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions scripts/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,39 +130,47 @@ function get_db() {

function fetch_species_array($sort_by, $date=null) {
$db = get_db();
$where = (isset($date)) ? "WHERE Date == \"$date\"" : "";
$where = (isset($date)) ? "WHERE Date = :date" : "";
if ($sort_by === "occurrences") {
$statement = $db->prepare("SELECT Date, Time, File_Name, Com_Name, Sci_Name, COUNT(*) as Count, MAX(Confidence) as MaxConfidence FROM detections $where GROUP BY Sci_Name ORDER BY COUNT(*) DESC");
$statement = $db->prepare("SELECT Date, Time, File_Name, Com_Name, Sci_Name, COUNT(*) as Count, MAX(Confidence) as MaxConfidence FROM detections $where GROUP BY Sci_Name ORDER BY Count DESC");
} elseif ($sort_by === "confidence") {
$statement = $db->prepare("SELECT Date, Time, File_Name, Com_Name, Sci_Name, COUNT(*) as Count, MAX(Confidence) as MaxConfidence FROM detections $where GROUP BY Sci_Name ORDER BY MAX(Confidence) DESC");
$statement = $db->prepare("SELECT Date, Time, File_Name, Com_Name, Sci_Name, COUNT(*) as Count, MAX(Confidence) as MaxConfidence FROM detections $where GROUP BY Sci_Name ORDER BY MaxConfidence DESC");
} elseif ($sort_by === "date") {
$statement = $db->prepare("SELECT Date, Time, File_Name, Com_Name, Sci_Name, COUNT(*) as Count, MAX(Confidence) as MaxConfidence FROM detections $where GROUP BY Sci_Name ORDER BY MIN(Date) DESC, Time DESC");
} else {
$statement = $db->prepare("SELECT Date, Time, File_Name, Com_Name, Sci_Name, COUNT(*) as Count, MAX(Confidence) as MaxConfidence FROM detections $where GROUP BY Sci_Name ORDER BY Com_Name ASC");
}
if (isset($date)) {
$statement->bindValue(':date', $date, SQLITE3_TEXT);
}
ensure_db_ok($statement);
$result = $statement->execute();
return $result;
}

function fetch_best_detection($com_name) {
$db = get_db();
$statement = $db->prepare("SELECT Com_Name, Sci_Name, COUNT(*), MAX(Confidence), File_Name, Date, Time from detections WHERE Com_Name = \"$com_name\"");
$statement = $db->prepare("SELECT Com_Name, Sci_Name, COUNT(*), MAX(Confidence), File_Name, Date, Time FROM detections WHERE Com_Name = :com_name");
ensure_db_ok($statement);
$statement->bindValue(':com_name', $com_name, SQLITE3_TEXT);
$result = $statement->execute();
return $result;
}

function fetch_all_detections($sci_name, $sort_by, $date=null) {
$db = get_db();
$filter = (isset($date)) ? "AND Date == \"$date\"" : "";
$filter = (isset($date)) ? "AND Date = :date" : "";
if ($sort_by === "occurrences") {
$statement = $db->prepare("SELECT * FROM detections WHERE Sci_Name == \"$sci_name\" $filter ORDER BY COUNT(*) DESC");
$statement = $db->prepare("SELECT * FROM detections WHERE Sci_Name = :sci_name $filter ORDER BY Confidence DESC");
} elseif ($sort_by === "confidence") {
$statement = $db->prepare("SELECT * FROM detections WHERE Sci_Name == \"$sci_name\" $filter ORDER BY Confidence DESC");
$statement = $db->prepare("SELECT * FROM detections WHERE Sci_Name = :sci_name $filter ORDER BY Confidence DESC");
} else {
$order = (isset($date)) ? "Time DESC" : "Date DESC, Time DESC";
$statement = $db->prepare("SELECT * FROM detections where Sci_Name == \"$sci_name\" $filter ORDER BY $order");
$statement = $db->prepare("SELECT * FROM detections WHERE Sci_Name = :sci_name $filter ORDER BY $order");
}
$statement->bindValue(':sci_name', $sci_name, SQLITE3_TEXT);
if (isset($date)) {
$statement->bindValue(':date', $date, SQLITE3_TEXT);
}
ensure_db_ok($statement);
$result = $statement->execute();
Expand Down
35 changes: 21 additions & 14 deletions scripts/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,28 +138,35 @@ function() {
}
}

$safe = function($val, $pattern = '/[^a-zA-Z0-9._\-]/') {
return '"' . preg_replace($pattern, '', $val) . '"';
};
$safe_num = function($val) {
return is_numeric($val) ? $val : '0';
};

$contents = file_get_contents("/etc/birdnet/birdnet.conf");
$contents = preg_replace("/SITE_NAME=.*/", "SITE_NAME=\"$site_name\"", $contents);
$contents = preg_replace("/LATITUDE=.*/", "LATITUDE=$latitude", $contents);
$contents = preg_replace("/LONGITUDE=.*/", "LONGITUDE=$longitude", $contents);
$contents = preg_replace("/BIRDWEATHER_ID=.*/", "BIRDWEATHER_ID=$birdweather_id", $contents);
$contents = preg_replace("/APPRISE_NOTIFICATION_TITLE=.*/", "APPRISE_NOTIFICATION_TITLE=\"$apprise_notification_title\"", $contents);
$contents = preg_replace("/APPRISE_NOTIFY_EACH_DETECTION=.*/", "APPRISE_NOTIFY_EACH_DETECTION=$apprise_notify_each_detection", $contents);
$contents = preg_replace("/APPRISE_NOTIFY_NEW_SPECIES=.*/", "APPRISE_NOTIFY_NEW_SPECIES=$apprise_notify_new_species", $contents);
$contents = preg_replace("/APPRISE_NOTIFY_NEW_SPECIES_EACH_DAY=.*/", "APPRISE_NOTIFY_NEW_SPECIES_EACH_DAY=$apprise_notify_new_species_each_day", $contents);
$contents = preg_replace("/APPRISE_WEEKLY_REPORT=.*/", "APPRISE_WEEKLY_REPORT=$apprise_weekly_report", $contents);
$contents = preg_replace("/IMAGE_PROVIDER=.*/", "IMAGE_PROVIDER=$image_provider", $contents);
$contents = preg_replace("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents);
$contents = preg_replace("/SITE_NAME=.*/", "SITE_NAME=" . $safe($site_name, '/[^a-zA-Z0-9._\- ]/'), $contents);
$contents = preg_replace("/LATITUDE=.*/", "LATITUDE=" . $safe_num($latitude), $contents);
$contents = preg_replace("/LONGITUDE=.*/", "LONGITUDE=" . $safe_num($longitude), $contents);
$contents = preg_replace("/BIRDWEATHER_ID=.*/", "BIRDWEATHER_ID=" . $safe($birdweather_id), $contents);
$contents = preg_replace("/APPRISE_NOTIFICATION_TITLE=.*/", "APPRISE_NOTIFICATION_TITLE=" . $safe($apprise_notification_title, '/[^a-zA-Z0-9._\- ]/'), $contents);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve notification template variables

When users follow the settings page guidance to put variables such as $comname or $confidence in the notification title, this whitelist removes the $ before writing birdnet.conf. The notification code later calls render_template() on the title, so a title like New $comname is saved as New comname and future alerts no longer include the detected species. Please preserve the supported $... tokens while still escaping the config value.

Useful? React with 👍 / 👎.

$contents = preg_replace("/APPRISE_NOTIFY_EACH_DETECTION=.*/", "APPRISE_NOTIFY_EACH_DETECTION=" . $safe_num($apprise_notify_each_detection), $contents);
$contents = preg_replace("/APPRISE_NOTIFY_NEW_SPECIES=.*/", "APPRISE_NOTIFY_NEW_SPECIES=" . $safe_num($apprise_notify_new_species), $contents);
$contents = preg_replace("/APPRISE_NOTIFY_NEW_SPECIES_EACH_DAY=.*/", "APPRISE_NOTIFY_NEW_SPECIES_EACH_DAY=" . $safe_num($apprise_notify_new_species_each_day), $contents);
$contents = preg_replace("/APPRISE_WEEKLY_REPORT=.*/", "APPRISE_WEEKLY_REPORT=" . $safe_num($apprise_weekly_report), $contents);
$contents = preg_replace("/IMAGE_PROVIDER=.*/", "IMAGE_PROVIDER=" . $safe($image_provider), $contents);
$contents = preg_replace("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=" . $safe($flickr_api_key, '/[^a-zA-Z0-9]/'), $contents);
if(strlen($language) == 2 || strlen($language) == 5){
$contents = preg_replace("/DATABASE_LANG=.*/", "DATABASE_LANG=$language", $contents);
}
$contents = preg_replace("/INFO_SITE=.*/", "INFO_SITE=$info_site", $contents);
$contents = preg_replace("/COLOR_SCHEME=.*/", "COLOR_SCHEME=$color_scheme", $contents);
$contents = preg_replace("/FLICKR_FILTER_EMAIL=.*/", "FLICKR_FILTER_EMAIL=$flickr_filter_email", $contents);
$contents = preg_replace("/APPRISE_MINIMUM_SECONDS_BETWEEN_NOTIFICATIONS_PER_SPECIES=.*/", "APPRISE_MINIMUM_SECONDS_BETWEEN_NOTIFICATIONS_PER_SPECIES=$minimum_time_limit", $contents);
$contents = preg_replace("/MODEL=.*/", "MODEL=$model", $contents);
$contents = preg_replace("/SF_THRESH=.*/", "SF_THRESH=$sf_thresh", $contents);
$contents = preg_replace("/DATA_MODEL_VERSION=.*/", "DATA_MODEL_VERSION=$data_model_version", $contents);
$contents = preg_replace("/MODEL=.*/", "MODEL=" . $safe($model), $contents);
$contents = preg_replace("/SF_THRESH=.*/", "SF_THRESH=" . $safe_num($sf_thresh), $contents);
$contents = preg_replace("/DATA_MODEL_VERSION=.*/", "DATA_MODEL_VERSION=" . $safe_num($data_model_version), $contents);
$contents = preg_replace("/APPRISE_ONLY_NOTIFY_SPECIES_NAMES=.*/", "APPRISE_ONLY_NOTIFY_SPECIES_NAMES=\"$only_notify_species_names\"", $contents);
$contents = preg_replace("/APPRISE_ONLY_NOTIFY_SPECIES_NAMES_2=.*/", "APPRISE_ONLY_NOTIFY_SPECIES_NAMES_2=\"$only_notify_species_names_2\"", $contents);

Expand Down
13 changes: 10 additions & 3 deletions scripts/play.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@
$statement1 = $db_writable->prepare('DELETE FROM detections WHERE File_Name = :file_name LIMIT 1');
ensure_db_ok($statement1);
$statement1->bindValue(':file_name', explode("/", $_GET['deletefile'])[2]);
$file_pointer = $home."/BirdSongs/Extracted/By_Date/".$_GET['deletefile'];
if (!exec("sudo rm $file_pointer 2>&1 && sudo rm $file_pointer.png 2>&1", $output)) {
$file_pointer = realpath($home."/BirdSongs/Extracted/By_Date/".$_GET['deletefile']);
$allowed_base = realpath($home."/BirdSongs/Extracted/By_Date/");
if ($file_pointer === false || !str_starts_with($file_pointer, $allowed_base)) {
echo "Error - invalid file path";
die();
}
@unlink($file_pointer);
if (@unlink($file_pointer . ".png") || true) {
Comment on lines +34 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fail deletion when the recording cannot be unlinked

If PHP/caddy lacks write permission on a restored or externally mounted recordings directory, the audio unlink() can fail, but || true makes the handler still return OK and then remove the database row. That leaves the recording orphaned on disk while the UI reports success; check the audio deletion result and only tolerate a missing PNG separately before deleting the DB row.

Useful? React with 👍 / 👎.

echo "OK";
} else {
echo "Error - file deletion failed : " . implode(", ", $output) . "<br>";
Expand Down Expand Up @@ -636,8 +642,9 @@ function changeDetection(filename,copylink=false) {

if(isset($_GET['filename'])){
$name = $_GET['filename'];
$statement2 = $db->prepare("SELECT * FROM detections where File_name == \"$name\" ORDER BY Date DESC, Time DESC");
$statement2 = $db->prepare("SELECT * FROM detections WHERE File_name = :filename ORDER BY Date DESC, Time DESC");
ensure_db_ok($statement2);
$statement2->bindValue(':filename', $name, SQLITE3_TEXT);
$result2 = $statement2->execute();
$results = $result2->fetchArray(SQLITE3_ASSOC);
$sciname = $results['Sci_Name'];
Expand Down
23 changes: 14 additions & 9 deletions scripts/todays_detections.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,18 +128,18 @@ function relativeTime($ts)

if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
if(isset($_GET['searchterm'])) {
if(strtolower(explode(" ", $_GET['searchterm'])[0]) == "not") {
$not = "NOT ";
$operator = "AND";
$_GET['searchterm'] = str_replace("not ", "", $_GET['searchterm']);
$_GET['searchterm'] = str_replace("NOT ", "", $_GET['searchterm']);
} else {
$not = "";
$operator = "OR";
$raw_term = $_GET['searchterm'];
$negate = (strtolower(explode(" ", $raw_term)[0]) === "not");
if ($negate) {
$raw_term = preg_replace('/^not\s+/i', '', $raw_term);
}
$searchquery = "AND (Com_name ".$not."LIKE '%".$_GET['searchterm']."%' ".$operator." Sci_name ".$not."LIKE '%".$_GET['searchterm']."%' ".$operator." Confidence ".$not."LIKE '%".$_GET['searchterm']."%' ".$operator." File_Name ".$not."LIKE '%".$_GET['searchterm']."%' ".$operator." Time ".$not."LIKE '%".$_GET['searchterm']."%')";
$not = $negate ? "NOT " : "";
$op = $negate ? "AND" : "OR";
$searchquery = "AND (Com_name {$not}LIKE :t1 {$op} Sci_name {$not}LIKE :t2 {$op} Confidence {$not}LIKE :t3 {$op} File_Name {$not}LIKE :t4 {$op} Time {$not}LIKE :t5)";
$search_term_bound = '%' . $raw_term . '%';
} else {
$searchquery = "";
$search_term_bound = null;
}
if(isset($_GET['display_limit']) && is_numeric($_GET['display_limit'])){
$statement0 = $db->prepare('SELECT Date, Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') '.$searchquery.' ORDER BY Time DESC LIMIT '.(intval($_GET['display_limit'])-40).',40');
Expand All @@ -152,6 +152,11 @@ function relativeTime($ts)
}

}
if ($search_term_bound !== null) {
for ($i = 1; $i <= 5; $i++) {
$statement0->bindValue(":t$i", $search_term_bound, SQLITE3_TEXT);
}
}
ensure_db_ok($statement0);
$result0 = $statement0->execute();

Expand Down
Loading