Files
AvianVisitors/scripts/common.php
T
jaredb7 66863f2e6b Save password last to avoid auth check preventing saving other settings
Because saveSetting() checks each time to see that the user has authenticated, if the password is changed first then very next setting will cause a auth prompt (as the password changed) and if cancelled then the other setting won't save.

Putting it last will allow us to save all the other setting and then change the password, then the next attempt to execute a action or access a page that required auth will prompt the user the the password.
2023-05-12 23:09:15 +10:00

1768 lines
62 KiB
PHP

<?php
if (realpath(__FILE__) == realpath($_SERVER['SCRIPT_FILENAME'])) {
header('HTTP/1.1 403 Forbidden', TRUE, 403);
}
ini_set('session.gc_maxlifetime', 7200);
ini_set('user_agent', 'PHP_Flickr/1.0');
ini_set('display_errors', 1);
error_reporting(E_ERROR);
//Setup the session if we're not using the API, since the API endpoint sets up it's own session
if (!isset($api_incl)) {
$api_incl = false;
session_set_cookie_params(7200);
session_start();
}
//Get the user and home directory for the first user in the system (userid 1000)
$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd");
$user = trim($user);
$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd");
$home = trim($home);
////////// SET TIMEZONE //////////
// If we can get the timezome from the systems timezone file ust that
$sys_timezone = "";
if (file_exists('/etc/timezone')) {
$tz_data = file_get_contents('/etc/timezone');
if ($tz_data !== false) {
$sys_timezone = trim($tz_data);
}
} else {
// Else get timezone from the timedatectl command
$tz_data = shell_exec('timedatectl show');
$tz_data_array = parse_ini_string($tz_data);
if (is_array($tz_data_array) && array_key_exists('Timezone', $tz_data_array)) {
$sys_timezone = $tz_data_array['Timezone'];
}
}
//Finally if we have a valid timezone, set it as the one PHP uses
if ($sys_timezone !== "") {
date_default_timezone_set($sys_timezone);
}
////////// PARSES THE CONFIG FILE //////////
$config = [];
parseConfig();
//Set a default site name if nothing iss configured
if ($config["SITE_NAME"] == "") {
$site_name = "BirdNET-Pi";
} else {
$site_name = $config['SITE_NAME'];
}
$models = array("BirdNET_6K_GLOBAL_MODEL", "BirdNET_GLOBAL_3K_V2.3_Model_FP16");
$audio_formats = array("8svx", "aif", "aifc", "aiff", "aiffc", "al", "amb", "amr-nb", "amr-wb", "anb", "au", "avr", "awb", "caf", "cdda", "cdr", "cvs", "cvsd", "cvu", "dat", "dvms", "f32", "f4", "f64", "f8", "fap", "flac", "fssd", "gsm", "gsrt", "hcom", "htk", "ima", "ircam", "la", "lpc", "lpc10", "lu", "mat", "mat4", "mat5", "maud", "mp2", "mp3", "nist", "ogg", "paf", "prc", "pvf", "raw", "s1", "s16", "s2", "s24", "s3", "s32", "s4", "s8", "sb", "sd2", "sds", "sf", "sl", "sln", "smp", "snd", "sndfile", "sndr", "sndt", "sou", "sox", "sph", "sw", "txw", "u1", "u16", "u2", "u24", "u3", "u32", "u4", "u8", "ub", "ul", "uw", "vms", "voc", "vorbis", "vox", "w64", "wav", "wavpcm", "wv", "wve", "xa", "xi");
$freqshift_tools = array("sox", "ffmpeg");
//Database Languages
$langs = array(
'not-selected' => 'Not Selected',
"af" => "Afrikaans",
"ca" => "Catalan",
"cs" => "Czech",
"zh" => "Chinese",
"hr" => "Croatian",
"da" => "Danish",
"nl" => "Dutch",
"en" => "English",
"et" => "Estonian",
"fi" => "Finnish",
"fr" => "French",
"de" => "German",
"hu" => "Hungarian",
"is" => "Icelandic",
"id" => "Indonesia",
"it" => "Italian",
"ja" => "Japanese",
"lv" => "Latvian",
"lt" => "Lithuania",
"no" => "Norwegian",
"pl" => "Polish",
"pt" => "Portugues",
"ru" => "Russian",
"sk" => "Slovak",
"sl" => "Slovenian",
"es" => "Spanish",
"sv" => "Swedish",
"th" => "Thai",
"uk" => "Ukrainian"
);
//Reference to database connection
$DB_CONN = null;
$USER_AUTHENTICATED = false;
/**
* Connects to the bird.db SQLite3 database
* @return SQLite3
*/
function connect_to_birdsdb()
{
global $DB_CONN, $api_incl;
//Initially check to see if the DB is already connected, it will not be null if it has
if ($DB_CONN == null) {
try {
$DB_CONN = new SQLite3(getFilePath('birds.db'), SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);
if ($DB_CONN == False) {
birdnet_error_log("connect_to_birdsdb:: birds.db database is busy");
}
} catch (Exception $sql_exec) {
birdnet_error_log("connect_to_birdsdb:: Exception occurred while trying to open birds.db - " . $sql_exec->getMessage());
}
}
return $DB_CONN;
}
/**
* Disconnects the database
*
* @return void
*/
function disconnect_from_birdsdb()
{
global $DB_CONN;
if ($DB_CONN != null) {
return $DB_CONN->close();
}
}
/**
* Executes the supplied query and returns all results
*
* @param $query string The query to execute
* @param $bind_params string Any values that should be bound into the query
* @param $fetchAllRecords string Any values that should be bound into the query
* @param $fetchMode string Controls how result data is returned, default is @->default('SQLITE3_ASSOC') Associative Array;
* @return array
*/
function db_execute_query($query, $bind_params = [], $fetchAllRecords = false, $fetchMode = SQLITE3_ASSOC)
{
global $DB_CONN;
$success = false;
$message = '';
$data_to_return = null;
//Connect to the DB
connect_to_birdsdb();
try {
$stmt = $DB_CONN->prepare($query);
//
if (!empty($bind_params)) {
//Loop over the bind values and add them
foreach ($bind_params as $bind_key => $bind_value) {
$stmt->bindValue($bind_key, $bind_value);
}
}
if ($stmt == False) {
//get caller's function name
$caller_func_name = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'];
$error_msg = "$caller_func_name => db_execute_custom_query:: birds.db database is busy or a query error occurred";
//
$success = false;
$message = $error_msg;
$data_to_return = null;
//Log the error message
birdnet_error_log($error_msg);
} else {
$result = $stmt->execute();
//Initial result collection
$resultArray = $result->fetchArray($fetchMode);
if ($fetchAllRecords) {
$multiArray = array(); //array to store all rows
//Loop over the results to collect them all
while ($resultArray !== false) {
array_push($multiArray, $resultArray); //insert all rows to $multiArray
$resultArray = $result->fetchArray($fetchMode);
}
unset($resultArray); //unset temporary variable
$data_to_return = $multiArray;
} else {
$data_to_return = $resultArray;
}
//
$success = true;
$message = 'Ok';
}
} catch (Exception $sql_exec) {
$caller_func_name = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'];
$error_msg = "$caller_func_name => db_execute_custom_query:: Exception occurred while executing query - " . $sql_exec->getMessage();
//
$success = false;
$message = $error_msg;
$data_to_return = null;
birdnet_error_log($error_msg);
}
return array('success' => $success, 'message' => $message, 'data' => $data_to_return);
}
/**
* Returns a count of detections in the database
* @return array
*/
function getDetectionCountAll()
{
return db_execute_query('SELECT COUNT(*) FROM detections');
}
/**
* Returns today's count of detections in the database
* @return array
*/
function getDetectionCountToday()
{
return db_execute_query('SELECT COUNT(*) FROM detections WHERE Date == DATE(\'now\', \'localtime\')');
}
/**
* Returns detection info for the most recent detection today
* @return array
*/
function getMostRecentDetectionToday()
{
return db_execute_query('SELECT * FROM detections WHERE Date == DATE(\'now\', \'localtime\') ORDER BY TIME DESC LIMIT 1', [], true);
}
/**
* Returns a count of detections in the past hour
* @return array
*/
function getDetectionCountLastHour()
{
return db_execute_query('SELECT COUNT(*) FROM detections WHERE Date == Date(\'now\', \'localtime\') AND TIME >= TIME(\'now\', \'localtime\', \'-1 hour\')');
}
/**
* Returns the most recent detection
* @return array|string
*/
function getMostRecentDetection($limit = 1)
{
return db_execute_query('SELECT Com_Name, Sci_Name, Date, Time, Confidence, File_Name FROM detections ORDER BY Date DESC, Time DESC LIMIT :limit', [':limit' => $limit], true);
}
/**
* Returns a talley for todays detected species
* @return array
*/
function getSpeciesTalley($range = "today", $start_date = null, $end_date = null)
{
$range = strtolower($range);
if ($range == "today") {
$result = db_execute_query('SELECT COUNT(DISTINCT(Com_Name)) FROM detections WHERE Date == Date(\'now\', \'localtime\')');
} else if ($range == "custom") {
$result = db_execute_query('SELECT COUNT(DISTINCT(Com_Name)) FROM detections WHERE Date == :start_date', [':start_date' => $start_date], false);
} else if ($range == "range") {
$result = db_execute_query('SELECT COUNT(DISTINCT(Com_Name)) FROM detections WHERE Date BETWEEN :start_date AND :end_date', [':start_date' => $start_date, ':end_date' => $end_date]);
}
return $result;
}
/**
* Returns a species talley from ALL detections
* @return array
*/
function getAllSpeciesTalley()
{
return db_execute_query('SELECT COUNT(DISTINCT(Com_Name)) FROM detections');
}
/**
* Returns the number of detections on the specified date
*
* @param $date string Date to count detections for
* @return array
*/
function getDetectionCountByDate($date, $date_range = false)
{
return db_execute_query("SELECT COUNT(*) FROM detections WHERE Date == :date", [':date' => $date], false);
}
/**
* Returns detections and counts for the specified date and start & finish times
*
* @param $date string Date to count detections for
* @param $starttime string Search for detections after this time
* @param $endtime string And detections up to this time
* @return array
*/
function getDetectionBreakdownByTime($date, $starttime, $endtime)
{
return db_execute_query('SELECT DISTINCT(Com_Name), COUNT(*) FROM detections WHERE Date == :date AND Time > :start_time AND Time < :end_time AND Confidence > 0.75 GROUP By Com_Name ORDER BY COUNT(*) DESC',
[
':date' => $date,
':start_time' => $starttime,
':end_time' => $endtime
],
true);
}
/**
* Returns the detection count for a specified bird
* @param $birdName string Species Name to get stats for
* @return array
*/
function getBirdDetectionStats($birdName)
{
//Cleanup the bird/species name
$birdName = str_replace("_", " ", $birdName);
$birdDetections = db_execute_query('SELECT Date, COUNT(*) AS Detections FROM detections WHERE Com_Name = :com_name AND Date BETWEEN DATE("now", "-30 days") AND DATE("now") GROUP BY Date', [':com_name' => $birdName], true);
// Fetch the result set as an associative array
$data = array();
foreach ($birdDetections as $birdDetection) {
$data[$birdDetection['Date']] = $birdDetection['Detections'];
}
// Create an array of all dates in the last 14 days
$last14Days = array();
for ($i = 0; $i < 31; $i++) {
$last14Days[] = date('Y-m-d', strtotime("-$i days"));
}
// Merge the data array with the last14Days array
$data = array_merge(array_fill_keys($last14Days, 0), $data);
// Sort the data by date in ascending order
ksort($data);
// Convert the data to an array of objects
$data = array_map(function ($date, $count) {
return array('date' => $date, 'count' => $count);
}, array_keys($data), $data);
// Return the data as JSON - overwrite the result data
$birdDetections['data'] = json_encode($data);
return $birdDetections;
}
/**
* Returns detection info for the specified
* @param $birdName string Species name to get detection info on
* @param $date string OPTIONAL - The date on which to list species on
* @param $sort string OPTIONAL - Whether to sort the species by confidence value (so species ranked by detection accuracy/confidence)
* @return array|string
*/
function getSpeciesDetectionInfo($birdName, $date = null, $sort = null)
{
//If no date is supplied, then list a unique list of detection dates in the DB, sorted descending
if ($date == null) {
//No date set so automatically search by date and time if no sort value is set
if (isset($sort) && $sort == "confidence") {
$detectionInfo = db_execute_query("SELECT * FROM detections where Com_Name == :birdname ORDER BY Confidence DESC", [':birdname' => $birdName], true);
} else {
$detectionInfo = db_execute_query("SELECT * FROM detections where Com_Name == :birdname ORDER BY Date DESC, Time DESC", [':birdname' => $birdName], true);
}
} else {
//Date set so use that to filter the results depending on the sort value
if (isset($sort) && $sort == "confidence") {
$detectionInfo = db_execute_query("SELECT * FROM detections where Com_Name == :birdname AND Date == :date ORDER BY Confidence DESC", [':birdname' => $birdName, ':date' => $date], true);
} else {
$detectionInfo = db_execute_query("SELECT * FROM detections where Com_Name == :birdname AND Date == :date ORDER BY Time DESC", [':birdname' => $birdName, ':date' => $date], true);
}
}
return $detectionInfo;
}
/**
* Returns the detections for a specified filename
*
* @param $filename string The filename on which to get detections for
* @return array|string
*/
function getDetectionsByFilename($filename)
{
return db_execute_query("SELECT * FROM detections where File_name == :filename ORDER BY Date DESC, Time DESC", [':filename' => $filename], true);
}
/**
* Returns a list of specie names on a specified date if supplied, else lists valid dates which can be passed again for specific list of species on that date
*
* @param $date string OPTIONAL - The date on which to list species on
* @param $sort string OPTIONAL - Whether to sort the species by occurrence (so species ranked by number of detections)
* @return array|string
*/
function getDetectionsByDate($date = null, $sort = null)
{
//If no date is supplied, then list a unique list of species in the DB
if ($date == null) {
$detectionsByDateResult = db_execute_query('SELECT DISTINCT(Date) FROM detections GROUP BY Date ORDER BY Date DESC', null, true);
} else {
//Else a date was supplied, first check the sort order if any
if (isset($sort) && $sort == "occurrences") {
$detectionsByDateResult = db_execute_query("SELECT DISTINCT(Com_Name) FROM detections WHERE Date == :date GROUP BY Com_Name ORDER BY COUNT(*) DESC", [':date' => $date], true);
} else {
$detectionsByDateResult = db_execute_query("SELECT DISTINCT(Com_Name) FROM detections WHERE Date == :date ORDER BY Com_Name", [':date' => $date], true);
}
}
return $detectionsByDateResult;
}
/**
* Returns a list detections for a species if supplied, else lists all detected species by name
*
* @param $species_name string OPTIONAL - List detections for this specific species
* @param $sort string OPTIONAL - Whether to sort the species by occurrence (so species ranked by number of detections)
* @return array
*/
function getDetectionsBySpecies($species_name = null, $sort = null)
{
//If no species is is supplied, then list all species
if (!isset($species_name)) {
if (isset($sort) && $sort == "occurrences") {
//Sort by occurrences
$speciesDetections = db_execute_query('SELECT DISTINCT(Com_Name) FROM detections GROUP BY Com_Name ORDER BY COUNT(*) DESC', null, true);
} else {
//Don't sort by occurrences
$speciesDetections = db_execute_query('SELECT DISTINCT(Com_Name) FROM detections ORDER BY Com_Name ASC', null, true);
}
} else {
//Else a species name was supplied, first check the sort order if any
$speciesDetections = db_execute_query("SELECT * FROM detections WHERE Com_Name == :species_name ORDER BY Com_Name", [':species_name' => $species_name], true);
//Also get the highest confidence record for this species
$speciesDetections_MaxConf = db_execute_query("SELECT Date, Time, Sci_Name, MAX(Confidence), File_Name FROM detections WHERE Com_Name == :species_name ORDER BY Com_Name", [':species_name' => $species_name], true);
}
//Rearrange data
$newReturnData = [];
$newReturnData['species'] = $speciesDetections['data'];
//Check to see if we have to get max confidence results for the species also
if (isset($speciesDetections_MaxConf)) {
$newReturnData['species_MaxConf'] = $speciesDetections_MaxConf['data'];
}
$speciesDetections['data'] = $newReturnData;
return $speciesDetections;
}
/**
* Returns a list of species either ordered alphabetically (default) or ordered by the number of detections the species has
*
* @param $sort string OPTIONAL - Sort result alphabetically (supply null) or by number of occurrences (supply "occurrences"
* @return array
*/
function getSpeciesBestRecordingList($sort = null)
{
if (isset($sort) && $sort == "occurrences") {
//Sort by occurrences
$speciesBestRecording = db_execute_query('SELECT Date, Time, File_Name, Com_Name, COUNT(*), MAX(Confidence) FROM detections GROUP BY Com_Name ORDER BY COUNT(*) DESC', null, true);
} else {
//Don't sort by occurrences, sort by alphabetical
$speciesBestRecording = db_execute_query('SELECT Date, Time, File_Name, Com_Name, COUNT(*), MAX(Confidence) FROM detections GROUP BY Com_Name ORDER BY Com_Name ASC', null, true);
}
return $speciesBestRecording;
}
/**
* Returns a list of best recordings for a supplied species
*
* @param $species_name string Name of the species
* @return array
*/
function getBestRecordingsForSpecies($species_name)
{
return db_execute_query("SELECT Com_Name, Sci_Name, COUNT(*), MAX(Confidence), File_Name, Date, Time from detections WHERE Com_Name = :species_name", [':species_name' => $species_name], true);
}
/**
* Get a list of todays detections
*
* @param $display_limit string Number of results to return
* @param $search_term string OPTIONAL: Return results that match the supplied term
* @param $hard_limit string OPTIONAL: Return a fix number of results
* @return array
*/
function getTodaysDetections($display_limit, $search_term = null, $hard_limit = null)
{
$bind_params = [];
if (isset($search_term)) {
if (strtolower(explode(" ", $search_term)[0]) == "not") {
$not = "NOT ";
$operator = "AND";
$search_term = str_replace("not ", "", $search_term);
$search_term = str_replace("NOT ", "", $search_term);
} else {
$not = "";
$operator = "OR";
}
$searchquery = "AND (Com_name " . $not . "LIKE :search_term " .
$operator . " Sci_name " . $not . "LIKE :search_term " .
$operator . " Confidence " . $not . "LIKE :search_term " .
$operator . " File_Name " . $not . "LIKE :search_term " .
$operator . " Time " . $not . "LIKE :search_term)";
$bind_params = [':search_term' => '%' . $search_term . '%'];
} else {
$searchquery = "";
}
if (isset($display_limit) && is_numeric($display_limit)) {
$bind_params[':display_limit'] = (intval($display_limit) - 40);
$result = db_execute_query('SELECT Date, Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') ' . $searchquery . ' ORDER BY Time DESC LIMIT :display_limit,40', $bind_params, true);
} else {
// legacy mode
if (isset($hard_limit) && is_numeric($hard_limit)) {
$bind_params[':hard_limit'] = $hard_limit;
$result = db_execute_query('SELECT Date, Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') ' . $searchquery . ' ORDER BY Time DESC LIMIT :hard_limit', $bind_params, true);
} else {
$result = db_execute_query('SELECT Date, Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') ' . $searchquery . ' ORDER BY Time DESC', $bind_params, true);
}
}
return $result;
}
/**
* Returns the species talley for last week and the week prior
*
* @return array[]
*/
function getWeeklyReportSpeciesTalley()
{
$last_week_dates = getLastWeekDates();
$startdate = $last_week_dates['start_date'];
$enddate = $last_week_dates['end_date'];
$totalspeciestally = getSpeciesTalley('range', date("Y-m-d", $startdate), date("Y-m-d", $enddate));
$priortotalspeciestally = getSpeciesTalley('range', date("Y-m-d", $startdate - (7 * 86400)), date("Y-m-d", $enddate - (7 * 86400)));
return ['totalspeciestally' => $totalspeciestally, 'priortotalspeciestally' => $priortotalspeciestally];
}
/**
* Returns the species talley for last week and the week prior
*
* @return array[]
*/
function getWeeklyReportSpeciesDetectionCounts($detections_asc = false)
{
$last_week_dates = getLastWeekDates();
$startdate = $last_week_dates['start_date'];
$enddate = $last_week_dates['end_date'];
$sort_order = $detections_asc ? 'ASC' : 'DESC';
$detections = db_execute_query('SELECT DISTINCT(Com_Name), COUNT(*) FROM detections WHERE Date BETWEEN :start_date AND :end_date GROUP By Com_Name ORDER BY COUNT(*) ' . $sort_order, [':start_date' => date("Y-m-d", $startdate), ':end_date' => date("Y-m-d", $enddate)], true);
$totalcount = db_execute_query('SELECT DISTINCT(Com_Name), COUNT(*) FROM detections WHERE Date BETWEEN :start_date AND :end_date', [':start_date' => date("Y-m-d", $startdate), ':end_date' => date("Y-m-d", $enddate)], true);
$priortotalcount = db_execute_query('SELECT DISTINCT(Com_Name), COUNT(*) FROM detections WHERE Date BETWEEN :start_date AND :end_date', [':start_date' => date("Y-m-d", date("Y-m-d", $startdate - (7 * 86400))), ':end_date' => date("Y-m-d", date("Y-m-d", $enddate - (7 * 86400)))], true);
return ['detections' => $detections, 'totalcount' => $totalcount, 'priortotalcount' => $priortotalcount];
}
/**
* Returns last weeks detection count for the specified species
*
* @param string $species_name Species to get last weeks detection count for
* @param bool $this_week Whether to find detections in or outside last week
* @return array
*/
function getWeeklyReportSpeciesDetection($species_name, $this_week = true)
{
$last_week_dates = getLastWeekDates();
$startdate = $last_week_dates['start_date'];
$enddate = $last_week_dates['end_date'];
if ($this_week) {
$result = db_execute_query('SELECT COUNT(*) FROM detections WHERE Com_Name == :species_name AND Date BETWEEN :start_date AND :end_date', [':species_name' => $species_name, ':start_date' => date("Y-m-d", $startdate - (7 * 86400)), ':end_date' => date("Y-m-d", $enddate - (7 * 86400))], false);
} else {
$result = db_execute_query('SELECT COUNT(*) FROM detections WHERE Com_Name == :species_name AND Date NOT BETWEEN :start_date AND :end_date', [':species_name' => $species_name, ':start_date' => date("Y-m-d", $startdate), ':end_date' => date("Y-m-d", $enddate)], false);
}
return $result;
}
/**
* Deletes a specified detection by filename
*
* @param $filename string The filename of the detection e.g 2023-04-25/Pacific_Koel/Pacific_Koel-76-2023-04-25-birdnet-RTSP_2-16:24:05.mp3
* @return array
*/
function deleteDetection($filename)
{
global $DB_CONN, $api_incl;
$success = false;
$message = '';
$data_to_return = null;
//Check authentication before going any further
//the front end would have done this check already and such should pass
authenticateUser();
$filename_exploded = explode("/", $filename);
$actual_filename = $filename_exploded[2];
//If the detection was successfully deleted, remove the mp3 and png spectrogram
$file_pointer = getDirectory('home') . "/BirdSongs/Extracted/By_Date/" . $filename;
if (!exec("sudo rm $file_pointer && sudo rm $file_pointer.png")) {
$message = "OK";
$statement = db_execute_query('DELETE FROM detections WHERE File_Name = :filename LIMIT 1', [':filename' => $actual_filename]);
} else {
$message = "Error";
}
//
$success = true;
//Message set above
$data_to_return = $statement;
return array('success' => $success, 'message' => $message, 'data' => $data_to_return);
}
/**
* Protects the specified file from deletion
*
* @param $mode string Set the mode to add (protect) to remove (unprotect) the specified filepath from protection
* @param $filename_to_protect string The filename of the detection to protect e.g 2023-04-25/Pacific_Koel/Pacific_Koel-76-2023-04-25-birdnet-RTSP_2-16:24:05.mp3
* @return array
*/
function protectDetectionFromDeletion($mode, $filename_to_protect)
{
global $api_incl;
$success = false;
$message = '';
$data_to_return = null;
$mode = strtolower($mode);
$scripts_dir = getDirectory('scripts');
$disk_check_exclude_file = $scripts_dir . "/disk_check_exclude.txt";
//Initially create the file if it doesn't exist
if (!file_exists($disk_check_exclude_file)) {
file_put_contents($disk_check_exclude_file, "##start\n##end\n");
}
if ($mode == "protect") {
// load the data and delete the line from the array which is ##end,
// so that all excluded files sit between the ##start and ##end tags
$lines = file($disk_check_exclude_file);
$last = sizeof($lines) - 1;
unset($lines[$last]);
//Open the file and truncate contents
if (($myfile = fopen($disk_check_exclude_file, "w")) !== false) {
$txt = $filename_to_protect;
//Write all existing lines
fwrite($myfile, implode("", $lines));
//Write out the lines for the file were trying to exclude
fwrite($myfile, $txt . "\n");
fwrite($myfile, $txt . ".png\n");
//Insert the end tag again to make it the last line
fwrite($myfile, "##end\n");
fclose($myfile);
//
$success = true;
$message = "OK";
} else {
$success = false;
$message = "Unable to open file! " . $disk_check_exclude_file;
}
} else if ($mode == "unprotect") {
$lines = file($disk_check_exclude_file);
$search = $filename_to_protect;
$result = '';
foreach ($lines as $line) {
if (stripos($line, $search) === false && stripos($line, $search . ".png") === false) {
$result .= $line;
}
}
if (file_put_contents($disk_check_exclude_file, $result) !== false) {
$success = true;
$message = "OK";
} else {
$success = true;
$message = "Failed writing contents after removing file from protection, back to file $disk_check_exclude_file";
}
}
return array('success' => $success, 'message' => $message, 'data' => $data_to_return);
}
/**
* Frequency shifts the specified audio file to aid listening for hearing impaired people
*
* @param $filename
* @param $performShift
* @return array
*/
function frequencyShiftDetectionAudio($filename, $performShift = null)
{
global $config, $api_incl;
$success = false;
$message = '';
$data_to_return = null;
//Check authentication before going any further
//the front end would have done this check already and such should pass
authenticateUser();
$shifted_path = getDirectory('shifted_audio') . '/';
$pp = pathinfo($filename);
$dir = $pp['dirname'];
$fn = $pp['filename'];
$ext = $pp['extension'];
$pi = getDirectory('extracted_bydate') . '/';
if (isset($performShift) && $performShift == true) {
$freqshift_tool = $config['FREQSHIFT_TOOL'];
if ($freqshift_tool == "ffmpeg") {
$cmd = "sudo /usr/bin/nohup /usr/bin/ffmpeg -y -i " . escapeshellarg($pi . $filename) . " -af \"rubberband=pitch=" . $config['FREQSHIFT_LO'] . "/" . $config['FREQSHIFT_HI'] . "\" " . escapeshellarg($shifted_path . $filename) . "";
shell_exec("sudo mkdir -p " . $shifted_path . $dir . " && " . $cmd);
} else if ($freqshift_tool == "sox") {
//linux.die.net/man/1/sox
$soxopt = "-q";
$soxpitch = $config['FREQSHIFT_PITCH'];
$cmd = "sudo /usr/bin/nohup /usr/bin/sox " . escapeshellarg($pi . $filename) . " " . escapeshellarg($shifted_path . $filename) . " pitch " . $soxopt . " " . $soxpitch;
shell_exec("sudo mkdir -p " . $shifted_path . $dir . " && " . $cmd);
}
} else {
$cmd = "sudo rm -f " . escapeshellarg($shifted_path . $filename);
shell_exec($cmd);
}
//
$success = true;
$message = "OK";
return array('success' => $success, 'message' => $message, 'data' => $data_to_return);
}
/**
* Adds the supplied Flickr Image Id to the list of blacklisted images
*
* @param $imageID
* @return array
*/
function blacklistFlickrImage($imageID)
{
$success = false;
$message = '';
$data_to_return = null;
$scripts_dir = getDirectory('scripts');
//Append the Flickr Image ID the blacklist file
if (($file_handle = fopen($scripts_dir . "/blacklisted_images.txt", 'a+')) !== false) {
fwrite($file_handle, $imageID . "\n");
fclose($file_handle);
//
$success = true;
$message = "Ok";
} else {
$success = false;
$message = "Failed";
}
return array('success' => $success, 'message' => $message, 'data' => $data_to_return);
}
/**
* Finds and returns a flicker image for the supplied detection, detection data must have the following columns Com_Name, Sci_Name, Date, Time, Confidence, File_Name
* getMostRecentDetections
*
* @param $detection_data
* @param bool $getAll
* @return string[][]
*/
function getFlickrImage($detection_data, $getAll = false)
{
global $config;
$success = false;
$message = '';
$data_to_return = null;
$flickr_data_to_return = array(
'Com_Name' => 'N/A',
'Sci_Name' => 'N/A',
'Date' => 'N/A',
'Time' => 'N/A',
'Confidence' => '0',
'File_Name' => 'N/A',
'Com_Name_clean' => 'N/A',
'Sci_Name_clean' => 'N/A',
'photos' => null,
'filename_path' => 'N/A',
'filename_formatted' => 'N/A'
);
$foundImage = false;
if (isset($detection_data)) {
$extracted_dir = getDirectory('extracted');
$home_dir = getDirectory('home');
//Setup out flickr image cache in the session
if (!isset($_SESSION['images'])) {
$_SESSION['images'] = [];
}
//Cleanup the Birds common and scientic name
$comname_clean = preg_replace('/ /', '_', $detection_data['Com_Name']);
$sciname_clean = preg_replace('/ /', '_', $detection_data['Sci_Name']);
$comname_clean = preg_replace('/\'/', '', $comname_clean);
$filename_path = "/By_Date/" . $detection_data['Date'] . "/" . $comname_clean . "/" . $detection_data['File_Name'];
$filename_formatted = $detection_data['Date'] . "/" . $comname_clean . "/" . $detection_data['File_Name'];
$args = "&license=2%2C3%2C4%2C5%2C6%2C9&orientation=square,portrait";
$comnameprefix = "%20bird";
if (!empty($config["FLICKR_API_KEY"])) {
if (!empty($config["FLICKR_FILTER_EMAIL"])) {
if (!isset($_SESSION["FLICKR_FILTER_EMAIL"])) {
unset($_SESSION['images']);
$_SESSION['FLICKR_FILTER_EMAIL'] = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.people.findByEmail&api_key=" . $config["FLICKR_API_KEY"] . "&find_email=" . $config["FLICKR_FILTER_EMAIL"] . "&format=json&nojsoncallback=1"), true)["user"]["nsid"];
}
$args = "&user_id=" . $_SESSION['FLICKR_FILTER_EMAIL'];
$comnameprefix = "";
} else {
if (isset($_SESSION["FLICKR_FILTER_EMAIL"])) {
unset($_SESSION["FLICKR_FILTER_EMAIL"]);
unset($_SESSION['images']);
}
}
// if we already searched flickr for this species before, use the previous image rather than doing an unneccesary api call
$key = array_search($comname_clean, array_column($_SESSION['images'], 0));
if ($key !== false && !$getAll) {
$flickr_image_data = $_SESSION['images'][$key];
$foundImage = true;
//Extract data out of the image data, either in our $_SESSION var already is recently pushed (new data)
$flickr_data_to_return['photos'][0]['image_url'] = $flickr_image_data[1];
$flickr_data_to_return['photos'][0]['photo_title'] = $flickr_image_data[2];
$flickr_data_to_return['photos'][0]['modal_text'] = $flickr_image_data[3];
$flickr_data_to_return['photos'][0]['author_link'] = $flickr_image_data[4];
$flickr_data_to_return['photos'][0]['license_url'] = $flickr_image_data[5];
} else {
// Get license information if we haven't already
if (empty($licenses_urls)) {
$licenses_url = "https://api.flickr.com/services/rest/?method=flickr.photos.licenses.getInfo&api_key=" . $config["FLICKR_API_KEY"] . "&format=json&nojsoncallback=1";
$licenses_response = file_get_contents($licenses_url);
$licenses_data = json_decode($licenses_response, true)["licenses"]["license"];
foreach ($licenses_data as $license) {
$license_id = $license["id"];
$license_name = $license["name"];
$license_url = $license["url"];
$licenses_urls[$license_id] = $license_url;
}
}
// only open the file once per script execution
if (!isset($lines)) {
$lines = file($home_dir . "/BirdNET-Pi/model/labels_flickr.txt");
}
// convert sci name to English name
foreach ($lines as $line) {
if (strpos($line, $detection_data['Sci_Name']) !== false) {
$sci_name_as_english_name = trim(explode("_", $line)[1]);
break;
}
}
// Read the blacklisted image ids from the file into an array
$blacklisted_ids = array_map('trim', file($home_dir . "/BirdNET-Pi/scripts/blacklisted_images.txt"));
// Make the API call
$flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=" . $config["FLICKR_API_KEY"] . "&text=" . str_replace(" ", "%20", $sci_name_as_english_name) . $comnameprefix . "&sort=relevance" . $args . "&per_page=5&media=photos&format=json&nojsoncallback=1"), true)["photos"]["photo"];
// Find the first photo that is not blacklisted or is not the specific blacklisted id
$photo = null;
foreach ($flickrjson as $flickrphoto) {
if ($flickrphoto["id"] !== "4892923285" && !in_array($flickrphoto["id"], $blacklisted_ids)) {
$photo = $flickrphoto;
$license_url = "https://api.flickr.com/services/rest/?method=flickr.photos.getInfo&api_key=" . $config["FLICKR_API_KEY"] . "&photo_id=" . $photo["id"] . "&format=json&nojsoncallback=1";
$license_response = file_get_contents($license_url);
$license_info = json_decode($license_response, true)["photo"]["license"];
$license_url = $licenses_urls[$license_info];
$modaltext = "https://flickr.com/photos/" . $photo["owner"] . "/" . $photo["id"];
$authorlink = "https://flickr.com/people/" . $photo["owner"];
$imageurl = 'https://farm' . $photo["farm"] . '.static.flickr.com/' . $photo["server"] . '/' . $photo["id"] . '_' . $photo["secret"] . '.jpg';
if (!$getAll) {
//Get single image then break the loop
array_push($_SESSION['images'], array($comname_clean, $imageurl, $photo["title"], $modaltext, $authorlink, $license_url));
$flickr_image_data = $_SESSION['images'][count($_SESSION['images']) - 1];
//Extract data out of the image data, either in our $_SESSION var already is recently pushed (new data)
$flickr_data_to_return['photos'][0]['image_url'] = $flickr_image_data[1];
$flickr_data_to_return['photos'][0]['photo_title'] = $flickr_image_data[2];
$flickr_data_to_return['photos'][0]['modal_text'] = $flickr_image_data[3];
$flickr_data_to_return['photos'][0]['author_link'] = $flickr_image_data[4];
$flickr_data_to_return['photos'][0]['license_url'] = $flickr_image_data[5];
//Break out of the loop
break;
} else {
//Get all images
//Extract data out of the image data, either in our $_SESSION var already is recently pushed (new data)
$flickr_data_to_return['photos'][(int)$flickrphoto["id"]]['image_url'] = $imageurl;
$flickr_data_to_return['photos'][(int)$flickrphoto["id"]]['photo_title'] = $photo["title"];
$flickr_data_to_return['photos'][(int)$flickrphoto["id"]]['modal_text'] = $modaltext;
$flickr_data_to_return['photos'][(int)$flickrphoto["id"]]['author_link'] = $authorlink;
$flickr_data_to_return['photos'][(int)$flickrphoto["id"]]['license_url'] = $license_url;
}
}
}
$foundImage = true;
$success = true;
$message = 'Flickr image data successfully downloaded';
}
//Fill out all other common data
$flickr_data_to_return['Com_Name'] = $detection_data['Com_Name'];
$flickr_data_to_return['Sci_Name'] = $detection_data['Sci_Name'];
$flickr_data_to_return['Date'] = $detection_data['Date'];
$flickr_data_to_return['Time'] = $detection_data['Time'];
$flickr_data_to_return['Confidence'] = array_key_exists('Confidence', $detection_data) ? $detection_data['Confidence'] : 0;
$flickr_data_to_return['File_Name'] = $detection_data['File_Name'];
$flickr_data_to_return['Com_Name_clean'] = $comname_clean;
$flickr_data_to_return['Sci_Name_clean'] = $sciname_clean;
$flickr_data_to_return['filename_path'] = $filename_path;
$flickr_data_to_return['filename_formatted'] = $filename_formatted;
}
}
return array('success' => $success, 'message' => $message, 'image_found' => $foundImage, 'data' => $flickr_data_to_return);
}
/**
* Returns the filename of todays species chart for the main page
*
* @return string
*/
function getChartString()
{
$myDate = date('Y-m-d');
$chart = "Combo-$myDate.png";
return $chart;
}
/**
* Returns the start and end dates for last week
*
* @return string[]
*/
function getLastWeekDates()
{
$startdate = strtotime('last sunday') - (7 * 86400);
$enddate = strtotime('last sunday') - (1 * 86400);
return ['start_date' => $startdate, 'end_date' => $enddate];
}
/**
* Change Database Language
*
* @param $model
* @param $language
* @return string|null
*/
function changeLanguage($model, $language)
{
global $config, $user;
//Re-parse the config just in case
parseConfig();
$result = "";
// Update Language settings only if a change is requested
if ($model != $config['MODEL'] || $language != $config['DATABASE_LANG']) {
if (strlen($language) == 2) {
$scripts_dir = getDirectory('scripts');
// Archive old language file
$result = syslog_shell_exec("cp -f " . getFilePath('labels.txt') . " " . getFilePath('labels.txt.old'), $user);
if ($model == "BirdNET_GLOBAL_3K_V2.3_Model_FP16") {
// Install new language label file
$result = syslog_shell_exec("chmod +x $scripts_dir/install_language_label_nm.sh && $scripts_dir/install_language_label_nm.sh -l $language", $user);
} else {
$result = syslog_shell_exec("$scripts_dir/install_language_label.sh -l $language", $user);
}
syslog(LOG_INFO, "Successfully changed language to '$language' and model to '$model'");
}
}
return $result;
}
/**
* Directory Path Helper, returns a full directory path for a supplied directory name e.g home, processed, extracted
*
* @param string $dir The directory to obtain a path for
* @return string
*/
function getDirectory($dir)
{
global $config, $home;
$dir = strtolower($dir);
if ($dir == "home") {
return $home;
} else if ($dir == "birdnet-pi" || $dir == "birdnet_pi") {
return getDirectory('home') . '/BirdNET-Pi';
} else if ($dir == "recs_dir" || $dir == "recordings_dir") {
$recs_dir_setting = $config['RECS_DIR'];
return str_replace('$HOME', getDirectory('home'), $recs_dir_setting);
} else if ($dir == "processed") {
$processed_dir_setting = $config['PROCESSED'];
return getDirectory('recs_dir') . str_replace('${RECS_DIR}', '', $processed_dir_setting);
} else if ($dir == "extracted") {
$extracted_dir_setting = $config['EXTRACTED'];
return getDirectory('recs_dir') . str_replace('${RECS_DIR}', '', $extracted_dir_setting);
} elseif ($dir == "extracted_bydate" || $dir == "extracted_by_date") {
return getDirectory('extracted') . '/By_Date';
} elseif ($dir == "shifted_audio" || $dir == "shifted_dir") {
return getDirectory('home') . '/BirdSongs/Extracted/By_Date/shifted';
} elseif ($dir == "database") {
// NOT USED
return getDirectory('birdnet_pi') . '/database';
} elseif ($dir == "config") {
// NOT USED
return getDirectory('birdnet_pi') . '/config';
} elseif ($dir == "models" || $dir == "model") {
return getDirectory('birdnet_pi') . '/model';
} elseif ($dir == "python3_ve") {
return getDirectory('birdnet_pi') . '/birdnet/bin';
} elseif ($dir == "scripts") {
return getDirectory('birdnet_pi') . '/scripts';
} elseif ($dir == "templates") {
return getDirectory('birdnet_pi') . '/templates';
} elseif ($dir == "web" || $dir == "www") {
return getDirectory('birdnet_pi') . '/homepage';
}
return "";
}
/**
* Returns the full filepath for the specified filename
*
* @param $filename
* @return string
*/
function getFilePath($filename)
{
if ($filename == "analyzing_now.txt") {
return getDirectory('birdnet_pi') . "/analyzing_now.txt";
//
} else if ($filename == "apprise.txt") {
return getDirectory('birdnet_pi') . "/apprise.txt";
//
} else if ($filename == "birdnet.conf") {
return getDirectory('birdnet_pi') . "/birdnet.conf";
//
} else if ($filename == "BirdDB.txt") {
return getDirectory('birdnet_pi') . "/BirdDB.txt";
//
} else if ($filename == "birds.db") {
return getDirectory('scripts') . "/birds.db";
//
} else if ($filename == "blacklisted_images.txt") {
return getDirectory('scripts') . "/blacklisted_images.txt";
//
} else if ($filename == "disk_check_exclude.txt") {
return getDirectory('scripts') . "/disk_check_exclude.txt";
//
} else if ($filename == "email_template") {
return getDirectory('scripts') . "/email_template";
//
} else if ($filename == "email_template2") {
return getDirectory('scripts') . "/email_template2";
//
} else if ($filename == "exclude_species_list.txt") {
return getDirectory('scripts') . "/exclude_species_list.txt";
//
} else if ($filename == "firstrun.ini") {
return getDirectory('home') . "/firstrun.ini";
//
} else if ($filename == "HUMAN.txt") {
return getDirectory('birdnet_pi') . "/HUMAN.txt";
//
} else if ($filename == "include_species_list.txt") {
return getDirectory('scripts') . "/include_species_list.txt";
//
} else if ($filename == "labels.txt" || $filename == "labels.txt.old") {
return getDirectory('model') . "/$filename";
//
} else if ($filename == "labels_flickr.txt") {
return getDirectory('model') . "/labels_flickr.txt";
//
} else if ($filename == "labels_l18n.zip") {
return getDirectory('model') . "/labels_l18n.zip";
//
} else if ($filename == "labels_lang.txt") {
return getDirectory('model') . "/labels_lang.txt";
//
} else if ($filename == "labels_nm.zip") {
return getDirectory('model') . "/labels_nm.zip";
//
} else if ($filename == "lastrun.txt") {
return getDirectory('scripts') . "/lastrun.txt";
//
} else if ($filename == "python3") {
return getDirectory('python3_ve') . "/python3 ";
//
} else if ($filename == "python3_appraise") {
return getDirectory('python3_ve') . "/apprise ";
//
} else if ($filename == "species.py") {
return getDirectory('scripts') . "/species.py";
//
} else if ($filename == "thisrun.txt") {
return getDirectory('scripts') . "/thisrun.txt";
//
}
return "";
}
/**
* Updates the setting value for the supplied setting name, if the setting doesn't exist it will be created
*
* @param $setting_name string Setting Name e.g SITE_NAME, BIRDWEATHER_ID, MODEL etc
* @param $setting_value string|int Setting value - This value has to be exactly how it needs to be stored (same as what was used in preg_replace) and properly escaped eg "\"asetting"\"
* @param $post_save_command string CommaAnd to execute after saving
* @return void
*/
function saveSetting($setting_name, $setting_value, $post_save_command = null)
{
global $config;
//Check authentication before going any further
//the front end would have done this check already and such should pass
authenticateUser();
//Setting exists already, see if the value changed
if (array_key_exists($setting_name, $config)) {
//Strip any outer quotes from the setting value (e.g "$rec_card") and then test if it change how it was originally tested
//https://stackoverflow.com/questions/9734758/remove-quotes-from-start-and-end-of-string-in-php
$setting_value_clean = preg_replace('~^"?(.*?)"?$~', '$1', $setting_value);
if (strcmp($setting_value_clean, $config[$setting_name]) !== 0) {
$contents = file_get_contents(getFilePath('birdnet.conf'));
$contents2 = file_get_contents(getFilePath('thisrun.txt'));
//
if ($contents !== false && $contents2 !== false) {
//Update the value
$contents = preg_replace("/$setting_name=.*/", "$setting_name=$setting_value", $contents);
$contents2 = preg_replace("/$setting_name=.*/", "$setting_name=$setting_value", $contents2);
//Write all the data out again to the the respective files
// $fh = fopen("/etc/birdnet/birdnet.conf", "w");
$fh = fopen(getFilePath('birdnet.conf'), "w");
$fh2 = fopen(getFilePath('thisrun.txt'), "w");
if ($fh !== false && $fh2 !== false) {
fwrite($fh, $contents);
fwrite($fh2, $contents2);
//Check if we need to execute a command after saving
if (isset($post_save_command)) {
sleep(1);
if (!is_array($post_save_command)) {
executeSysCommand($post_save_command);
} else {
//array of commands
foreach ($post_save_command as $single_command) {
executeSysCommand($single_command);
}
}
}
//Reload the settings to update our global $config variable
parseConfig();
}
}
}
} else {
//Create the setting in the setting file
shell_exec('sudo echo "' . $setting_name . '=' . $setting_value . '" >> ' . getFilePath('birdnet.conf'));
//also update this run txt file
shell_exec('sudo echo "' . $setting_name . '=' . $setting_value . '" >> ' . getFilePath('thisrun.txt'));
//Check if we need to execute a command after saving
if (isset($post_save_command)) {
sleep(1);
if (!is_array($post_save_command)) {
executeSysCommand($post_save_command);
} else {
//array of commands
foreach ($post_save_command as $single_command) {
executeSysCommand($single_command);
}
}
}
//Reload the settings to update our global $config variable
parseConfig();
}
}
/**
* Returns the value for the supplied setting name
*
* @param $setting_name
* @return mixed|null
*/
function getSetting($setting_name)
{
global $config;
$setting_value = null;
if (array_key_exists($setting_name, $config)) {
$setting_value = $config[$setting_name];
}
return $setting_value;
}
/**
* Returns the username of the user with User ID 1000
*
* @return string
*/
function getUser()
{
global $user;
return $user;
}
/**
* Executes commands against the system through a shorthand command type
*
* @param $command_type string
* @param $extra_data_to_pass string|[] Data or params that will be passed through to the command
* @return false|string|null
*/
function executeSysCommand($command_type, $extra_data_to_pass = null)
{
global $user, $home;
//Check authentication before going any further
//the front end would have done this check already and such should pass
authenticateUser();
$command_type = strtolower($command_type);
$result = null;
if ($command_type == "appraise_notification") {
$result = shell_exec(getFilePath('python3_appraise') . " -vv --plugin-path " . $home . "/.apprise/plugins " . " -t '" . escapeshellcmd($extra_data_to_pass['title']) . "' -b '" . escapeshellcmd($extra_data_to_pass['body']) . "' " . $extra_data_to_pass['attach'] . " " . $extra_data_to_pass['cf'] . " ");
//
} else if ($command_type == "current_timezone") {
$result = shell_exec("cat /etc/timezone");
//
} else if ($command_type == "disable_ntp") {
$result = shell_exec("sudo timedatectl set-ntp false");
//
} else if ($command_type == "enable_ntp") {
$result = shell_exec("sudo timedatectl set-ntp true");
//
} else if ($command_type == "is_ntp_active") {
$result = shell_exec("sudo timedatectl | grep \"NTP service: active\"");
//
} else if ($command_type == "restart_php") {
$result = serviceMaintenance('restart php.service');
//
} else if ($command_type == "restart_services") {
syslog(LOG_INFO, "Restarting Services");
$result = serviceMaintenance('restart core.services');
//
} else if ($command_type == "restart birdnet_recording") {
$result = serviceMaintenance('restart birdnet_recording.service');
//
} else if ($command_type == "restart icecast2") {
$result = serviceMaintenance('restart icecast2.service');
//
} else if ($command_type == "restart livestream") {
$result = serviceMaintenance('restart livestream.service');
//
} else if ($command_type == "restart spectrogram_viewer") {
$result = serviceMaintenance('restart spectrogram_viewer.service');
//
} else if ($command_type == "set_date") {
$command = "sudo date -s '" . escapeshellcmd($extra_data_to_pass['date']) . " " . escapeshellcmd($extra_data_to_pass['time']) . "'";
$result = shell_exec($command);
//
} else if ($command_type == "set_timezone") {
$command = "sudo timedatectl set-timezone " . escapeshellcmd($extra_data_to_pass);
$result = shell_exec($command);
//
} else if ($command_type == "test_threshold") {
$command = "sudo -u $user " . getFilePath('python3') . ' ' . getFilePath('species.py') . " --threshold " . escapeshellcmd($extra_data_to_pass) . " 2>&1";
$result = shell_exec($command);
//
} else if ($command_type == "update_birdnet") {
$result = shell_exec("update_birdnet.sh");
//
} else if ($command_type == "reboot_birdnet") {
$result = shell_exec("sudo reboot");
//
} else if ($command_type == "shutdown_birdnet") {
$result = shell_exec("sudo shutdown now");
//
} else if ($command_type == "clear_all_data") {
$result = shell_exec("sudo clear_all_data.sh");
//
}
return $result;
}
/**
* Updates the caddy configuration, only ever called when BirdNET-Pi Password or Site URL is set
*
* @return void
*/
function update_caddyfile()
{
exec("sudo /usr/local/bin/update_caddyfile.sh > /dev/null 2>&1 &");
}
/**
* Performs tasks such as stop, restart, disable and enable on the supplied service
* input command must contain the service name and must contain a keyword that describes the action
* positioning does not matter
* eg. restart livestream.service, stop birdnet_recording.service
*
* @param $command String Command containing a keyword [stop,restart,disable,enable] and the service name
* @return false|string|null
*/
function serviceMaintenance($command)
{
$command = trim($command);
$result = "";
//Check authentication before going any further
//the front end would have done this check already and such should pass
authenticateUser();
///e.g $command = 'service stop livestream.service', match the service name
////// BIRDNET LOG SERVICE //////
if (strpos($command, 'birdnet_log.service') !== false) {
if (strpos($command, 'stop') !== false) {
$result = shell_exec('sudo systemctl stop birdnet_log.service 2>&1');
} else if (strpos($command, 'restart') !== false) {
$result = shell_exec('sudo systemctl restart birdnet_log.service 2>&1');
} else if (strpos($command, 'disable') !== false) {
$result = shell_exec('sudo systemctl disable --now birdnet_log.service 2>&1');
} else if (strpos($command, 'enable') !== false) {
$result = shell_exec('sudo systemctl enable --now birdnet_log.service 2>&1');
}
} ////// BIRDNET SERVER SERVICE //////
else if (strpos($command, 'birdnet_server.service') !== false) {
if (strpos($command, 'stop') !== false) {
$result = shell_exec('sudo systemctl stop birdnet_server.service 2>&1');
} else if (strpos($command, 'restart') !== false) {
$result = shell_exec('sudo systemctl restart birdnet_server.service 2>&1');
} else if (strpos($command, 'disable') !== false) {
$result = shell_exec('sudo systemctl disable --now birdnet_server.service 2>&1');
} else if (strpos($command, 'enable') !== false) {
$result = shell_exec('sudo systemctl enable --now birdnet_server.service 2>&1');
}
} ////// BIRDNET ANALYSIS SERVICE //////
else if (strpos($command, 'birdnet_analysis.service') !== false) {
if (strpos($command, 'stop') !== false) {
$result = shell_exec('sudo systemctl stop birdnet_analysis.service 2>&1');
} else if (strpos($command, 'restart') !== false) {
$result = shell_exec('sudo systemctl restart birdnet_analysis.service 2>&1');
} else if (strpos($command, 'disable') !== false) {
$result = shell_exec('sudo systemctl disable --now birdnet_analysis.service 2>&1');
} else if (strpos($command, 'enable') !== false) {
$result = shell_exec('sudo systemctl enable --now birdnet_analysis.service 2>&1');
}
} ////// BIRDNET STATS SERVICE //////
else if (strpos($command, 'birdnet_stats.service') !== false) {
if (strpos($command, 'stop') !== false) {
$result = shell_exec('sudo systemctl stop birdnet_stats.service 2>&1');
} else if (strpos($command, 'restart') !== false) {
$result = shell_exec('sudo systemctl restart birdnet_stats.service 2>&1');
} else if (strpos($command, 'disable') !== false) {
$result = shell_exec('sudo systemctl disable --now birdnet_stats.service 2>&1');
} else if (strpos($command, 'enable') !== false) {
$result = shell_exec('sudo systemctl enable --now birdnet_stats.service 2>&1');
}
} ////// BIRDNET RECORDING SERVICE //////
else if (strpos($command, 'birdnet_recording.service') !== false) {
if (strpos($command, 'stop') !== false) {
$result = shell_exec('sudo systemctl stop birdnet_recording.service 2>&1');
} else if (strpos($command, 'restart') !== false) {
$result = shell_exec('sudo systemctl restart birdnet_recording.service 2>&1');
} else if (strpos($command, 'disable') !== false) {
$result = shell_exec('sudo systemctl disable --now birdnet_recording.service 2>&1');
} else if (strpos($command, 'enable') !== false) {
$result = shell_exec('sudo systemctl enable --now birdnet_recording.service 2>&1');
}
} ////// LIVESTREAM SERVICE //////
else if (strpos($command, 'livestream.service') !== false) {
//Check which keyword exists
if (strpos($command, 'stop') !== false) {
$result = shell_exec('sudo systemctl stop livestream.service && sudo systemctl stop icecast2.service 2>&1');
} else if (strpos($command, 'restart') !== false) {
$result = shell_exec('sudo systemctl restart livestream.service && sudo systemctl restart icecast2.service 2>&1');
} else if (strpos($command, 'disable') !== false) {
$result = shell_exec('sudo systemctl disable --now livestream.service && sudo systemctl disable icecast2 && sudo systemctl stop icecast2.service 2>&1');
} else if (strpos($command, 'enable') !== false) {
$result = shell_exec('sudo systemctl enable icecast2 && sudo systemctl start icecast2.service && sudo systemctl enable --now livestream.service 2>&1');
}
} ////// WEB TERMINAL SERVICE //////
else if (strpos($command, 'web_terminal.service') !== false) {
if (strpos($command, 'stop') !== false) {
$result = shell_exec('sudo systemctl stop web_terminal.service 2>&1');
} else if (strpos($command, 'restart') !== false) {
$result = shell_exec('sudo systemctl restart web_terminal.service 2>&1');
} else if (strpos($command, 'disable') !== false) {
$result = shell_exec('sudo systemctl disable --now web_terminal.service 2>&1');
} else if (strpos($command, 'enable') !== false) {
$result = shell_exec('sudo systemctl enable --now web_terminal.service 2>&1');
}
} ////// EXTRACTION SERVICE //////
else if (strpos($command, 'extraction.service') !== false) {
if (strpos($command, 'stop') !== false) {
$result = shell_exec('sudo systemctl stop extraction.service 2>&1');
} else if (strpos($command, 'restart') !== false) {
$result = shell_exec('sudo systemctl restart extraction.service 2>&1');
} else if (strpos($command, 'disable') !== false) {
$result = shell_exec('sudo systemctl disable --now extraction.service 2>&1');
} else if (strpos($command, 'enable') !== false) {
$result = shell_exec('sudo systemctl enable --now extraction.service 2>&1');
}
}////// CHART VIEWER SERVICE //////
else if (strpos($command, 'chart_viewer.service') !== false) {
if (strpos($command, 'stop') !== false) {
$result = shell_exec('sudo systemctl stop chart_viewer.service 2>&1');
} else if (strpos($command, 'restart') !== false) {
$result = shell_exec('sudo systemctl restart chart_viewer.service 2>&1');
} else if (strpos($command, 'disable') !== false) {
$result = shell_exec('sudo systemctl disable --now chart_viewer.service 2>&1');
} else if (strpos($command, 'enable') !== false) {
$result = shell_exec('sudo systemctl enable --now chart_viewer.service 2>&1');
}
} ////// SPECTROGRAM VIEWER SERVICE //////
else if (strpos($command, 'spectrogram_viewer.service') !== false) {
if (strpos($command, 'stop') !== false) {
$result = shell_exec('sudo systemctl stop spectrogram_viewer.service 2>&1');
} else if (strpos($command, 'restart') !== false) {
$result = shell_exec('sudo systemctl restart spectrogram_viewer.service 2>&1');
} else if (strpos($command, 'disable') !== false) {
$result = shell_exec('sudo systemctl disable --now spectrogram_viewer.service 2>&1');
} else if (strpos($command, 'enable') !== false) {
$result = shell_exec('sudo systemctl enable --now spectrogram_viewer.service 2>&1');
}
} ////// CORE SERVICES //////
else if ((strpos($command, 'core.services') !== false) || (strpos($command, 'core services') !== false)) {
if (strpos($command, 'stop') !== false) {
$result = shell_exec('stop_core_services.sh 2>&1');
} else if (strpos($command, 'restart') !== false) {
$result = shell_exec('restart_services.sh 2>&1');
}
} ////// PHP FPM //////
else if (strpos($command, 'php.service') !== false) {
if (strpos($command, 'restart') !== false) {
$result = shell_exec("sudo service php7.4-fpm restart");
}
}
return $result;
}
/**
* Returns a the supplied timestamp as sometime human-readable
* from https://stackoverflow.com/questions/2690504/php-producing-relative-date-time-from-timestamps
*
* @param $ts
* @return false|string
*/
function relativeTime($ts)
{
if (!ctype_digit($ts))
$ts = strtotime($ts);
$diff = time() - $ts;
if ($diff == 0)
return 'now';
elseif ($diff > 0) {
$day_diff = floor($diff / 86400);
if ($day_diff == 0) {
if ($diff < 60) return 'just now';
if ($diff < 120) return '1 minute ago';
if ($diff < 3600) return floor($diff / 60) . ' minutes ago';
if ($diff < 7200) return '1 hour ago';
if ($diff < 86400) return floor($diff / 3600) . ' hours ago';
}
if ($day_diff == 1) return 'Yesterday';
if ($day_diff < 7) return $day_diff . ' days ago';
if ($day_diff < 31) return ceil($day_diff / 7) . ' weeks ago';
if ($day_diff < 60) return 'last month';
return date('F Y', $ts);
} else {
$diff = abs($diff);
$day_diff = floor($diff / 86400);
if ($day_diff == 0) {
if ($diff < 120) return 'in a minute';
if ($diff < 3600) return 'in ' . floor($diff / 60) . ' minutes';
if ($diff < 7200) return 'in an hour';
if ($diff < 86400) return 'in ' . floor($diff / 3600) . ' hours';
}
if ($day_diff == 1) return 'Tomorrow';
if ($day_diff < 4) return date('l', $ts);
if ($day_diff < 7 + (7 - date('w'))) return 'next week';
if (ceil($day_diff / 7) < 4) return 'in ' . ceil($day_diff / 7) . ' weeks';
if (date('n', $ts) == date('n') + 1) return 'next month';
return date('F Y', $ts);
}
}
/**
* Execute system command as SUDO and logs it's output in the syslog
*
* @param $cmd string Command to Execute
* @param $sudo_user string User to execute as
* @return string
*/
function syslog_shell_exec($cmd, $sudo_user = null)
{
$output = "";
//Check authentication before going any further
//the front end would have done this check already and such should pass
authenticateUser();
if ($sudo_user) {
$cmd = "sudo -u $sudo_user $cmd";
}
$output = shell_exec($cmd);
if (strlen($output) > 0) {
syslog(LOG_INFO, $output);
}
return $output;
}
/**
* Loads in configuration settings from either thisrun or firstrun.txt
*
* @return void
*/
function parseConfig()
{
global $config;
if (file_exists(getFilePath('thisrun.txt'))) {
$config = parse_ini_file(getFilePath('thisrun.txt'));
} elseif (file_exists(getFilePath('firstrun.txt'))) {
$config = parse_ini_file(getFilePath('firstrun.txt'));
}
}
/**
* Updates the apprise config file
*
* @param $apprise_config
* @return void
*/
function updateAppriseConfig($apprise_config)
{
if (isset($apprise_config)) {
$appriseconfig_config_file = fopen(getFilePath("apprise.txt"), "w");
fwrite($appriseconfig_config_file, $apprise_config);
}
}
/**
* Returns the apprise config in apprise.txt
* @return false|string
*/
function getAppriseConfig()
{
if (file_exists(getFilePath('apprise.txt'))) {
$apprise_config = file_get_contents(getFilePath('apprise.txt'));
} else {
$apprise_config = "";
}
return $apprise_config;
}
/**
* Return the linecount of labels.txt
* @return int
*/
function getLabelsCount()
{
return count(file(getFilePath('labels.txt')));
}
/**
* Returns the current git commit hash of the BirdNET-Pi respository
* @return false|string|null
*/
function getGitCurrentHash()
{
global $user;
return shell_exec("cd " . getDirectory('home') . "/BirdNET-Pi && sudo -u " . $user . " git rev-list --max-count=1 HEAD");
}
/**
* Runs git fetch against the BirdNET-Pi repository
* @return false|string|null
*/
function doGitFetch()
{
global $user;
return shell_exec("sudo -u" . $user . " git -C " . getDirectory('home') . "/BirdNET-Pi fetch 2>&1");
}
/**
* Get the git status of the BirdNET-Pi repository
* @return false|string|null
*/
function getGitStatus()
{
global $user;
$behind = 0;
//Fetch latest updates
doGitFetch();
$git_status = trim(shell_exec("sudo -u" . $user . " git -C " . getDirectory('home') . "/BirdNET-Pi status"));
//Extract the text numberof commits from 'Your branch is behind '....' by XX commits,
if (preg_match("/behind '.*?' by (\d+) commit(s?)\b/", $git_status, $matches)) {
$num_commits_behind = $matches[1];
$behind = $num_commits_behind;
}
if (preg_match('/\b(\d+)\b and \b(\d+)\b different commits each/', $git_status, $matches)) {
$num1 = (int)$matches[1];
$num2 = (int)$matches[2];
$sum = $num1 + $num2;
$behind = $sum;
}
return $behind;
}
/**
* Returns the status of the specified service
*
* @param $name string Name of the service
* @return string[]
*/
function getServiceStatus($name)
{
$message = '';
$status = '';
if ($name == "birdnet_server.service") {
$filesinproc = trim(shell_exec("ls " . getDirectory('home') . "/BirdSongs/Processed | wc -l"));
if ($filesinproc > 200) {
$message = "stalled - backlog of " . $filesinproc . " files in ~/BirdSongs/Processed/";
}
}
$status = shell_exec("sudo systemctl status " . $name . " | grep Active | grep ' active\| activating\|running\|waiting\|start'");
if (strlen($status) > 0) {
$message = "(active)";
} else {
$message = "(inactive)";
}
return ["status" => $status, "message" => $message];
}
/**
* Returns the boolean flag
* @return false
*/
function userIsAuthenticated()
{
global $USER_AUTHENTICATED;
return $USER_AUTHENTICATED;
}
/**
* When called authenticates the user using the built in PHP_AUTH
* This will automatically
*
* @return void
*/
function authenticateUser()
{
global $config, $USER_AUTHENTICATED;
parseConfig();
$USER_AUTHENTICATED = false;
$caddypwd = $config['CADDY_PWD'];
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
echo '<table><tr><td>You cannot edit the settings for this installation</td></tr></table>';
exit;
} else {
//Read the supplied details
$submittedpwd = $_SERVER['PHP_AUTH_PW'];
$submitteduser = $_SERVER['PHP_AUTH_USER'];
//
if ($submittedpwd !== $caddypwd || $submitteduser !== 'birdnet') {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
echo '<table><tr><td>You cannot edit the settings for this installation</td></tr></table>';
exit;
} else {
$USER_AUTHENTICATED = true;
}
}
}
/**
* Logging helper to accept a message and process it accordingly
* @param $message
* @param $level
* @return void
*/
function birdnet_error_log($message, $level = 'error')
{
$logfile_path = getDirectory('scripts') . "/web-ui-error.log";
if (!file_exists($logfile_path)) {
touch($logfile_path);
}
error_log($message . "\r\n", 3, $logfile_path);
}
register_shutdown_function('disconnect_from_birdsdb');