2026-07-08 09:00:50 -03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Kanboard\Plugin\OrganonTweaks\Helper;
|
|
|
|
|
|
|
|
|
|
use Kanboard\Core\Base;
|
2026-07-08 21:09:30 -03:00
|
|
|
use Kanboard\Model\CustomFilterModel;
|
2026-07-08 09:00:50 -03:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Per-board helper for OrganonTweaks.
|
|
|
|
|
*
|
|
|
|
|
* Resolves the 3-state "emphasize due date" setting for the CURRENT board (read from the route
|
|
|
|
|
* project_id, which the router populates for pretty URLs before the layout renders):
|
2026-07-08 13:03:35 -03:00
|
|
|
* per-board 'on' -> on, 'off' -> off, 'default'/absent -> follow the LIVE global default.
|
|
|
|
|
*
|
|
|
|
|
* The stored values are the non-falsy strings 'on'/'off' (not '1'/'0'): Kanboard's
|
|
|
|
|
* MetadataModel::get() uses `?:`, and PHP treats the string "0" as falsy, so a stored "0" would
|
|
|
|
|
* come back as the 'default' fallback and "Always off" would never be honored.
|
2026-07-08 21:09:30 -03:00
|
|
|
*
|
2026-07-09 06:49:15 -03:00
|
|
|
* Also maintains shared, reserved-name custom filters on every board (v1.5 "Show all tasks", v1.6
|
|
|
|
|
* "Due this month" / "Around this month"), each group gated by its own config toggle.
|
2026-07-08 09:00:50 -03:00
|
|
|
*/
|
|
|
|
|
class OrganonProjectHelper extends Base
|
|
|
|
|
{
|
|
|
|
|
const EMPHASIZE_KEY = 'organon_tweaks_emphasize_duedate';
|
|
|
|
|
const EMPHASIZE_DEFAULT_KEY = 'organon_tweaks_emphasize_duedate_default';
|
|
|
|
|
|
2026-07-09 06:49:15 -03:00
|
|
|
// Config toggles for the auto-managed custom-filter groups.
|
2026-07-08 21:09:30 -03:00
|
|
|
const SHOW_ALL_FILTER_KEY = 'organon_tweaks_show_all_filter';
|
2026-07-09 06:49:15 -03:00
|
|
|
const MONTH_FILTERS_KEY = 'organon_tweaks_month_filters';
|
2026-07-09 07:40:52 -03:00
|
|
|
const RECURRING_FILTERS_KEY = 'organon_tweaks_recurring_filters';
|
2026-07-09 06:49:15 -03:00
|
|
|
|
|
|
|
|
// Reserved custom-filter groups: each row is [name, query, append]. The NAME is reserved by the
|
|
|
|
|
// plugin (fixed, non-translated strings so dedup is stable across locales); a user's own filter
|
|
|
|
|
// with a reserved name is absorbed into the canonical form. "Show all tasks" is a REPLACE filter
|
|
|
|
|
// (append 0) so it clears; the rest are APPEND (1) so clicks stack (distinct attributes AND).
|
2026-07-09 07:54:12 -03:00
|
|
|
// Names are prefixed by group so the dropdown -- which core sorts alphabetically by name
|
|
|
|
|
// (CustomFilterModel::getAll -> asc(name)) -- clusters them: Board < Month < Recurring.
|
2026-07-09 06:49:15 -03:00
|
|
|
const GROUP_SHOW_ALL = array(
|
2026-07-09 07:54:12 -03:00
|
|
|
array('Board: show all tasks', '', 0),
|
2026-07-09 06:49:15 -03:00
|
|
|
);
|
|
|
|
|
const GROUP_MONTH = array(
|
2026-07-09 07:54:12 -03:00
|
|
|
array('Month: due this month', 'duemonth:this', 1),
|
|
|
|
|
array('Month: around this month', 'aroundmonth:this', 1),
|
2026-07-09 06:49:15 -03:00
|
|
|
);
|
2026-07-09 07:40:52 -03:00
|
|
|
const GROUP_RECURRING = array(
|
2026-07-09 07:54:12 -03:00
|
|
|
array('Recurring: all', 'recurring:anyall', 1),
|
|
|
|
|
array('Recurring: event', 'recurring:evtall', 1),
|
|
|
|
|
array('Recurring: calendar', 'recurring:calall', 1),
|
|
|
|
|
array('Recurring: templates', 'recurring:anyori', 1),
|
|
|
|
|
array('Recurring: duplicates', 'recurring:anydup', 1),
|
2026-07-09 07:40:52 -03:00
|
|
|
);
|
2026-07-08 21:09:30 -03:00
|
|
|
|
2026-07-08 09:00:50 -03:00
|
|
|
/**
|
|
|
|
|
* Should the current board emphasize the due date? Board pages only (project_id in the route).
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isEmphasizeDueDate()
|
|
|
|
|
{
|
|
|
|
|
$project_id = $this->request->getIntegerParam('project_id');
|
|
|
|
|
|
|
|
|
|
if ($project_id <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$value = $this->projectMetadataModel->get($project_id, self::EMPHASIZE_KEY, 'default');
|
|
|
|
|
|
2026-07-08 13:03:35 -03:00
|
|
|
if ($value === 'on') {
|
2026-07-08 09:00:50 -03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-08 13:03:35 -03:00
|
|
|
if ($value === 'off') {
|
2026-07-08 09:00:50 -03:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 'default' (or absent) -> follow the live global default.
|
|
|
|
|
return (int) $this->configModel->get(self::EMPHASIZE_DEFAULT_KEY, 0) === 1;
|
|
|
|
|
}
|
2026-07-08 21:09:30 -03:00
|
|
|
|
|
|
|
|
/**
|
2026-07-09 06:49:15 -03:00
|
|
|
* Ensure one canonical reserved custom filter on a board (shared). Idempotent: create when
|
|
|
|
|
* absent, force a same-named one back to the canonical form (query + shared + append) since the
|
|
|
|
|
* name is reserved, and drop duplicates. No-op without a real project and a real owner
|
|
|
|
|
* (custom_filters.user_id has a FK to users, so a system id like 0 would fail the insert).
|
2026-07-08 21:09:30 -03:00
|
|
|
*
|
2026-07-09 06:49:15 -03:00
|
|
|
* @param int $project_id
|
|
|
|
|
* @param string $name
|
|
|
|
|
* @param string $query
|
|
|
|
|
* @param int $append
|
2026-07-08 21:09:30 -03:00
|
|
|
*/
|
2026-07-09 06:49:15 -03:00
|
|
|
public function ensureReservedFilter($project_id, $name, $query, $append)
|
2026-07-08 21:09:30 -03:00
|
|
|
{
|
|
|
|
|
$project_id = (int) $project_id;
|
|
|
|
|
$user_id = (int) $this->userSession->getId();
|
|
|
|
|
|
|
|
|
|
if ($project_id <= 0 || $user_id <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-09 06:49:15 -03:00
|
|
|
$append = $append ? 1 : 0;
|
|
|
|
|
|
2026-07-08 21:09:30 -03:00
|
|
|
$existing = $this->db->table(CustomFilterModel::TABLE)
|
|
|
|
|
->eq('project_id', $project_id)
|
2026-07-09 06:49:15 -03:00
|
|
|
->eq('name', $name)
|
2026-07-08 21:09:30 -03:00
|
|
|
->findAll();
|
|
|
|
|
|
|
|
|
|
if (empty($existing)) {
|
|
|
|
|
$this->db->table(CustomFilterModel::TABLE)->insert(array(
|
2026-07-09 06:49:15 -03:00
|
|
|
'filter' => $query,
|
2026-07-08 21:09:30 -03:00
|
|
|
'project_id' => $project_id,
|
|
|
|
|
'user_id' => $user_id,
|
2026-07-09 06:49:15 -03:00
|
|
|
'name' => $name,
|
2026-07-08 21:09:30 -03:00
|
|
|
'is_shared' => 1,
|
2026-07-09 06:49:15 -03:00
|
|
|
'append' => $append,
|
2026-07-08 21:09:30 -03:00
|
|
|
));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-09 06:49:15 -03:00
|
|
|
// Reserved name: keep one, force it to the canonical form, drop the rest.
|
2026-07-08 21:09:30 -03:00
|
|
|
$keep = array_shift($existing);
|
|
|
|
|
|
2026-07-09 06:49:15 -03:00
|
|
|
if ($keep['filter'] !== $query || (int) $keep['is_shared'] !== 1 || (int) $keep['append'] !== $append) {
|
2026-07-08 21:09:30 -03:00
|
|
|
$this->db->table(CustomFilterModel::TABLE)
|
|
|
|
|
->eq('id', $keep['id'])
|
2026-07-09 06:49:15 -03:00
|
|
|
->update(array('filter' => $query, 'is_shared' => 1, 'append' => $append));
|
2026-07-08 21:09:30 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($existing as $duplicate) {
|
|
|
|
|
$this->db->table(CustomFilterModel::TABLE)->eq('id', $duplicate['id'])->remove();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-07-09 06:49:15 -03:00
|
|
|
* Ensure every ENABLED group's filters on a board. Called lazily on each project-header render,
|
|
|
|
|
* so it covers old and new boards alike.
|
|
|
|
|
*
|
|
|
|
|
* @param int $project_id
|
|
|
|
|
*/
|
|
|
|
|
public function ensureBoardFilters($project_id)
|
|
|
|
|
{
|
|
|
|
|
if ((int) $this->configModel->get(self::SHOW_ALL_FILTER_KEY, 1) === 1) {
|
|
|
|
|
$this->ensureGroup($project_id, self::GROUP_SHOW_ALL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((int) $this->configModel->get(self::MONTH_FILTERS_KEY, 1) === 1) {
|
|
|
|
|
$this->ensureGroup($project_id, self::GROUP_MONTH);
|
|
|
|
|
}
|
2026-07-09 07:40:52 -03:00
|
|
|
|
|
|
|
|
if ((int) $this->configModel->get(self::RECURRING_FILTERS_KEY, 1) === 1) {
|
|
|
|
|
$this->ensureGroup($project_id, self::GROUP_RECURRING);
|
|
|
|
|
}
|
2026-07-09 06:49:15 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function ensureGroup($project_id, array $rows)
|
|
|
|
|
{
|
|
|
|
|
foreach ($rows as $row) {
|
|
|
|
|
$this->ensureReservedFilter($project_id, $row[0], $row[1], $row[2]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Toggle turned ON: eagerly (re)create a group's filters on every existing board.
|
|
|
|
|
* ensureBoardFilters() keeps covering boards created afterwards.
|
|
|
|
|
*
|
|
|
|
|
* @param array $rows
|
2026-07-08 21:09:30 -03:00
|
|
|
*/
|
2026-07-09 06:49:15 -03:00
|
|
|
public function addGroupToAllProjects(array $rows)
|
2026-07-08 21:09:30 -03:00
|
|
|
{
|
|
|
|
|
foreach ($this->projectModel->getAll() as $project) {
|
2026-07-09 06:49:15 -03:00
|
|
|
$this->ensureGroup((int) $project['id'], $rows);
|
2026-07-08 21:09:30 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-07-09 06:49:15 -03:00
|
|
|
* Toggle turned OFF: remove a group's filters across all boards (by their reserved names).
|
|
|
|
|
*
|
|
|
|
|
* @param array $rows
|
2026-07-08 21:09:30 -03:00
|
|
|
*/
|
2026-07-09 06:49:15 -03:00
|
|
|
public function removeGroup(array $rows)
|
2026-07-08 21:09:30 -03:00
|
|
|
{
|
2026-07-09 06:49:15 -03:00
|
|
|
$names = array();
|
|
|
|
|
|
|
|
|
|
foreach ($rows as $row) {
|
|
|
|
|
$names[] = $row[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->db->table(CustomFilterModel::TABLE)->in('name', $names)->remove();
|
2026-07-08 21:09:30 -03:00
|
|
|
}
|
2026-07-08 09:00:50 -03:00
|
|
|
}
|