first commit
This commit is contained in:
823
include/reporting_functions.php
Executable file
823
include/reporting_functions.php
Executable file
@@ -0,0 +1,823 @@
|
||||
<?php
|
||||
# Reporting functions
|
||||
|
||||
/**
|
||||
* Retrieves the name of a report
|
||||
*
|
||||
* @param array $report The report array containing at least a 'name' key.
|
||||
* @return string The translated report name.
|
||||
*/
|
||||
function get_report_name($report)
|
||||
{
|
||||
# Translates or customizes the report name.
|
||||
$customName = hook('customreportname', '', array($report));
|
||||
if ($customName) {
|
||||
return $customName;
|
||||
}
|
||||
|
||||
return lang_or_i18n_get_translated($report["name"], "report-");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of reports from the database.
|
||||
*
|
||||
* This function queries the database for all reports and processes them by:
|
||||
* 1. Translating the report names using the `get_report_name` function.
|
||||
* 2. Checking if the reports contain date fields using `report_has_date`.
|
||||
* 3. Verifying if the reports have associated thumbnails using `report_has_thumbnail`.
|
||||
*
|
||||
* The reports are always listed in the same order, regardless of the language used.
|
||||
*
|
||||
* @return array An array of processed reports, each containing 'ref', 'name',
|
||||
* 'contains_date', and 'has_thumbnail' keys.
|
||||
*/
|
||||
function get_reports()
|
||||
{
|
||||
# Executes query.
|
||||
$r = ps_query("SELECT ref, `name`, `query`, support_non_correlated_sql FROM report ORDER BY name");
|
||||
|
||||
# Translates report names in the newly created array.
|
||||
$return = array();
|
||||
for ($n = 0; $n < count($r); $n++) {
|
||||
if (!hook('ignorereport', '', array($r[$n]))) {
|
||||
$r[$n]["name"] = get_report_name($r[$n]);
|
||||
$r[$n]["contains_date"] = report_has_date((string) $r[$n]["query"]);
|
||||
$r[$n]['has_thumbnail'] = report_has_thumbnail((string) $r[$n]["query"]);
|
||||
$return[] = $r[$n];
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* do_report - Runs the specified report. This is used in a number of ways:-
|
||||
* 1) Outputs an HTML table to screen ($download = false)
|
||||
* 2) Produces a CSV
|
||||
* - for direct download from team_report.php
|
||||
* - captured and saved as a CSV file if called by send_periodic_report_emails() and over 100 rows are returned
|
||||
*
|
||||
*
|
||||
* @param int $ref Report ID
|
||||
* @param mixed $from_y Start year (used for reprts with date placholders)
|
||||
* @param mixed $from_m Start month
|
||||
* @param mixed $from_d Start day
|
||||
* @param mixed $to_y End year
|
||||
* @param mixed $to_m To month
|
||||
* @param mixed $to_d To day
|
||||
* @param mixed $download Output as CSV attachment (default)/output directly to client
|
||||
* @param mixed $add_border Optional table border (not for download)
|
||||
* @param mixed $foremail Sending as email?
|
||||
* @param array $search_params Search parameters - {@see get_search_params()} - will run the report on the search
|
||||
* results and replace the '[non_correlated_sql]' placeholder with the search query.
|
||||
*
|
||||
* @return void | string | array Outputs CSV file, returns HTML table or returns an array with path to the CSV file, rows and filename
|
||||
*/
|
||||
function do_report($ref, $from_y, $from_m, $from_d, $to_y, $to_m, $to_d, $download = true, $add_border = false, $foremail = false, array $search_params = array())
|
||||
{
|
||||
# Run report with id $ref for the date range specified. Returns a result array.
|
||||
global $lang, $baseurl, $report_rows_attachment_limit;
|
||||
|
||||
$report = ps_query("SELECT ref, `name`, `query`, support_non_correlated_sql FROM report WHERE ref = ?", array("i",$ref));
|
||||
|
||||
if (count($report) < 1) {
|
||||
return $lang['error_generic'];
|
||||
}
|
||||
|
||||
$has_date_range = report_has_date($report[0]["query"]);
|
||||
$report = $report[0];
|
||||
$report['name'] = get_report_name($report);
|
||||
|
||||
if ($download || $foremail) {
|
||||
if ($has_date_range) {
|
||||
$filename = str_replace(array(" ","(",")","-","/",","), "_", $report["name"]) . "_" . $from_y . "_" . $from_m . "_" . $from_d . "_" . $lang["to"] . "_" . $to_y . "_" . $to_m . "_" . $to_d . ".csv";
|
||||
} else {
|
||||
$filename = str_replace(array(" ","(",")","-","/",","), "_", $report["name"]) . ".csv";
|
||||
}
|
||||
}
|
||||
|
||||
if ($results = hook("customreport", "", array($ref,$from_y,$from_m,$from_d,$to_y,$to_m,$to_d,$download,$add_border, $report))) {
|
||||
// Hook has created the $results array
|
||||
} else {
|
||||
// Generate report results normally
|
||||
$sql_parameters = array();
|
||||
$report_placeholders = [
|
||||
'[from-y]' => $from_y,
|
||||
'[from-m]' => $from_m,
|
||||
'[from-d]' => $from_d,
|
||||
'[to-y]' => $to_y,
|
||||
'[to-m]' => $to_m,
|
||||
'[to-d]' => $to_d,
|
||||
];
|
||||
if ((bool)$report['support_non_correlated_sql'] === true && !empty($search_params)) {
|
||||
// If report supports being run on search results, embed the non correlated sql necessary to feed the report
|
||||
$returned_search = do_search(
|
||||
$search_params['search'],
|
||||
$search_params['restypes'],
|
||||
$search_params['order_by'],
|
||||
$search_params['archive'],
|
||||
-1, # fetchrows
|
||||
$search_params['sort'],
|
||||
false, # access_override
|
||||
DEPRECATED_STARSEARCH,
|
||||
false, # ignore_filters
|
||||
false, # return_disk_usage
|
||||
$search_params['recentdaylimit'],
|
||||
false, # go
|
||||
false, # stats_logging
|
||||
true, # return_refs_only
|
||||
false, # editable_only
|
||||
true # returnsql
|
||||
);
|
||||
|
||||
if (!is_a($returned_search, "PreparedStatementQuery") || !is_string($returned_search->sql)) {
|
||||
debug("Invalid SQL returned by do_search(). Report cannot be generated");
|
||||
return "";
|
||||
}
|
||||
$sql_parameters = array_merge($sql_parameters, $returned_search->parameters);
|
||||
$report_placeholders[REPORT_PLACEHOLDER_NON_CORRELATED_SQL] = "(SELECT ncsql.ref FROM ({$returned_search->sql}) AS ncsql)";
|
||||
}
|
||||
|
||||
$sql = report_process_query_placeholders($report['query'], $report_placeholders);
|
||||
|
||||
db_set_connection_mode("read_only");
|
||||
$results = ps_query($sql, $sql_parameters);
|
||||
db_clear_connection_mode();
|
||||
}
|
||||
|
||||
$resultcount = count($results);
|
||||
if ($resultcount == 0) {
|
||||
// No point downloading as the resultant file will be empty
|
||||
$download = false;
|
||||
}
|
||||
|
||||
foreach ($results as &$result) {
|
||||
foreach ($result as $key => &$value) {
|
||||
# Merge translation strings if multiple in a single column
|
||||
if (substr($key, 0, 4) == "i18n") {
|
||||
$delimiter = substr($key, 4, strpos($key, "_") - 4);
|
||||
$value = implode("", i18n_merge_translations(explode($delimiter, (string)$value)));
|
||||
}
|
||||
}
|
||||
unset($value);
|
||||
}
|
||||
unset($result);
|
||||
|
||||
if ($download) {
|
||||
header("Content-type: application/octet-stream");
|
||||
header("Content-disposition: attachment; filename=\"" . $filename . "\"");
|
||||
}
|
||||
|
||||
if ($download || ($foremail && $resultcount > $report_rows_attachment_limit)) {
|
||||
if ($foremail) {
|
||||
ob_clean();
|
||||
ob_start();
|
||||
}
|
||||
for ($n = 0; $n < $resultcount; $n++) {
|
||||
$result = $results[$n];
|
||||
if ($n == 0) {
|
||||
$f = 0;
|
||||
foreach ($result as $key => $value) {
|
||||
$f++;
|
||||
if ($f > 1) {
|
||||
echo ",";
|
||||
}
|
||||
if (substr($key, 0, 4) == "i18n") {
|
||||
$key = substr($key, strpos($key, "_") + 1);
|
||||
}
|
||||
if ($key != "thumbnail") {
|
||||
echo "\"" . lang_or_i18n_get_translated($key, "columnheader-") . "\"";
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
$f = 0;
|
||||
foreach ($result as $key => $value) {
|
||||
$f++;
|
||||
if ($f > 1) {
|
||||
echo ",";
|
||||
}
|
||||
$custom = hook('customreportfield', '', array($result, $key, $value, $download));
|
||||
if ($custom !== false) {
|
||||
echo $custom;
|
||||
} elseif ($key != "thumbnail") {
|
||||
$value = lang_or_i18n_get_translated($value, "usergroup-");
|
||||
$value = str_replace('"', '""', $value); # escape double quotes
|
||||
if (substr($value, 0, 1) == ",") {
|
||||
$value = substr($value, 1);
|
||||
} # Remove comma prefix on dropdown / checkbox values
|
||||
echo "\"" . $value . "\"";
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
if ($foremail) {
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$unique_id = uniqid();
|
||||
$reportfile = get_temp_dir(false, "Reports") . "/Report_" . $unique_id . ".csv";
|
||||
file_put_contents($reportfile, $output);
|
||||
return array("file" => $reportfile,"filename" => $filename, "rows" => $resultcount);
|
||||
}
|
||||
} else {
|
||||
# Not downloading - output a table
|
||||
|
||||
// If report results are too big, display the first rows and notify user they should download it instead
|
||||
$output = '';
|
||||
if ($resultcount > $report_rows_attachment_limit) {
|
||||
$results = array_slice($results, 0, $report_rows_attachment_limit);
|
||||
|
||||
// Catch the error now and place it above the table in the output
|
||||
render_top_page_error_style($lang['team_report__err_report_too_long']);
|
||||
$output = ob_get_contents();
|
||||
ob_clean();
|
||||
ob_start();
|
||||
}
|
||||
|
||||
// Pre-render process: Process nodes search syntax (e.g @@228 or @@!223) and add a new column that contains the node list and their names
|
||||
if (isset($results[0]['search_string'])) {
|
||||
$results = process_node_search_syntax_to_names($results, 'search_string');
|
||||
}
|
||||
$border = "";
|
||||
if ($add_border) {
|
||||
$border = "border=\"1\"";
|
||||
}
|
||||
$output .= "<br /><h2>" . $report['name'] . "</h2><style>.InfoTable td {padding:5px;}</style><table $border class=\"InfoTable\">";
|
||||
for ($n = 0; $n < count($results); $n++) {
|
||||
$result = $results[$n];
|
||||
if ($n == 0) {
|
||||
$f = 0;
|
||||
$output .= "<tr>\r\n";
|
||||
foreach ($result as $key => $value) {
|
||||
$f++;
|
||||
if ($key == "thumbnail") {
|
||||
$output .= "<td><strong>Link</strong></td>\r\n";
|
||||
} else {
|
||||
if (substr($key, 0, 4) == "i18n") {
|
||||
$key = substr($key, strpos($key, "_") + 1);
|
||||
}
|
||||
$output .= "<td><strong>" . lang_or_i18n_get_translated($key, "columnheader-") . "</strong></td>\r\n";
|
||||
}
|
||||
}
|
||||
$output .= "</tr>\r\n";
|
||||
}
|
||||
$f = 0;
|
||||
$output .= "<tr>\r\n";
|
||||
foreach ($result as $key => $value) {
|
||||
$f++;
|
||||
if ($key == "thumbnail") {
|
||||
$thm_path = get_resource_path($value, true, "thm", false, "", -1, 1, false);
|
||||
if (!file_exists($thm_path)) {
|
||||
$thm_path = dirname(__DIR__) . "/gfx/no_preview/default_thm.png";
|
||||
} else {
|
||||
$thm_path = get_resource_path($value, true, "col", false, "", -1, 1, false);
|
||||
}
|
||||
|
||||
$output .= sprintf(
|
||||
"<td><a href=\"%s/?r=%s\" target=\"_blank\"><img src=\"data:image/%s;base64,%s\"></a></td>\r\n",
|
||||
$baseurl,
|
||||
$value,
|
||||
pathinfo($thm_path, PATHINFO_EXTENSION),
|
||||
base64_encode(file_get_contents($thm_path))
|
||||
);
|
||||
} else {
|
||||
$custom = hook('customreportfield', '', array($result, $key, $value, $download));
|
||||
if ($custom !== false) {
|
||||
$output .= $custom;
|
||||
} else {
|
||||
$output .= "<td>" . strip_tags_and_attributes(lang_or_i18n_get_translated($value, "usergroup-"), array("a"), ['href', 'target', 'rel', 'title']) . "</td>\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
$output .= "</tr>\r\n";
|
||||
}
|
||||
$output .= "</table>\r\n";
|
||||
if (count($results) == 0) {
|
||||
$output .= $lang["reportempty"];
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new automatic periodic e-mail report
|
||||
*
|
||||
*/
|
||||
function create_periodic_email($user, $report, $period, $email_days, array $user_groups, array $search_params)
|
||||
{
|
||||
if ($email_days < 1) {
|
||||
$email_days = 1; # Minimum email frequency is daily.
|
||||
}
|
||||
# Delete any matching rows for this report/period.
|
||||
$query = "DELETE FROM report_periodic_emails
|
||||
WHERE user = ?
|
||||
AND report = ?
|
||||
AND period = ?";
|
||||
$parameters = array("i",$user, "i",$report, "i",$period);
|
||||
ps_query($query, $parameters);
|
||||
|
||||
# Insert a new row.
|
||||
$query = "INSERT INTO report_periodic_emails
|
||||
(user, report, period, email_days, search_params)
|
||||
VALUES (?,?,?,?,?)";
|
||||
$parameters = array("i",$user, "i",$report, "i",$period, "i",$email_days,
|
||||
"s",json_encode($search_params, JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK));
|
||||
ps_query($query, $parameters);
|
||||
|
||||
$ref = sql_insert_id();
|
||||
|
||||
# Send to all users?
|
||||
if (
|
||||
checkperm('m')
|
||||
&& !empty($user_groups)
|
||||
) {
|
||||
$ugstring = implode(",", $user_groups);
|
||||
ps_query("UPDATE report_periodic_emails SET user_groups = ? WHERE ref = ?", array("s",$ugstring, "i",$ref));
|
||||
}
|
||||
|
||||
# Return
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends periodic report emails to users based on configured schedules.
|
||||
*
|
||||
* This function checks for any scheduled reports that need to be sent, either because they are
|
||||
* pending or overdue. It gathers the necessary user email addresses, generates the reports,
|
||||
* and sends them as emails with attachments if applicable.
|
||||
*
|
||||
* @param bool $echo_out Determines whether to output progress messages during processing.
|
||||
* @param bool $toemail Determines whether to send the reports via email.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function send_periodic_report_emails($echo_out = true, $toemail = true)
|
||||
{
|
||||
# For all configured periodic reports, send a mail if necessary.
|
||||
global $lang,$baseurl, $report_rows_zip_limit, $email_notify_usergroups, $userref;
|
||||
|
||||
if (is_process_lock("periodic_report_emails")) {
|
||||
echo " - periodic_report_emails process lock is in place. Skipping.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
set_process_lock("periodic_report_emails");
|
||||
|
||||
// Keep record of temporary CSV/ZIP files to delete after emails have been sent
|
||||
$deletefiles = array();
|
||||
$users = [];
|
||||
|
||||
# Query to return all 'pending' report e-mails, i.e. where we haven't sent one before OR one is now overdue.
|
||||
$query = "
|
||||
SELECT pe.ref,
|
||||
pe.user,
|
||||
pe.send_all_users,
|
||||
pe.user_groups,
|
||||
pe.report,
|
||||
pe.period,
|
||||
pe.email_days,
|
||||
pe.last_sent,
|
||||
pe.search_params,
|
||||
u.email,
|
||||
r.name
|
||||
FROM report_periodic_emails pe
|
||||
JOIN user u ON pe.user = u.ref
|
||||
JOIN report r ON pe.report = r.ref
|
||||
WHERE pe.last_sent IS NULL
|
||||
OR (date_add(date(pe.last_sent), INTERVAL pe.email_days DAY) <= date(now()) AND pe.email_days > 0);
|
||||
";
|
||||
$reports = ps_query($query);
|
||||
|
||||
foreach ($reports as $report) {
|
||||
$start = time() - (60 * 60 * 24 * $report["period"]);
|
||||
|
||||
$from_y = date("Y", $start);
|
||||
$from_m = date("m", $start);
|
||||
$from_d = date("d", $start);
|
||||
|
||||
$to_y = date("Y");
|
||||
$to_m = date("m");
|
||||
$to_d = date("d");
|
||||
|
||||
// Send e-mail reports to users belonging to the specific user groups
|
||||
if (empty($report['user_groups'])) {
|
||||
if ($report['send_all_users']) {
|
||||
// Send to all users is deprecated. Send to $email_notify_usergroups or Super Admin if not set
|
||||
if (!empty($email_notify_usergroups)) {
|
||||
foreach ($email_notify_usergroups as $usergroup) {
|
||||
if (get_usergroup($usergroup) !== false) {
|
||||
$addusers = get_users($usergroup, "", "u.username", false, -1, 1);
|
||||
$users = array_merge($users, $addusers);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$users = get_notification_users("SYSTEM_ADMIN");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$users = get_users($report['user_groups'], "", "u.username", false, -1, 1);
|
||||
}
|
||||
|
||||
// Always add original report creator
|
||||
$creator = get_user($report['user']);
|
||||
$users[] = $creator;
|
||||
$sentousers = [];
|
||||
if (isset($userref)) {
|
||||
// Store current user before emulating each to get report
|
||||
$saveduserref = $userref;
|
||||
}
|
||||
|
||||
// Get unsubscribed users
|
||||
$unsubscribed = ps_array(
|
||||
'SELECT user_id as `value`
|
||||
FROM report_periodic_emails_unsubscribe
|
||||
WHERE periodic_email_id = ?',
|
||||
["i",$report['ref']]
|
||||
);
|
||||
|
||||
$reportcache = null;
|
||||
foreach ($users as $user) {
|
||||
if (in_array($user["ref"], $unsubscribed) || in_array($user["ref"], $sentousers)) {
|
||||
// User has unsubscribed from this report or already been sent it
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check valid email
|
||||
$email = $user['email'];
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Construct and run the report
|
||||
// Emulate the receiving user so language text strings are translated and search results take into account any permissions and filters
|
||||
emulate_user($user["ref"]);
|
||||
$userref = $user["ref"];
|
||||
|
||||
// Translates the report name.
|
||||
$report["name"] = lang_or_i18n_get_translated($report["name"], "report-");
|
||||
|
||||
$search_params = (trim($report['search_params'] ?? "") !== '' ? json_decode($report['search_params'], true) : []);
|
||||
|
||||
$static_report = true; // If no dynamic search results are included then the same report results can be used for all recipients
|
||||
|
||||
if (!empty($search_params)) {
|
||||
$static_report = false; // Report may vary so cannot be cached
|
||||
}
|
||||
|
||||
# Generate report (table or CSV)
|
||||
if ($static_report && isset($reportcache)) {
|
||||
$output = $reportcache["output"];
|
||||
$reportfiles = $reportcache["reportfiles"];
|
||||
} else {
|
||||
$output = do_report($report["report"], $from_y, $from_m, $from_d, $to_y, $to_m, $to_d, false, true, $toemail, $search_params);
|
||||
|
||||
if (empty($output)) {
|
||||
// No data, maybe no access to search results
|
||||
$output = "<br/>" . $lang["reportempty"] . "<br/>";
|
||||
}
|
||||
$reportfiles = [];
|
||||
// If report is large, make it an attachment (requires $use_phpmailer=true)
|
||||
if (is_array($output) && isset($output["file"])) {
|
||||
$deletefiles[] = $output["file"];
|
||||
// Include the file as an attachment
|
||||
if ($output["rows"] > $report_rows_zip_limit) {
|
||||
// Convert to zip file
|
||||
$unique_id = uniqid();
|
||||
$zipfile = get_temp_dir(false, "Reports") . "/Report_" . $unique_id . ".zip";
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($zipfile, ZIPARCHIVE::CREATE);
|
||||
$zip->addFile($output["file"], $output["filename"]);
|
||||
|
||||
$zip->close();
|
||||
$deletefiles[] = $zipfile;
|
||||
$zipname = str_replace(".csv", ".zip", $output["filename"]);
|
||||
$reportfiles[$zipname] = $zipfile;
|
||||
} else {
|
||||
$reportfiles[$output["filename"]] = $output["file"];
|
||||
}
|
||||
}
|
||||
if ($static_report) {
|
||||
$reportcache["output"] = $output;
|
||||
$reportcache["reportfiles"] = $reportfiles;
|
||||
}
|
||||
}
|
||||
|
||||
// Formulate a title
|
||||
$title = $report["name"] . ": " . str_replace("?", $report["period"], $lang["lastndays"]);
|
||||
if (!empty($reportfiles)) {
|
||||
$output = str_replace("[report_title]", $title, $lang["report_periodic_email_report_attached"]);
|
||||
}
|
||||
|
||||
$unsubscribe_url = generateURL($baseurl, ["ur" => $report["ref"],"u" => $user["ref"]]);
|
||||
$unsubscribe_link = sprintf(
|
||||
"<br />%s<br /><a href=\"%s\" target=\"_blank\">%s</a>",
|
||||
$lang["unsubscribereport"],
|
||||
$unsubscribe_url,
|
||||
$unsubscribe_url
|
||||
);
|
||||
|
||||
if ($echo_out) {
|
||||
echo escape($lang["sendingreportto"]) . " " . $email . "<br />" . $output . $unsubscribe_link . "<br />";
|
||||
}
|
||||
|
||||
$delete_link = "";
|
||||
if ((int)$user['ref'] == (int)$report["user"]) {
|
||||
// Add a delete link to the report
|
||||
$delete_link = "<br />" . $lang["report_delete_periodic_email_link"] . "<br /><a href=\"" . $baseurl . "/?dr=" . $report["ref"] . "\" target=\"_blank\">" . $baseurl . "/?dr=" . $report["ref"] . "</a>";
|
||||
}
|
||||
send_mail($email, $title, $output . $delete_link . $unsubscribe_link, "", "", "", "", "", "", "", $reportfiles);
|
||||
$sentousers[] = $user['ref'];
|
||||
}
|
||||
|
||||
if (isset($saveduserref)) {
|
||||
$userref = $saveduserref;
|
||||
emulate_user($userref);
|
||||
}
|
||||
|
||||
# Mark as done.
|
||||
ps_query('UPDATE report_periodic_emails set last_sent = now() where ref = ?', array("i",$report['ref']));
|
||||
}
|
||||
|
||||
$GLOBALS["use_error_exception"] = true;
|
||||
foreach ($deletefiles as $deletefile) {
|
||||
try {
|
||||
unlink($deletefile);
|
||||
} catch (Exception $e) {
|
||||
debug("Unable to delete - file not found: " . $deletefile);
|
||||
}
|
||||
}
|
||||
unset($GLOBALS["use_error_exception"]);
|
||||
|
||||
clear_process_lock("periodic_report_emails");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a periodic report for the current user.
|
||||
*
|
||||
* This function removes the specified periodic report from the database for the user
|
||||
* and also clears any associated unsubscribe (opt out) records.
|
||||
*
|
||||
* @param int $ref The reference ID of the periodic report to delete.
|
||||
* @return bool Returns true upon successful deletion.
|
||||
*/
|
||||
function delete_periodic_report($ref)
|
||||
{
|
||||
global $userref;
|
||||
ps_query('DELETE FROM report_periodic_emails WHERE user = ? AND ref = ?', array("i",$userref, "i",$ref));
|
||||
ps_query('DELETE FROM report_periodic_emails_unsubscribe WHERE periodic_email_id = ?', array("i", $ref));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes a user from a specified periodic report.
|
||||
*
|
||||
* This function inserts a record into the unsubscribe table, preventing the specified user
|
||||
* from receiving future emails related to the given periodic report.
|
||||
*
|
||||
* @param int $user_id The ID of the user to unsubscribe.
|
||||
* @param int $periodic_email_id The ID of the periodic email report to unsubscribe from.
|
||||
* @return bool Returns true upon successful unsubscription.
|
||||
*/
|
||||
function unsubscribe_user_from_periodic_report($user_id, $periodic_email_id)
|
||||
{
|
||||
$query = 'INSERT INTO report_periodic_emails_unsubscribe
|
||||
(user_id, periodic_email_id)
|
||||
VALUES (?, ?)';
|
||||
ps_query($query, array("i",$user_id, "i",$periodic_email_id));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the translated version of an activity type.
|
||||
*
|
||||
* This function takes an activity type string, checks if a corresponding
|
||||
* translation exists in the global language array, and returns the
|
||||
* translated string if available. If no translation is found, it returns
|
||||
* the original activity type.
|
||||
*
|
||||
* @param string $activity_type The activity type in plain text English.
|
||||
* @return string The translated activity type if available; otherwise, the original activity type.
|
||||
*/
|
||||
function get_translated_activity_type($activity_type)
|
||||
{
|
||||
# Activity types are stored in plain text english in daily_stat. This function will use language strings to resolve a translated value where one is set.
|
||||
global $lang;
|
||||
$key = "stat-" . strtolower(str_replace(" ", "", $activity_type));
|
||||
if (!isset($lang[$key])) {
|
||||
return $activity_type;
|
||||
} else {
|
||||
return $lang[$key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the presence of date placeholders in a report's SQL query.
|
||||
*
|
||||
* @param string $query The report's SQL query.
|
||||
*
|
||||
* @return boolean Returns true if a date placeholder was found else false.
|
||||
*/
|
||||
function report_has_date(string $query)
|
||||
{
|
||||
$date_placeholders = array('[from-y]','[from-m]','[from-d]','[to-y]','[to-m]','[to-d]');
|
||||
$date_present = false;
|
||||
|
||||
foreach ($date_placeholders as $placeholder) {
|
||||
$position = strpos($query, $placeholder);
|
||||
if ($position !== false) {
|
||||
$date_present = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $date_present;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the presence of date placeholders in a report's sql query using the report's id.
|
||||
*
|
||||
* @param int $report Report id of the report to retrieve the query data from the report table.
|
||||
*
|
||||
* @return boolean Returns true if a date placeholder was found else false.
|
||||
*/
|
||||
function report_has_date_by_id(int $report)
|
||||
{
|
||||
$query = ps_value("SELECT `query` as value FROM report WHERE ref = ?", array("i",$report), 0);
|
||||
return report_has_date($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if report has a "thumbnail" column in its SQL query.
|
||||
*
|
||||
* @param ?string $query The reports' SQL query.
|
||||
*/
|
||||
function report_has_thumbnail(?string $query): bool
|
||||
{
|
||||
return preg_match('/(AS )*\'thumbnail\'/mi', (string) $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get report date range based on user input
|
||||
*
|
||||
* @param array $info Information about the period selection. See unit test for example input
|
||||
*/
|
||||
function report_process_period(array $info): array
|
||||
{
|
||||
$available_periods = array_merge($GLOBALS['reporting_periods_default'], [0, -1]);
|
||||
$period = isset($info['period']) && in_array($info['period'], $available_periods) ? $info['period'] : $available_periods[0];
|
||||
|
||||
// Specific number of days specified.
|
||||
if ($period == 0) {
|
||||
$period = (int) $info['period_days'] ?? 0;
|
||||
if ($period < 1) {
|
||||
$period = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Specific date range specified.
|
||||
if ($period == -1) {
|
||||
$from_y = $info['from-y'] ?? '';
|
||||
$from_m = $info['from-m'] ?? '';
|
||||
$from_d = $info['from-d'] ?? '';
|
||||
|
||||
$to_y = $info['to-y'] ?? '';
|
||||
$to_m = $info['to-m'] ?? '';
|
||||
$to_d = $info['to-d'] ?? '';
|
||||
}
|
||||
// Work out the FROM and TO range based on the provided period in days.
|
||||
else {
|
||||
$start = time() - (60 * 60 * 24 * $period);
|
||||
|
||||
$from_y = date('Y', $start);
|
||||
$from_m = date('m', $start);
|
||||
$from_d = date('d', $start);
|
||||
|
||||
$to_y = date('Y');
|
||||
$to_m = date('m');
|
||||
$to_d = date('d');
|
||||
}
|
||||
|
||||
return [
|
||||
'from_year' => $from_y,
|
||||
'from_month' => $from_m,
|
||||
'from_day' => $from_d,
|
||||
'to_year' => $to_y,
|
||||
'to_month' => $to_m,
|
||||
'to_day' => $to_d,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and replace a reports' query placeholders with their values.
|
||||
*
|
||||
* @param string $query Reports' SQL query
|
||||
* @param array $placeholders Map between a placeholder and its actual value
|
||||
*/
|
||||
function report_process_query_placeholders(string $query, array $placeholders): string
|
||||
{
|
||||
$default_placeholders = [
|
||||
'[title_field]' => $GLOBALS['view_title_field'],
|
||||
];
|
||||
$all_placeholders = array_merge($default_placeholders, $placeholders);
|
||||
|
||||
$sql = $query;
|
||||
foreach ($all_placeholders as $placeholder => $value) {
|
||||
$sql = str_replace($placeholder, $value, $sql);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the Javascript to build a pie chart in the canvas denoted by $id
|
||||
* $data must be in the following format
|
||||
* $data = array(
|
||||
* "slice_a label" => "slice_a value",
|
||||
* "slice_b label" => "slice_b value",
|
||||
* );
|
||||
*
|
||||
* @param string $id identifier for the canvas to render the chart in
|
||||
* @param array $data data to be rendered in the chart
|
||||
* @param string|null $total null will mean that the data is complete and an extra field is not required
|
||||
* a string can be used to denote the total value to pad the data to
|
||||
* @return void
|
||||
*/
|
||||
function render_pie_graph($id, $data, $total = null)
|
||||
{
|
||||
global $home_colour_style_override,$header_link_style_override;
|
||||
|
||||
$rt = 0;
|
||||
$labels = [];
|
||||
$values = [];
|
||||
foreach ($data as $row) {
|
||||
$rt += $row["c"];
|
||||
$values[ ] = $row["c"];
|
||||
$labels[] = $row["name"];
|
||||
}
|
||||
|
||||
if (!is_null($total) && $total > $rt) {
|
||||
# The total doesn't match, some rows were truncated, add an "Other".
|
||||
$values[] = $total - $rt;
|
||||
$labels[] = "Other";
|
||||
}
|
||||
|
||||
$labels = array_map(fn($v) => substr(json_encode($v), 1, -1), $labels);
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
// Setup Styling
|
||||
new Chart(document.getElementById('<?php echo escape($id) ?>'), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ['<?php echo implode("', '", $labels) ?>'],
|
||||
datasets: [
|
||||
{
|
||||
data: [<?php echo escape(implode(", ", $values)) ?>]
|
||||
}
|
||||
]
|
||||
},
|
||||
options: chartstyling<?php echo escape($id)?>,
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the Javascript to build a bar chart in the canvas denoted by $id
|
||||
* $data must be in the following format
|
||||
* $data = array(
|
||||
* "point_a x value" => "point_a y value",
|
||||
* "point_b x value" => "point_b y value",
|
||||
*
|
||||
* @param string $id identifier for the canvas to render the chart in
|
||||
* @param array $data data to be rendered in the chart
|
||||
* @return void
|
||||
*/
|
||||
function render_bar_graph(string $id, array $data)
|
||||
{
|
||||
$values = "";
|
||||
foreach ($data as $t => $c) {
|
||||
$values .= "{x: $t, y: $c },\n";
|
||||
}
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
new Chart(
|
||||
document.getElementById('<?php echo escape($id) ?>'),
|
||||
{
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
data: [<?php echo escape($values) ?>]
|
||||
}
|
||||
]
|
||||
},
|
||||
options: chartstyling<?php echo escape($id)?>,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<?php
|
||||
}
|
Reference in New Issue
Block a user