mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-11-07 05:03:00 -05:00
Looking for other instances of the CronTab.php fatal in the previous commit found eleven, in ten files, dating from 2019 to 2023. Each is a call to a logger function from the global namespace without the \ZM\ prefix, so each is a fatal the moment its branch runs: web/ajax/console.php, events.php, reports.php, add_monitors.php, status.php web/includes/actions/snapshot.php (two), donate.php web/includes/functions.php, session.php web/views/image.php They have survived this long because of where they sit. Every one is on an error or diagnostic path -- an invalid sort order, a zero byte download, a failed read lock -- so the call is only reached when something has already gone wrong, and then it turns a handled problem into a blank 500. That is the shape of the bug on the monitor save: the write had worked, and the thing that broke was the attempt to log. Verified before changing any of them that each file is in the global namespace, does not define the function itself and does not import it, so the call really does not resolve. Tests: tests/php/test_logger_calls_qualified.php, 16 checks. It tokenizes every php file under web/ and reports unqualified calls, with the function names read out of logger.php rather than listed, so a new one is covered the day it is added. web/api is skipped as cakephp with its own logging, and assets as third party. Php's own tokenizer rather than a regex, because the distinctions matter: a method call on an object that happens to have an Error() method is fine, as is a definition, a mention, a qualified call, the same call from inside namespace ZM, and the name appearing in a comment or a string. There is a check for each of those, since a scan that quietly matched nothing would pass the tree-wide check on its own. Verified the scan fails when the Debug() in CronTab.php is put back. Ran: tests/php/test_logger_calls_qualified.php (16 passed), the rest of tests/php and tests/js, npx eslint . (clean), utils/check-homoglyphs.py (clean). test_bandwidth_clamp.php fails on this box for an unrelated reason: it cannot read /etc/zm/conf.d/03custom.conf and the permission warning lands in its captured output. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JpiSWBmtQkR5bcgpHWY4ME
211 lines
5.8 KiB
PHP
211 lines
5.8 KiB
PHP
<?php
|
|
ini_set('display_errors', '');
|
|
$message = '';
|
|
$data = array();
|
|
|
|
//
|
|
// INITIALIZE AND CHECK SANITY
|
|
//
|
|
|
|
if (!canView('Events'))
|
|
$message = 'Insufficient permissions for user '.$user->Username().'<br/>';
|
|
|
|
if (empty($_REQUEST['task'])) {
|
|
$message = 'Must specify a task<br/>';
|
|
} else {
|
|
$task = $_REQUEST['task'];
|
|
}
|
|
|
|
if (empty($_REQUEST['ids'])) {
|
|
if (isset($_REQUEST['task']) && $_REQUEST['task'] != 'query')
|
|
$message = 'No id(s) supplied<br/>';
|
|
} else {
|
|
$ids = $_REQUEST['ids'];
|
|
}
|
|
|
|
if ($message) {
|
|
ajaxError($message);
|
|
return;
|
|
}
|
|
|
|
require_once('includes/Filter.php');
|
|
require_once('includes/Report.php');
|
|
|
|
// Search contains a user entered string to search on
|
|
$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
|
|
|
|
// Advanced search contains an array of "column name" => "search text" pairs
|
|
// Bootstrap table sends json_ecoded array, which we must decode
|
|
$advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array();
|
|
|
|
$order = 'ASC';
|
|
// Order specifies the sort direction, either asc or desc
|
|
if (isset($_REQUEST['order'])) {
|
|
if (strtolower($_REQUEST['order']) == 'asc') {
|
|
$order = 'ASC';
|
|
} else if (strtolower($_REQUEST['order']) == 'desc') {
|
|
$order = 'DESC';
|
|
} else {
|
|
\ZM\Warning('Invalid value for order ' . $_REQUEST['order']);
|
|
}
|
|
}
|
|
|
|
// Sort specifies the name of the column to sort on
|
|
$sort = (isset($_REQUEST['sort'])) ? $_REQUEST['sort'] : '';
|
|
|
|
// Offset specifies the starting row to return, used for pagination
|
|
$offset = 0;
|
|
if (isset($_REQUEST['offset'])) {
|
|
if ((!is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']))) {
|
|
ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']);
|
|
} else {
|
|
$offset = $_REQUEST['offset'];
|
|
}
|
|
}
|
|
|
|
// Limit specifies the number of rows to return
|
|
// Set the default to 0 for reports view, to prevent an issue with ALL pagination
|
|
$limit = 0;
|
|
if (isset($_REQUEST['limit'])) {
|
|
if ((!is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']))) {
|
|
ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']);
|
|
} else {
|
|
$limit = $_REQUEST['limit'];
|
|
}
|
|
}
|
|
|
|
//
|
|
// MAIN LOOP
|
|
//
|
|
|
|
switch ($task) {
|
|
case 'delete' :
|
|
if (!canEdit('Events')) {
|
|
ajaxError('Insufficient permissions for user '.$user->Username());
|
|
return;
|
|
}
|
|
foreach ($ids as $id) {
|
|
$message = deleteRequest($id);
|
|
if (count($message)) {
|
|
$data[] = $message;
|
|
}
|
|
}
|
|
break;
|
|
case 'query' :
|
|
$data = queryRequest($search, $advsearch, $sort, $offset, $order, $limit);
|
|
break;
|
|
default :
|
|
ZM\Fatal("Unrecognised task '$task'");
|
|
} // end switch task
|
|
|
|
ajaxResponse($data);
|
|
|
|
//
|
|
// FUNCTION DEFINITIONS
|
|
//
|
|
|
|
function deleteRequest($id) {
|
|
$message = array();
|
|
$report = new ZM\Report($id);
|
|
if ( !$report->Id() ) {
|
|
$message[] = array($id=>'Report not found.');
|
|
} else if (!$report->canEdit()) {
|
|
$message[] = array($id=>'You do not have permission to delete report '.$report->Id());
|
|
} else {
|
|
$report->delete();
|
|
}
|
|
|
|
return $message;
|
|
}
|
|
|
|
function queryRequest($search, $advsearch, $sort, $offset, $order, $limit) {
|
|
global $dateTimeFormatter;
|
|
$data = array(
|
|
'total' => 0,
|
|
'totalNotFiltered' => 0,
|
|
'rows' => array(),
|
|
'updated' => $dateTimeFormatter->format(time())
|
|
);
|
|
|
|
// Put server pagination code here
|
|
// The table we want our data from
|
|
$table = 'Reports';
|
|
|
|
// The names of the dB columns in the reports table we are interested in
|
|
$columns = array('Id', 'Name', 'FilterId', 'StartDateTime', 'EndDateTime', 'Interval');
|
|
|
|
if ($sort != '') {
|
|
if (!in_array($sort, $columns)) {
|
|
ZM\Error('Invalid sort field: ' . $sort);
|
|
$sort = '';
|
|
} else if ($sort == 'EndDateTime') {
|
|
if ($order == 'ASC') {
|
|
$sort = 'EndDateTime IS NULL, E.EndDateTime';
|
|
} else {
|
|
$sort = 'EndDateTime IS NOT NULL, E.EndDateTime';
|
|
}
|
|
}
|
|
}
|
|
|
|
$values = array();
|
|
$likes = array();
|
|
$where = '';
|
|
|
|
$col_str = '*';
|
|
$sql = 'SELECT ' .$col_str. ' FROM `Reports` '.$where.($sort?' ORDER BY '.$sort.' '.$order:'');
|
|
if ($limit) $sql .= ' LIMIT '.$limit;
|
|
|
|
$unfiltered_rows = array();
|
|
$ids = array();
|
|
|
|
ZM\Debug('Calling the following sql query: ' .$sql);
|
|
$query = dbQuery($sql, $values);
|
|
if (!$query) {
|
|
ajaxError(dbError($sql));
|
|
return;
|
|
}
|
|
while ($row = dbFetchNext($query)) {
|
|
$request = new ZM\Report($row);
|
|
$request->remove_from_cache();
|
|
$ids[] = $request->Id();
|
|
$unfiltered_rows[] = $row;
|
|
} # end foreach row
|
|
|
|
# Filter limits come before pagination limits.
|
|
if ($limit and ($limit > count($unfiltered_rows))) {
|
|
ZM\Debug('Filtering rows due to filter->limit '.count($unfiltered_rows).' limit: '.$limit);
|
|
$unfiltered_rows = array_slice($unfiltered_rows, 0, $limit);
|
|
}
|
|
|
|
ZM\Debug('Have ' . count($unfiltered_rows) . ' reports matching base filter.');
|
|
$filtered_rows = $unfiltered_rows;
|
|
|
|
if ($limit) {
|
|
ZM\Debug("Filtering rows due to limit " . count($filtered_rows)." offset: $offset limit: $limit");
|
|
$filtered_rows = array_slice($filtered_rows, $offset, $limit);
|
|
}
|
|
|
|
$returned_rows = array();
|
|
foreach ($filtered_rows as $row) {
|
|
$report = new ZM\Report($row);
|
|
|
|
$row['Name'] = validHtmlStr($row['Name']);
|
|
$row['StartDateTime'] = $row['StartDateTime'] ? $dateTimeFormatter->format(strtotime($row['StartDateTime'])) : null;
|
|
$row['EndDateTime'] = $row['EndDateTime'] ? $dateTimeFormatter->format(strtotime($row['EndDateTime'])) : null;
|
|
$returned_rows[] = $row;
|
|
} # end foreach row matching search
|
|
|
|
$data['rows'] = $returned_rows;
|
|
|
|
# totalNotFiltered must equal total, except when either search bar has been used
|
|
$data['totalNotFiltered'] = count($unfiltered_rows);
|
|
if ( $search != '' || count($advsearch) ) {
|
|
$data['total'] = count($filtered_rows);
|
|
} else {
|
|
$data['total'] = $data['totalNotFiltered'];
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
?>
|