diff --git a/config/menu/adminPages.default.php b/config/menu/adminPages.default.php
index 9d68b19edd..94e22de10e 100644
--- a/config/menu/adminPages.default.php
+++ b/config/menu/adminPages.default.php
@@ -16,20 +16,21 @@
* Do NOT change $menu variable name!
*/
-use src\Utils\Uri\SimpleRouter;
use src\Controllers\Admin\CacheSetAdminController;
+use src\Utils\Uri\SimpleRouter;
/** @var array $links OcConfig::$links is accessible in within this scope */
$menu = [
'mnu_reports' => '/admin_reports.php', // counters added in MainLayoutCtrl
- 'mnu_pendings' => '/viewpendings.php', // counters added in MainLayoutCtrl
+ 'mnu_pendings' => SimpleRouter::getLink('Admin.GeoCacheApprovalAdmin'), // counters added in MainLayoutCtrl
'mnu_octeamStats' => '/articles.php?page=cog',
'mnu_notFoundCaches' => '/admin_cachenotfound.php',
- 'mnu_searchUser' => SimpleRouter::getLink('Admin.UserAdmin','search'),
+ 'mnu_searchUser' => SimpleRouter::getLink('Admin.UserAdmin', 'search'),
'mnu_ocTeamNews' => SimpleRouter::getLink('News.NewsAdmin'),
'mnu_geoPathAdmin' => '/powerTrailCOG.php',
'mnu_abandonCacheSets' => SimpleRouter::getLink(
- CacheSetAdminController::class, 'cacheSetsToArchive'
+ CacheSetAdminController::class,
+ 'cacheSetsToArchive'
),
];
diff --git a/lib/languages/en.php b/lib/languages/en.php
index db24dde31a..b80dd2a032 100644
--- a/lib/languages/en.php
+++ b/lib/languages/en.php
@@ -3214,4 +3214,11 @@
'at_day' => 'NOT recommended at night',
'at_notinwinter' => 'NOT available during winter',
'at_allseasons' => 'Available all seasons',
+
+ 'cache_approval_refresh' => 'Refresh list',
+ 'cache_approval_refresh_time' => 'Last update',
+ 'cache_approval_nonamed' => 'no name',
+ 'cache_approval_no_caches' => 'No geocaches waiting to be accepted',
+ 'cache_approval_changed_time' => 'status changed',
+ 'cache_approval_cache_invalid' => 'Cache "%s" is not valid for approval actions',
];
diff --git a/public/css/style_screen.css b/public/css/style_screen.css
index 50e787ca4a..1f75b8af8b 100644
--- a/public/css/style_screen.css
+++ b/public/css/style_screen.css
@@ -1339,3 +1339,7 @@ tr.geoKretLog {
.float-right {
float: right;
}
+
+.hidden {
+ display: none;
+}
diff --git a/public/views/admin/geocacheApproval/geocache_approval.css b/public/views/admin/geocacheApproval/geocache_approval.css
new file mode 100644
index 0000000000..9b0105e1ad
--- /dev/null
+++ b/public/views/admin/geocacheApproval/geocache_approval.css
@@ -0,0 +1,147 @@
+.geocacheApproval-truncated {
+ display: inline-block;
+ width: 130px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+#geocacheApproval_info {
+ height: 2.2em;
+}
+
+.geocacheApproval-acceptedInfo {
+ font-weight: bold;
+ color: #007000;
+}
+
+.geocacheApproval-rejectedInfo {
+ font-weight: bold;
+ color: #550000;
+}
+
+.geocacheApproval-errorInfo {
+ font-weight: bold;
+ color: #A00000;
+}
+
+.geocacheApproval-icon {
+ float: left;
+ margin-right: 8px;
+}
+
+.geocacheApproval-hidden {
+ display: none;
+}
+
+.geocacheApproval-refreshBlock {
+ width: 97%;
+ clear: both;
+ padding: 3px 0px;
+}
+
+.geocacheApproval-refreshButton {
+ display: block;
+ float: left;
+ margin-bottom: 3px;
+ font-size: 1.2em;
+}
+
+.geocacheApproval-refreshInfo {
+ display: block;
+ float: right;
+}
+
+.geocacheApproval-emptyList {
+ text-align: center !important;
+ padding: 3px 0px !important;
+ font-weight: bold;
+}
+
+.geocacheApproval-region {
+ font-weight:bold;
+ font-size:10px;
+ color:blue;
+}
+
+.geocacheApproval-confirmDialog-noTitlebar .ui-dialog-titlebar {
+ display: none;
+}
+
+.geocacheApproval-confirmDialog-content {
+ text-align: center;
+}
+
+.geocacheApproval-confirmDialog-content2 {
+ display: inline-block;
+ font-size: 1.2em;
+ margin-left: auto;
+ margin-right: auto;
+ text-align: justify;
+}
+
+.geocacheApproval-confirmDialog-buttons {
+ padding-top: 3em;
+}
+
+.geocacheApproval-confirmDialog.ui-widget.ui-widget-content {
+ border: 1px solid #A0A0A0;
+}
+
+.geocacheApproval-confirmDialog .ui-dialog-buttonpane {
+ padding-right: .3em !important;
+}
+
+.geocacheApproval-confirmDialog .ui-dialog-buttonpane .ui-dialog-buttonset {
+ float: none !important;
+}
+
+.geocacheApproval-confirmDialog .ui-dialog-buttonpane .ui-dialog-buttonset button {
+ box-sizing: border-box;
+ padding: 6px 11px;
+ margin: 5px 0;
+ border-radius: 4px;
+ border: 1px solid;
+ font-size: 14px;
+ background: none;
+}
+
+.geocacheApproval-confirmDialog-cancelButton {
+ float: right !important;
+ color: #333 !important;
+ background-color: #fff !important;
+ border-color: #ccc !important;
+}
+
+.geocacheApproval-confirmDialog-cancelButton:hover {
+ color: #333 !important;
+ background-color: #e6e6e6 !important;
+ border-color: #adadad !important;
+}
+
+.geocacheApproval-confirmDialog-acceptButton,
+.geocacheApproval-confirmDialog-blockButton {
+ float: left !important;
+ color: #fff !important;
+}
+
+.geocacheApproval-confirmDialog-acceptButton {
+ background-color: #5cb85c !important;
+ border-color: #4cae4c !important;
+}
+
+.geocacheApproval-confirmDialog-acceptButton:hover {
+ background-color: #449d44 !important;
+ border-color: #398439 !important;
+}
+
+.geocacheApproval-confirmDialog-blockButton {
+ background-color: #d9534f !important;
+ border-color: #d43f3a !important;
+}
+
+.geocacheApproval-confirmDialog-blockButton:hover {
+ color: #fff !important;
+ background-color: #c9302c !important;
+ border-color: #ac2925 !important;
+}
diff --git a/public/views/admin/geocacheApproval/geocache_approval.js b/public/views/admin/geocacheApproval/geocache_approval.js
new file mode 100644
index 0000000000..fd1d022505
--- /dev/null
+++ b/public/views/admin/geocacheApproval/geocache_approval.js
@@ -0,0 +1,301 @@
+/**
+ * Removes html tags from given input string
+ */
+function strip_tags(input) {
+ return input !== null ? input.replace(/<[\s\S]*?>/g, '') : "";
+}
+
+/**
+ * Determines what style class to use for caches list row.
+ *
+ * rowData - a row resulted from API,
+ * daysBeforeAlert - maximum days between now and cache submission when alert is
+ * not set.
+ */
+function getApprovalTableRowStyle(rowData, daysBeforeAlert) {
+ result = "";
+
+ if (
+ rowData["assigned_user_id"] === null
+ && moment(rowData["date_created"]).isBefore(moment().subtract(5, 'd'))
+ ) {
+ result = "alert";
+ } else if (
+ rowData["assigned_user_id"] !== null
+ && currentUserId == rowData["assigned_user_id"]
+ ) {
+ result = "highlighted";
+ }
+
+ return result;
+}
+
+/**
+ * Returns predefined, translated string if given cache name is empty.
+ */
+function getNonEmptyCacheName(name) {
+ return name.replace(/\s/g,'') == '' ? cacheNonamedTrans : name;
+}
+
+/**
+ * Fills up html row contents with row data resulted from API.
+ *
+ * row - jQuery row (tr) object
+ * rowData - one row from API "data" result"
+ * bgClass - row background class name, applied to cells
+ */
+function fillRowWithData(row, rowData, bgClass) {
+ row.attr("cache_id", rowData['cache_id']);
+ row.find("td").addClass(bgClass);
+ row.find(".geocacheApproval-cacheName")
+ .attr("href", viewCacheLink + "?cacheid=" + rowData['cache_id'])
+ .text(getNonEmptyCacheName(rowData["cachename"]));
+ row.find(".geocacheApproval-userName")
+ .attr("href", "viewprofile.php?userid=" + rowData['user_id'])
+ .text(rowData["username"]);
+ row.find(".geocacheApproval-region")
+ .text(rowData["adm3"]);
+ row.find(".alertable")
+ .text(rowData["date_created"]);
+ row.find(".geocacheApproval-lastLogDate")
+ .text(rowData["last_log_date"]);
+ if (rowData['last_log_author'] !== null) {
+ row.find(".geocacheApproval-lastLogUserName")
+ .attr(
+ "href",
+ "viewprofile.php?userid=" + rowData['last_log_author']
+ )
+ .text(rowData["last_log_username"]);
+ }
+ if (rowData['last_log_id'] !== null) {
+ row.find(".geocacheApproval-lastLogText")
+ .attr("href", "viewlogs.php?logid=" + rowData['last_log_id'])
+ .attr("title", strip_tags(rowData['last_log_text']))
+ .text(strip_tags(rowData['last_log_text']));
+ }
+ row.find(".geocacheApproval-assignedUser")
+ .attr("href", "viewprofile.php?userid=" + rowData['assigned_user_id'])
+ .text(rowData['assigned_user_name']);
+}
+
+function assignCurrentUser(element) {
+ cacheAction(element, "assign");
+}
+
+function acceptCache(element) {
+ cacheAction(element, "accept");
+}
+
+function rejectCache(element) {
+ cacheAction(element, "reject");
+}
+
+/**
+ * Displays general error information on Api failure
+ */
+function showApiError(jqXHR, textStatus, errorThrown) {
+ console.log(textStatus);
+ console.log(errorThrown);
+ var tmpl = Handlebars.compile(
+ $("#geocacheApproval_generalErrorTemplate").html()
+ );
+ var templateData = {
+ error_thrown: errorThrown,
+ text_status: textStatus
+ };
+ $("#geocacheApproval_info").html(tmpl(templateData));
+}
+
+/**
+ * Performs chosen action on cache waiting for approval.
+ *
+ * element - jQuery "clicked" element (a)
+ * actionType - one of "assign", "accept", "reject"
+ */
+function cacheAction(element, actionType) {
+ var params = {
+ action: null,
+ successTpl: null,
+ failureTpl: null
+ };
+ if (actionType == "assign") {
+ params.action = "assign";
+ params.successTpl = "assigned";
+ params.failureTpl = "assign";
+ } else if (actionType == "accept") {
+ params.action = "accept";
+ params.successTpl = "accepted";
+ params.failureTpl = "accept";
+ } else if (actionType == "reject") {
+ params.action = "reject";
+ params.successTpl = "rejected";
+ params.failureTpl = "reject";
+ }
+ var parentRow = element.closest("tr");
+ var cacheId = parentRow.attr("cache_id");
+ $.get("/Admin.GeoCacheApprovalAdminApi/" + params.action + "/" + cacheId)
+ .done(function(result) {
+ if ("status" in result && result["status"] === "OK") {
+ var tmpl = Handlebars.compile(
+ $("#geocacheApproval_" + params.successTpl + "Template").html()
+ );
+ $("#geocacheApproval_info").html(tmpl(result));
+ } else if (
+ params.failureTpl != null
+ && "status" in result
+ && result["status"] === "ERROR"
+ ) {
+ var tmpl = Handlebars.compile(
+ $(
+ "#geocacheApproval_" + params.failureTpl + "ErrorTemplate"
+ ).html()
+ );
+ $("#geocacheApproval_info").html(tmpl(result));
+ }
+ })
+ .fail(showApiError)
+ .always(function() {
+ refreshWaitingTable();
+ });
+}
+
+/**
+ * Shows jQuery UI dialog with confirmation of accept/reject actions
+ *
+ * element - jQuery "clicked" element (a)
+ * templateId - id of template to create dialog from
+ * actionButtonText - text of "confirm" button
+ * actionOnConfirm - function/method to call on confirm button click
+ */
+function showConfirmDialog(
+ element, templateId, actionButtonText, actionButtonClass, actionOnConfirm
+) {
+ var tmpl = Handlebars.compile($("#" + templateId).html());
+ var parentRow = element.closest("tr");
+ var templateData = {
+ cache_id: parentRow.attr("cache_id"),
+ cache_name: parentRow.find(".geocacheApproval-cacheName").text(),
+ cache_owner: parentRow.find(".geocacheApproval-userName").text()
+ };
+
+ $("#geocacheApproval_confirmDialogTemplate").dialog({
+ dialogClass: "geocacheApproval-confirmDialog"
+ + " geocacheApproval-confirmDialog-noTitlebar",
+ resizable: false,
+ height: "auto",
+ minHeight: 10,
+ minWidth: 600,
+ modal: true,
+ buttons: [
+ {
+ text: actionButtonText,
+ class: actionButtonClass,
+ click: function() {
+ $(this).dialog("close");
+ actionOnConfirm();
+ }
+ },
+ {
+ text: cancelButtonText,
+ class: "geocacheApproval-confirmDialog-cancelButton",
+ click: function() {
+ $(this).dialog("close");
+ }
+ }
+ ],
+ open: function() {
+ $(this).html(tmpl(templateData));
+ }
+ });
+}
+
+/**
+ * Regenerates list of waiting caches, clearing existing and assigning new
+ * actions for corresponding links.
+ */
+function refreshWaitingTable() {
+ $.get("/Admin.GeoCacheApprovalAdminApi/getWaiting")
+ .done(function(result) {
+ var apprTable = $("#geocacheApproval_waitingTable")
+
+ // clear table and click handlers
+ apprTable.find(".geocacheApproval-row").remove();
+ $(".geocacheApproval-actionAccept").off('click');
+ $(".geocacheApproval-actionBlock").off('click');
+ $(".geocacheApproval-actionAssign").off('click');
+
+ if (result["data"].length > 0) {
+ var inReviewCount = 0;
+ $.each(result["data"], function(index, rowData) {
+ var bgClass = "bgcolor" + (2 - (index) % 2);
+ row = $(
+ "#geocacheApproval_rowTemplate .geocacheApproval-row"
+ ).clone(
+ true, true
+ );
+ row.addClass(getApprovalTableRowStyle(rowData));
+ fillRowWithData(row, rowData, bgClass);
+ apprTable.append(row);
+ if (rowData["assigned_user_id"] !== null) {
+ inReviewCount++;
+ }
+ });
+ $("#adminMenu_waitingForAssignee").text(
+ result["data"].length - inReviewCount
+ );
+ $("#adminMenu_newPendings").text(result["data"].length);
+ $("#adminMenu_newPendingsStats").removeClass("hidden");
+ $(".geocacheApproval-actionAccept").click(function(ev) {
+ ev.preventDefault();
+ var caller = $(this);
+ showConfirmDialog(
+ $(this),
+ "geocacheApproval_acceptTemplate",
+ acceptButtonText,
+ "geocacheApproval-confirmDialog-acceptButton",
+ function() {
+ acceptCache(caller);
+ }
+ );
+ });
+ $(".geocacheApproval-actionBlock").click(function(ev) {
+ ev.preventDefault();
+ var caller = $(this);
+ showConfirmDialog(
+ $(this),
+ "geocacheApproval_blockTemplate",
+ blockButtonText,
+ "geocacheApproval-confirmDialog-blockButton",
+ function() {
+ rejectCache(caller);
+ }
+ );
+ });
+ $(".geocacheApproval-actionAssign").click(function(ev) {
+ ev.preventDefault();
+ assignCurrentUser($(this));
+ });
+ } else {
+ row = $(
+ "#geocacheApproval_rowEmptyTemplate .geocacheApproval-row"
+ ).clone(
+ true, true
+ );
+ apprTable.append(row);
+ $("#adminMenu_waitingForAssignee").text(0);
+ $("#adminMenu_newPendings").text(0);
+ $("#adminMenu_newPendingsStats").addClass("hidden");
+ }
+ $("#geocacheApproval_refreshDatetime").text(result["updated"]);
+ })
+ .fail(showApiError);
+}
+
+$(document).ready(function() {
+ $("#geocacheApproval_refresh").click(function(ev) {
+ // clear error info if was visible previously
+ $("#geocacheApproval_info .geocacheApproval-errorInfo").html("");
+ refreshWaitingTable();
+ });
+ $("#geocacheApproval_refresh").trigger("click");
+});
diff --git a/resources/email/admin/approval_action_cache_activated.email.html b/resources/email/admin/approval_action_cache_activated.email.html
new file mode 100644
index 0000000000..1cfe045922
--- /dev/null
+++ b/resources/email/admin/approval_action_cache_activated.email.html
@@ -0,0 +1,9 @@
+
diff --git a/resources/email/admin/approval_action_cache_archived.email.html b/resources/email/admin/approval_action_cache_archived.email.html
new file mode 100644
index 0000000000..ebdb4de185
--- /dev/null
+++ b/resources/email/admin/approval_action_cache_archived.email.html
@@ -0,0 +1,9 @@
+
+
+ {{cacheArchived_02}}:
{cacheName} - {wp}
+ {{cacheArchived_03}}.
+
+ {{cacheArchived_04}}.
+
+ {{cacheArchived_05}}.
+
diff --git a/resources/email/oc_team_notify_new_cache.email.html b/resources/email/oc_team_notify_new_cache.email.html
index 5b259e9005..e171fe2c2a 100644
--- a/resources/email/oc_team_notify_new_cache.email.html
+++ b/resources/email/oc_team_notify_new_cache.email.html
@@ -4,5 +4,5 @@
{cachename} {ocTeamNewCache_03}.
{ocTeamNewCache_04}
- {ocTeamNewCache_06} {ocTeamNewCache_05}.
+ {ocTeamNewCache_06} {ocTeamNewCache_05}.
diff --git a/src/Controllers/Admin/GeoCacheApprovalAdminApiController.php b/src/Controllers/Admin/GeoCacheApprovalAdminApiController.php
new file mode 100644
index 0000000000..7c3c2a5e93
--- /dev/null
+++ b/src/Controllers/Admin/GeoCacheApprovalAdminApiController.php
@@ -0,0 +1,320 @@
+isUserLogged() || ! $this->loggedUser->hasOcTeamRole()) {
+ // this controller is accessible only for OCTeam
+ $this->ajaxErrorResponse(
+ 'Not authorized for this operation',
+ HttpCode::STATUS_UNAUTHORIZED
+ );
+ }
+ }
+
+ /**
+ * Retrieves list of caches waiting for approval
+ */
+ public function getWaiting()
+ {
+ $this->ajaxJsonResponse([
+ 'updated' => Formatter::date(OcDateTime::now(), true),
+ 'data' => GeoCacheApproval::getWaitingForApproval(),
+ ]);
+ }
+
+ /**
+ * Assigns current user (OC Team member) as a reviewer of give cache.
+ *
+ * @param int $cacheId id of the cache to assign user to
+ */
+ public function assign(int $cacheId)
+ {
+ $this->wrap($cacheId);
+ }
+
+ /**
+ * Accepts (approves) given cache. Current user is assigned as a reviewer,
+ * then cache status is updated to "not yet available", notification emails
+ * are sent to a cache owner and current user and admin note is added to the
+ * geocache log.
+ *
+ * @param int $cacheId id of the cache to accept
+ */
+ public function accept(int $cacheId)
+ {
+ $this->wrap(
+ $cacheId,
+ function (GeoCache $cache): array {
+ $cache->updateStatus(GeoCacheCommons::STATUS_NOTYETAVAILABLE);
+
+ // currently empty array, may be not empty in future
+ return [];
+ },
+ function (GeoCache $cache): array {
+ $this->sendApprovalActionMessages($cache);
+ GeoCacheLog::newLog(
+ $cache->getCacheId(),
+ $this->loggedUser->getUserId(),
+ GeoCacheLogCommons::LOGTYPE_ADMINNOTE,
+ htmlspecialchars(tr('viewPending_03'))
+ );
+
+ // currently empty array, may be not empty in future
+ return [];
+ }
+ );
+ }
+
+ /**
+ * Rejects (declines) given cache. Current user is assigned as a reviewer,
+ * then cache status is updated to "blocked", notification emails
+ * are sent to a cache owner and current user and admin note is added to the
+ * geocache log.
+ *
+ * @param int $cacheId id of the cache to reject
+ */
+ public function reject(int $cacheId)
+ {
+ $this->wrap(
+ $cacheId,
+ function (GeoCache $cache): array {
+ $cache->updateStatus(GeoCacheCommons::STATUS_BLOCKED);
+
+ // currently empty array, may be not empty in future
+ return [];
+ },
+ function (GeoCache $cache): array {
+ $this->sendApprovalActionMessages($cache, false);
+ GeoCacheLog::newLog(
+ $cache->getCacheId(),
+ $this->loggedUser->getUserId(),
+ GeoCacheLogCommons::LOGTYPE_ADMINNOTE,
+ htmlspecialchars(tr('viewPending_06'))
+ );
+
+ // currently empty array, may be not empty in future
+ return [];
+ }
+ );
+ }
+
+ /**
+ * A common wrapping function for "assign", "accept" and "reject" operations.
+ * Assigns current user as a reviewer for given cache, then fills up
+ * resulting array with data useful for calling client. Then calls a wrapped
+ * function, if provided, merging its result with resulting array. The
+ * resulting array is returned in ajax response.
+ *
+ * @param int $cacheId id of the cache to accept
+ * @param callable $wrappedLocked an optional function to call after
+ * assigning reviewer, but still inside
+ * the cache lock
+ * @param callable $wrappedUnLocked an optional function to call after
+ * assigning reviewer and after
+ * wrappedLocked, outside of lock
+ */
+ private function wrap(
+ int $cacheId,
+ callable $wrappedLocked = null,
+ callable $wrappedUnlocked = null
+ ) {
+ $resultData = [];
+
+ $cache = null;
+ $cacheAssigned = false;
+
+ // Prevents concurrent work on the same geocache
+ $lockHandle = Lock::tryLock(
+ __CLASS__ . '_cache_id_'
+ . (! empty($cacheId) ? $cacheId : 'no_cache'),
+ Lock::EXCLUSIVE
+ );
+
+ if ($lockHandle) {
+ $cache = GeoCache::fromCacheIdFactory($cacheId);
+
+ if (
+ ! empty($cache)
+ && $cache->getStatus() === GeoCacheCommons::STATUS_WAITAPPROVERS
+ ) {
+ GeoCacheApproval::assignUserToCase(
+ $cache,
+ $this->loggedUser
+ );
+ $resultData['cache_id'] = $cacheId;
+ $resultData['cache_name'] = $cache->getCacheName();
+ $resultData['cache_wp'] = $cache->getWaypointId();
+ $resultData['cache_owner'] = $cache->getOwner()->getUserName();
+ $resultData['assigned_id'] = $this->loggedUser->getUserId();
+ $resultData['assigned_username']
+ = $this->loggedUser->getUserName();
+ $cacheAssigned = true;
+
+ $resultData = $this->invokeWrapped(
+ $resultData,
+ $cache,
+ $wrappedLocked
+ );
+ }
+
+ Lock::unlock($lockHandle);
+ }
+
+ if ($cacheAssigned) {
+ $resultData = $this->invokeWrapped(
+ $resultData,
+ $cache,
+ $wrappedUnlocked
+ );
+ $resultData['updated'] = Formatter::date(
+ OcDateTime::now(),
+ true
+ );
+ } else {
+ $this->ajaxErrorResponse(
+ tr(
+ 'cache_approval_cache_invalid',
+ [
+ ! empty($cache)
+ ? $cache->getCacheName()
+ . ' - ' . $cache->getWaypointId()
+ : '',
+ ]
+ ),
+ HttpCode::STATUS_OK
+ );
+ }
+
+ $this->ajaxSuccessResponse(null, $resultData);
+ }
+
+ /**
+ * Invoked wrapped function if not empty, merging its result with
+ * $resultData
+ *
+ * @return array updated $resultData after invocation
+ */
+ private function invokeWrapped(
+ array $resultData,
+ GeoCache $cache,
+ callable $wrapped = null
+ ): array {
+ if (! empty($wrapped)) {
+ $wrappedResultData = $wrapped($cache);
+
+ if (! empty($wrappedResultData)) {
+ $resultData = array_merge(
+ $resultData,
+ $wrappedResultData
+ );
+ }
+ }
+
+ return $resultData;
+ }
+
+ /**
+ * Prepares a notification email for given user and operation.
+ *
+ * @param GeoCacche $cache a geocache to prepare message from
+ * @param bool $isAccepted true if operation is "accept", false for "reject"
+ */
+ private function prepareApprovalActionMessage(
+ GeoCache $cache,
+ bool $isAccepted = true
+ ): EmailFormatter {
+ $message = new EmailFormatter(
+ self::TEMPLATE_PATH . 'approval_action_cache_'
+ . ($isAccepted ? 'activated' : 'archived')
+ . '.email.html',
+ true
+ );
+ $message->setVariable(
+ 'absoluteServerUri',
+ OcConfig::getAbsolute_server_URI()
+ );
+ $message->setVariable('cacheName', $cache->getCacheName());
+ $message->setVariable('wp', $cache->getWaypointId());
+ $message->setVariable('viewCacheUrl', $cache->getCacheUrl());
+
+ if ($isAccepted) {
+ $message->setVariable(
+ 'editCacheUrl',
+ 'editcache.php?cacheid=' . $cache->getCacheId()
+ // TODO: IMHO editcache.php shouldn't be hardcoded
+ );
+ }
+
+ $message->addFooterAndHeader(
+ $cache->getOwner()->getUserName(),
+ false
+ );
+
+ return $message;
+ }
+
+ /**
+ * Sends notification emails: to geocache owner and a copy to current user
+ * (reviewer).
+ *
+ * @param GeoCacche $cache a geocache to send message about
+ * @param bool $isAccepted true if operation is "accept", false for "reject"
+ */
+ private function sendApprovalActionMessages(
+ GeoCache $cache,
+ bool $isAccepted = true
+ ) {
+ $message = $this->prepareApprovalActionMessage($cache, $isAccepted);
+
+ foreach ([$cache->getOwner(), $this->loggedUser] as $user) {
+ $email = new Email();
+ $email->addToAddr($user->getEmail());
+ $email->setFromAddr(OcConfig::getEmailAddrOcTeam());
+ $email->setReplyToAddr(OcConfig::getEmailAddrOcTeam());
+ $email->addSubjectPrefix(OcConfig::getEmailSubjectPrefix());
+ $email->setSubject(
+ tr($isAccepted ? 'viewPending_01' : 'viewPending_04')
+ . ': '
+ . $cache->getCacheName()
+ );
+ $email->setHtmlBody(
+ (
+ $user == $this->loggedUser
+ ? (
+ tr($isAccepted ? 'viewPending_02' : 'viewPending_05')
+ . ": \n"
+ )
+ : ''
+ )
+ . $message->getEmailContent()
+ );
+ $email->send();
+ }
+ }
+}
diff --git a/src/Controllers/Admin/GeoCacheApprovalAdminController.php b/src/Controllers/Admin/GeoCacheApprovalAdminController.php
new file mode 100644
index 0000000000..a0319905bd
--- /dev/null
+++ b/src/Controllers/Admin/GeoCacheApprovalAdminController.php
@@ -0,0 +1,50 @@
+redirectNotLoggedUsers();
+
+ if (! $this->loggedUser->hasOcTeamRole()) {
+ $this->view->redirect('/');
+ }
+ }
+
+ public function isCallableFromRouter(string $actionName): bool
+ {
+ // all public methods can be called by router
+ return true;
+ }
+
+ /**
+ * Initial, static view. The rest of operations are performed by API
+ */
+ public function index()
+ {
+ $this->view->loadJQuery();
+ $this->view->loadJQueryUI();
+ $this->view->addHeaderChunk('momentJs');
+ $this->view->addHeaderChunk('handlebarsJs');
+ $this->view->addLocalCss(
+ Uri::getLinkWithModificationTime(
+ '/views/admin/geocacheApproval/geocache_approval.css'
+ )
+ );
+ $this->view->addLocalJs(
+ Uri::getLinkWithModificationTime(
+ '/views/admin/geocacheApproval/geocache_approval.js'
+ )
+ );
+ $this->view->setTemplate('admin/geocacheApproval/geocacheApproval');
+ $this->view->setVar('currentUserId', $this->loggedUser->getUserId());
+ $this->view->buildView();
+ }
+}
diff --git a/src/Controllers/PageLayout/MainLayoutController.php b/src/Controllers/PageLayout/MainLayoutController.php
index f921db9fe1..32b0044f52 100644
--- a/src/Controllers/PageLayout/MainLayoutController.php
+++ b/src/Controllers/PageLayout/MainLayoutController.php
@@ -244,15 +244,19 @@ private function adminMenuHandler(&$key, &$url)
break;
case 'mnu_pendings':
- $new_pendings = GeoCacheApproval::getWaitingForApprovalCount();
-
- if ($new_pendings > 0) {
- $in_review_count = GeoCacheApproval::getInReviewCount();
- $waitingForAssigne = $new_pendings - $in_review_count;
- $key = tr($key) . " ({$waitingForAssigne}/{$new_pendings})";
- } else {
- $key = tr($key);
- }
+ $newPendings = GeoCacheApproval::getWaitingForApprovalCount();
+
+ $inReviewCount = GeoCacheApproval::getInReviewCount();
+ $waitingForAssigne = $newPendings - $inReviewCount;
+ $key
+ = tr($key)
+ . '';
break;
default:
$key = tr($key); // by default menu key is just a translation
diff --git a/src/Models/Admin/GeoCacheApproval.php b/src/Models/Admin/GeoCacheApproval.php
index 12fbffa7ab..0d494cd878 100644
--- a/src/Models/Admin/GeoCacheApproval.php
+++ b/src/Models/Admin/GeoCacheApproval.php
@@ -1,31 +1,225 @@
loadByCacheId($cacheId);
+ }
+ }
+
+ /**
+ * Factory
+ *
+ * @return GeoCacheApproval|null (null if geocache with given id is not found)
+ */
+ public static function fromCacheIdFactory(int $cacheId)
+ {
+ try {
+ return new self($cacheId);
+ } catch (Exception $e) {
+ return;
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ private function loadByCacheId(int $cacheId)
+ {
+ $s = $this->db->multiVariableQuery(
+ 'SELECT * FROM approval_status WHERE cache_id = :1 LIMIT 1',
+ $cacheId
+ );
+
+ $cacheApprovalDbRow = $this->db->dbResultFetch($s);
+
+ if (is_array($cacheApprovalDbRow)) {
+ $this->loadFromRow($cacheApprovalDbRow);
+ } else {
+ throw new Exception('Cache approval status not found');
+ }
+ }
+
+ /**
+ * Load object data based on DB data-row
+ *
+ * @throws Exception
+ */
+ private function loadFromRow(array $geocacheApprovalDbRow)
+ {
+ $this->cacheId = $geocacheApprovalDbRow['cache_id'];
+ $this->userId = $geocacheApprovalDbRow['user_id'];
+ $this->status = $geocacheApprovalDbRow['status'];
+
+ if (! empty($geocacheApprovalDbRow['date_approval'])) {
+ $this->dateApproval = new DateTime(
+ $geocacheApprovalDbRow['date_approval']
+ );
+ }
}
public static function getWaitingForApprovalCount()
{
return self::db()->multiVariableQueryValue(
- "SELECT COUNT(status) FROM caches WHERE status = :1 ",
- 0, GeoCacheCommons::STATUS_WAITAPPROVERS);
+ 'SELECT COUNT(status) FROM caches WHERE status = :1',
+ 0,
+ GeoCacheCommons::STATUS_WAITAPPROVERS
+ );
+ }
+
+ /**
+ * Retrieves all caches waiting for approval, including additional data
+ * needed to display in approval view table rows.
+ * @throws Exception
+ */
+ public static function getWaitingForApproval(): array
+ {
+ $stmt = self::db()->multiVariableQuery(
+ "SELECT
+ cache_owner.username AS username,
+ cache_owner.user_id AS user_id,
+ caches.cache_id AS cache_id,
+ caches.name AS cachename,
+ IFNULL(`cache_location`.`adm3`, '') AS `adm3`,
+ caches.date_created AS date_created,
+ last_log.id AS last_log_id,
+ last_log.date AS last_log_date,
+ last_log.user_id AS last_log_author,
+ log_author.username AS last_log_username,
+ last_log.text AS last_log_text,
+ assigned_user.user_id AS assigned_user_id,
+ assigned_user.username AS assigned_user_name
+ FROM
+ `caches`
+ LEFT JOIN `cache_location`
+ ON `caches`.`cache_id` = `cache_location`.`cache_id`
+ LEFT JOIN (
+ SELECT
+ id,
+ cache_id,
+ text,
+ user_id,
+ date
+ FROM
+ cache_logs logs
+ WHERE
+ date = (
+ SELECT
+ MAX(date)
+ FROM
+ cache_logs
+ WHERE
+ cache_id = logs.cache_id
+ )
+ ) AS last_log
+ ON caches.cache_id = last_log.cache_id
+ LEFT JOIN user AS cache_owner
+ ON caches.user_id = cache_owner.user_id
+ LEFT JOIN user AS log_author
+ ON last_log.user_id = log_author.user_id
+ LEFT JOIN approval_status
+ ON caches.cache_id = approval_status.cache_id
+ LEFT JOIN user AS assigned_user
+ ON approval_status.user_id = assigned_user.user_id
+ WHERE
+ caches.status = :1
+ GROUP BY
+ caches.cache_id
+ ORDER BY
+ caches.date_created DESC",
+ GeoCacheCommons::STATUS_WAITAPPROVERS
+ );
+
+ return self::db()->dbResultFetchAll($stmt);
}
public static function getInReviewCount()
{
return self::db()->multiVariableQueryValue(
- "SELECT COUNT(*)
+ 'SELECT COUNT(*)
FROM caches
JOIN approval_status USING(cache_id)
- WHERE caches.status = :1",
- 0, GeoCacheCommons::STATUS_WAITAPPROVERS);
+ WHERE caches.status = :1',
+ 0,
+ GeoCacheCommons::STATUS_WAITAPPROVERS
+ );
}
+ /**
+ * Factory
+ * @param int $cacheId
+ * @return GeoCacheApproval|null (null if no $returnInstance)
+ * @throws Exception
+ */
+ public static function assignUserToCase(
+ GeoCache $cache,
+ User $user,
+ bool $returnInstance = false
+ ) {
+ $result = null;
+
+ if (! empty($cache->getCacheId()) && ! empty($user->getUserId())) {
+ self::db()->multiVariableQuery(
+ 'INSERT INTO approval_status
+ (cache_id, user_id, status, date_approval)
+ VALUES (:1, :2, :3, NOW())
+ ON DUPLICATE KEY UPDATE user_id = :2',
+ $cache->getCacheId(),
+ $user->getUserId(),
+ self::STATUS_ASSIGNED
+ );
+
+ if ($returnInstance) {
+ $result = new self($cache->getCacheId());
+ }
+ }
+
+ return $result;
+ }
}
diff --git a/src/Views/admin/geocacheApproval/geocacheApproval.tpl.php b/src/Views/admin/geocacheApproval/geocacheApproval.tpl.php
new file mode 100644
index 0000000000..ad061aedcc
--- /dev/null
+++ b/src/Views/admin/geocacheApproval/geocacheApproval.tpl.php
@@ -0,0 +1,70 @@
+
+
+
+ {{pendings}}
+
+
+
+ {{cache_approval_refresh}}
+ {{cache_approval_refresh_time}}:
+
+
+
+ Cache
+ {{date_created}}
+ {{pendings_last_log}}
+ {{actions}}
+ {{assigned_to}}
+
+
+
+
+
+
+
+
+
+ -- {{cache_approval_no_caches}} --
+
+
+
+
+= $view->callSubTpl('/admin/geocacheApproval/geocacheApprovalMessages'); ?>
+
+
+
diff --git a/src/Views/admin/geocacheApproval/geocacheApprovalMessages.tpl.php b/src/Views/admin/geocacheApproval/geocacheApprovalMessages.tpl.php
new file mode 100644
index 0000000000..37c408689d
--- /dev/null
+++ b/src/Views/admin/geocacheApproval/geocacheApprovalMessages.tpl.php
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/chunks/momentJs.tpl.php b/src/Views/chunks/momentJs.tpl.php
new file mode 100644
index 0000000000..49b2931f81
--- /dev/null
+++ b/src/Views/chunks/momentJs.tpl.php
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
-.truncated {
- display: inline-block;
- width: 130px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-
- {{pendings}}
-
-{confirm}
-
-
-
- Cache
- {{date_created}}
- {{pendings_last_log}}
- {{actions}}
- {{assigned_to}}
-
- {content}
-
-
diff --git a/src/Views/viewpendings_error.tpl.php b/src/Views/viewpendings_error.tpl.php
deleted file mode 100644
index f5c62d7959..0000000000
--- a/src/Views/viewpendings_error.tpl.php
+++ /dev/null
@@ -1 +0,0 @@
-{{noaccess_error_01}} [{{noaccess_error_02}} ]
diff --git a/viewpendings.php b/viewpendings.php
deleted file mode 100644
index 5160c7d3c0..0000000000
--- a/viewpendings.php
+++ /dev/null
@@ -1,323 +0,0 @@
-$text";
- case '2':
- return "$text ";
- case '3':
- return "$text ";
- default:
- return "$text ";
- }
-}
-
-function nonEmptyCacheName($cacheName)
-{
- if (str_replace(" ", "", $cacheName) == "")
- return "[bez nazwy]";
- return $cacheName;
-}
-
-function getUsername($userid)
-{
- return XDb::xMultiVariableQueryValue(
- "SELECT username FROM user WHERE user_id= :1 LIMIT 1",
- null, $userid);
-}
-
-function getCachename($cacheid)
-{
- return XDb::xMultiVariableQueryValue(
- "SELECT name FROM caches WHERE cache_id= :1 LIMIT 1",
- null, $cacheid);
-}
-
-function getCacheOwnername($cacheid)
-{
- return XDb::xMultiVariableQueryValue(
- "SELECT username FROM user WHERE user_id= :1 LIMIT 1",
- null, getCacheOwnerId($cacheid));
-}
-
-function getCacheOwnerId($cacheid)
-{
- return XDb::xMultiVariableQueryValue(
- "SELECT user_id FROM caches WHERE cache_id= :1 LIMIT 1",
- null, $cacheid);
-}
-
-function actionRequired($cacheid)
-{
- // check if cache requires activation
- return XDb::xMultiVariableQueryValue(
- "SELECT status FROM caches WHERE cache_id= :1 AND status = 4",
- null, $cacheid);
-}
-
-function activateCache($cacheid)
-{
- // activate the cache by changing its status to yet unavailable
- if (actionRequired($cacheid)) {
- if (XDb::xSql("UPDATE caches SET status = 5 WHERE cache_id= ? ", $cacheid)) {
- return true;
- } else
- return false;
- }
- return false;
-}
-
-function declineCache($cacheid)
-{
- // activate the cache by changing its status to yet unavailable
- if (actionRequired($cacheid)) {
- if (XDb::xSql("UPDATE caches SET status = 6 WHERE cache_id= ? ", $cacheid)) {
- return true;
- } else
- return false;
- }
- return false;
-}
-
-function getAssignedUserId($cacheid)
-{
- // check if cache requires activation
- return XDb::xMultiVariableQueryValue(
- "SELECT user_id FROM approval_status WHERE cache_id= :1 LIMIT 1",
- false, $cacheid);
-}
-
-function assignUserToCase($userid, $cacheid)
-{
- // check if user is in OC Team Member
- $user = User::fromUserIdFactory($userid);
- if (!$user || !$user->hasOcTeamRole()) {
- return false;
- }
-
- XDb::xSql(
- "INSERT INTO approval_status (cache_id, user_id, status, date_approval)
- VALUES ( ?, ?, 2, NOW())
- ON DUPLICATE KEY UPDATE user_id = ?",
- $cacheid, $userid, $userid);
-}
-
-function notifyOwner($cacheid, $msgType)
-{
- // msgType - 0 = cache accepted, 1 = cache declined (=archived)
- global $absolute_server_URI;
-
- $user = ApplicationContainer::GetAuthorizedUser();
- if(!$user) {
- return;
- }
-
- $user_id = getCacheOwnerId($cacheid);
-
- $cachename = getCachename($cacheid);
- if ($msgType == 0) {
- $email_content = file_get_contents(__DIR__ . '/resources/email/activated_cache.email');
- } else {
- $email_content = file_get_contents(__DIR__ . '/resources/email/archived_cache.email');
- }
- $email_headers = "Content-Type: text/plain; charset=utf-8\r\n";
- $email_headers .= "From: " . OcConfig::getSiteName() . " <" . OcConfig::getEmailAddrOcTeam() . ">\r\n";
- $email_headers .= "Reply-To: " . OcConfig::getEmailAddrOcTeam() . "\r\n";
- $email_content = mb_ereg_replace('{server}', $absolute_server_URI, $email_content);
- $email_content = mb_ereg_replace('{cachename}', $cachename, $email_content);
- $email_content = mb_ereg_replace('{cacheid}', $cacheid, $email_content);
- $email_content = mb_ereg_replace('{octeamEmailsSignature}', OcConfig::getOcteamEmailsSignature(), $email_content);
- $email_content = mb_ereg_replace('{cacheArchived_01}', tr('cacheArchived_01'), $email_content);
- $email_content = mb_ereg_replace('{cacheArchived_02}', tr('cacheArchived_02'), $email_content);
- $email_content = mb_ereg_replace('{cacheArchived_03}', tr('cacheArchived_03'), $email_content);
- $email_content = mb_ereg_replace('{cacheArchived_04}', tr('cacheArchived_04'), $email_content);
- $email_content = mb_ereg_replace('{cacheArchived_05}', tr('cacheArchived_05'), $email_content);
- $email_content = mb_ereg_replace('{Cacheactivated_01}', tr('Cacheactivated_01'), $email_content);
- $email_content = mb_ereg_replace('{Cacheactivated_02}', tr('Cacheactivated_02'), $email_content);
- $email_content = mb_ereg_replace('{Cacheactivated_03}', tr('Cacheactivated_03'), $email_content);
- $email_content = mb_ereg_replace('{Cacheactivated_04}', tr('Cacheactivated_04'), $email_content);
- $email_content = mb_ereg_replace('{Cacheactivated_05}', tr('Cacheactivated_05'), $email_content);
-
-
- $owner_email['email'] = XDb::xMultiVariableQueryValue(
- "SELECT `email` FROM `user` WHERE `user_id`= :1 LIMIT 1", '', $user_id);
-
- if ($msgType == 0) {
- //send email to owner
- mb_send_mail($owner_email['email'], tr('viewPending_01') . ": " . $cachename, $email_content, $email_headers);
- //send email to approver
- mb_send_mail($user->getEmail(), tr('viewPending_01') . ": " . $cachename, tr('viewPending_02') . ":\n" . $email_content, $email_headers);
- // generate automatic log about status cache
- $log_text = htmlspecialchars(tr("viewPending_03"));
- $log_uuid = Uuid::create();
- XDb::xSql(
- "INSERT INTO `cache_logs`
- (`cache_id`, `user_id`, `type`, `date`, `text`, `text_html`, `date_created`, `last_modified`, `uuid`, `node`)
- VALUES (?, ?, '12', NOW(), ?, '2', NOW(), NOW(), ?, ?)",
- $cacheid, $user->getUserId(), $log_text, $log_uuid, OcConfig::getSiteNodeId());
-
- } else {
- //send email to owner
- mb_send_mail($owner_email['email'], tr('viewPending_04') . ": " . $cachename, $email_content, $email_headers);
- //send email to approver
- mb_send_mail($user->getEmail(), tr('viewPending_04') . ": " . $cachename, tr('viewPending_05') . ":\n" . $email_content, $email_headers);
-
- // generate automatic log about status cache
- $log_text = htmlspecialchars(tr("viewPending_06"));
- $log_uuid = Uuid::create();
- XDb::xSql(
- "INSERT INTO `cache_logs`
- (`id`, `cache_id`, `user_id`, `type`, `date`,
- `text`, `text_html`, `date_created`, `last_modified`, `uuid`,
- `node`)
- VALUES ('', ?, ?, ?, NOW(),
- ?, ?, NOW(), NOW(), ?,
- ?)",
- $cacheid, $user->getUserId(), 12,
- $log_text, 2, $log_uuid,
- OcConfig::getSiteNodeId());
- }
-}
-
-require_once(__DIR__ . '/lib/common.inc.php');
-
-$view = tpl_getView();
-$user = ApplicationContainer::GetAuthorizedUser();
-
-if (empty($user) || !$user->hasOcTeamRole()) {
- $view->setTemplate('viewpendings_error');
- $view->buildView();
- exit;
-}
-
-$view->setTemplate('viewpendings');
-
-$content = '';
-if (isset($_GET['cacheid'])) {
- if (isset($_GET['assign'])) {
- if (assignUserToCase($_GET['assign'], $_GET['cacheid'])) {
- $confirm = "" . tr("viewPending_07") . " " . getUsername($_GET['assign']) . " " . tr("viewPending_08") . ".
";
- tpl_set_var('confirm', $confirm);
- } else {
- tpl_set_var('confirm', '');
- }
- } else {
- if (actionRequired($_GET['cacheid'])) {
- // requires activation
- if (isset($_GET['confirm']) && isset($_GET['user_id']) && $_GET['confirm'] == 1) {
- // confirmed - change the status and notify the owner now
- if (activateCache($_GET['cacheid'])) {
- assignUserToCase($user->getUserId(), $_GET['cacheid']);
- notifyOwner($_GET['cacheid'], 0);
- AdminNote::addAdminNote($user->getUserId(), $_GET['user_id'], true, AdminNote::CACHE_PASS, $_GET['cacheid']);
- $confirm = " " . tr("viewPending_09") . ".
";
- } else {
- $confirm = " " . tr("viewPending_10") . ".
";
- }
- } else if (isset($_GET['confirm']) && isset($_GET['user_id']) && $_GET['confirm'] == 2) {
- // declined - change status to archived and notify the owner now
- if (declineCache($_GET['cacheid'])) {
- assignUserToCase($user->getUserId(), $_GET['cacheid']);
- notifyOwner($_GET['cacheid'], 1);
- AdminNote::addAdminNote($user->getUserId(), $_GET['user_id'], true, AdminNote::CACHE_BLOCKED, $_GET['cacheid']);
- $confirm = " " . tr("viewPending_11") . ".
";
- } else {
- $confirm = " " . tr("viewPending_12") . ".
";
- }
- } else if ($_GET['action'] == 1 && isset($_GET['user_id'])) {
- // require confirmation
- $confirm = " " . tr("viewPending_13") . " \"" . getCachename($_GET['cacheid']) . " \" " . tr("viewPending_14") . " " . getCacheOwnername($_GET['cacheid']) . ". " . tr("viewPending_15") . ".
";
- $confirm .= "" . tr("viewPending_16") . "
- " . tr("viewPending_17") . "
";
- } else if ($_GET['action'] == 2 && isset($_GET['user_id'])) {
- // require confirmation
- $confirm = " " . tr("viewPending_18") . " \"" . getCachename($_GET['cacheid']) . " \" " . tr("viewPending_14") . " " . getCacheOwnername($_GET['cacheid']) . ". " . tr("viewPending_19") . ".
";
- $confirm .= "" . tr("viewPending_20") . "
- " . tr("viewPending_17") . "
";
- }
- tpl_set_var('confirm', $confirm);
- } else {
- tpl_set_var('confirm', '' . tr('viewPending_21') . '.
');
- }
- }
-} else {
- tpl_set_var('confirm', '');
-}
-
-$stmt = XDb::xSql(
- "SELECT cache_status.id AS cs_id, cache_status.pl AS cache_status,
- cache_owner.username AS username, cache_owner.user_id AS user_id,
- caches.cache_id AS cache_id, caches.name AS cachename,
- IFNULL(`cache_location`.`adm3`, '') AS `adm3`, caches.date_created AS date_created,
- last_log.id AS last_log_id, last_log.date AS last_log_date,
- last_log.user_id AS last_log_author, log_author.username AS last_log_username,
- last_log.text AS last_log_text
- FROM cache_status, `caches`
- LEFT JOIN `cache_location` ON `caches`.`cache_id` = `cache_location`.`cache_id`
- LEFT JOIN (
- SELECT id, cache_id, text, user_id, date
- FROM cache_logs logs
- WHERE date = (SELECT MAX(date) FROM cache_logs WHERE cache_id = logs.cache_id)
- ) AS last_log ON caches.cache_id = last_log.cache_id
- LEFT JOIN user AS cache_owner
- ON caches.user_id = cache_owner.user_id
- LEFT JOIN user AS log_author
- ON last_log.user_id = log_author.user_id
- WHERE cache_status.id = caches.status
- AND caches.status = 4
- GROUP BY caches.cache_id
- ORDER BY caches.date_created DESC");
-
-$row_num = 0;
-while ($report = XDb::xFetchArray($stmt)) {
- $assignedUserId = getAssignedUserId($report['cache_id']);
-
- if (!$assignedUserId && new DateTime($report['date_created']) < new DateTime('5 days ago')) {
- //set alert for forgotten cache
- $trstyle = "alert";
- } else if ($user->getUserId() == $assignedUserId) {
- //highlight caches assigned to current user
- $trstyle = "highlighted";
- } else {
- $trstyle = "";
- }
-
- if ($row_num % 2)
- $bgcolor = "bgcolor1";
- else
- $bgcolor = "bgcolor2";
-
- $content .= "\n";
- $content .= "
- " . nonEmptyCacheName($report['cachename']) . "
- " . $report['username'] . "
- " . $report['adm3'] . "
- \n";
-
- $content .= " " . $report['date_created'] . " \n";
-
- $content .= "" . $report['last_log_date'] . "
- " . $report['last_log_username'] . "
- " . strip_tags($report['last_log_text']) . "
- \n";
-
- $content .= " " . tr('accept') . "
- " . tr('block') . "
- getUserId() . "'>" . tr('assign_yourself') . " \n";
- $content .= "" . getUsername($assignedUserId) . " ";
- $content .= " \n";
- $row_num++;
-}
-tpl_set_var('content', $content);
-$view->buildView();