Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b63d366296 | |||
| 89888235d1 | |||
| 4ebebd5ffb | |||
| 9b637160b2 |
@@ -21,6 +21,7 @@ class ConfigController extends BaseController
|
|||||||
'organon_tweaks_emphasize_duedate_default' => (int) $this->configModel->get('organon_tweaks_emphasize_duedate_default', 0),
|
'organon_tweaks_emphasize_duedate_default' => (int) $this->configModel->get('organon_tweaks_emphasize_duedate_default', 0),
|
||||||
'organon_tweaks_show_all_filter' => (int) $this->configModel->get('organon_tweaks_show_all_filter', 1),
|
'organon_tweaks_show_all_filter' => (int) $this->configModel->get('organon_tweaks_show_all_filter', 1),
|
||||||
'organon_tweaks_month_filters' => (int) $this->configModel->get('organon_tweaks_month_filters', 1),
|
'organon_tweaks_month_filters' => (int) $this->configModel->get('organon_tweaks_month_filters', 1),
|
||||||
|
'organon_tweaks_recurring_filters' => (int) $this->configModel->get('organon_tweaks_recurring_filters', 1),
|
||||||
),
|
),
|
||||||
'errors' => array(),
|
'errors' => array(),
|
||||||
)));
|
)));
|
||||||
@@ -37,6 +38,7 @@ class ConfigController extends BaseController
|
|||||||
$emphasizeDueDateDefault = isset($values['organon_tweaks_emphasize_duedate_default']) ? 1 : 0;
|
$emphasizeDueDateDefault = isset($values['organon_tweaks_emphasize_duedate_default']) ? 1 : 0;
|
||||||
$showAllFilter = isset($values['organon_tweaks_show_all_filter']) ? 1 : 0;
|
$showAllFilter = isset($values['organon_tweaks_show_all_filter']) ? 1 : 0;
|
||||||
$monthFilters = isset($values['organon_tweaks_month_filters']) ? 1 : 0;
|
$monthFilters = isset($values['organon_tweaks_month_filters']) ? 1 : 0;
|
||||||
|
$recurringFilters = isset($values['organon_tweaks_recurring_filters']) ? 1 : 0;
|
||||||
|
|
||||||
if ($this->configModel->save(array(
|
if ($this->configModel->save(array(
|
||||||
'organon_tweaks_always_comment_icon' => $alwaysCommentIcon,
|
'organon_tweaks_always_comment_icon' => $alwaysCommentIcon,
|
||||||
@@ -46,6 +48,7 @@ class ConfigController extends BaseController
|
|||||||
'organon_tweaks_emphasize_duedate_default' => $emphasizeDueDateDefault,
|
'organon_tweaks_emphasize_duedate_default' => $emphasizeDueDateDefault,
|
||||||
'organon_tweaks_show_all_filter' => $showAllFilter,
|
'organon_tweaks_show_all_filter' => $showAllFilter,
|
||||||
'organon_tweaks_month_filters' => $monthFilters,
|
'organon_tweaks_month_filters' => $monthFilters,
|
||||||
|
'organon_tweaks_recurring_filters' => $recurringFilters,
|
||||||
))) {
|
))) {
|
||||||
$this->flash->success(t('Settings saved successfully.'));
|
$this->flash->success(t('Settings saved successfully.'));
|
||||||
} else {
|
} else {
|
||||||
@@ -68,6 +71,12 @@ class ConfigController extends BaseController
|
|||||||
$projectHelper->removeGroup($projectHelper::GROUP_MONTH);
|
$projectHelper->removeGroup($projectHelper::GROUP_MONTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($recurringFilters === 1) {
|
||||||
|
$projectHelper->addGroupToAllProjects($projectHelper::GROUP_RECURRING);
|
||||||
|
} else {
|
||||||
|
$projectHelper->removeGroup($projectHelper::GROUP_RECURRING);
|
||||||
|
}
|
||||||
|
|
||||||
$this->response->redirect($this->helper->url->to('ConfigController', 'show', array('plugin' => 'OrganonTweaks')));
|
$this->response->redirect($this->helper->url->to('ConfigController', 'show', array('plugin' => 'OrganonTweaks')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
100
Filter/TaskRecurringFilter.php
Normal file
100
Filter/TaskRecurringFilter.php
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Kanboard\Plugin\OrganonTweaks\Filter;
|
||||||
|
|
||||||
|
use Kanboard\Core\Filter\FilterInterface;
|
||||||
|
use Kanboard\Filter\BaseFilter;
|
||||||
|
use Kanboard\Model\TaskModel;
|
||||||
|
use PicoDb\Database;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter tasks by recurrence: `recurring:XY`, one attribute with one composite value so it can
|
||||||
|
* INTERSECT the two axes (stacking two values of the same attribute would only OR them).
|
||||||
|
*
|
||||||
|
* X = system : evt (event-triggered = Kanboard native) | cal (calendar = RecoReco) | any
|
||||||
|
* Y = role : ori (original/template) | dup (duplicate/copy) | all
|
||||||
|
*
|
||||||
|
* Conditions are authoritative -- taken straight from how core classifies these cards:
|
||||||
|
* - evtori = recurrence_status PENDING, evtdup = recurrence_status PROCESSED. This is exactly how
|
||||||
|
* core colors the board refresh icon (app/Template/board/task_footer.php: normal icon = PENDING,
|
||||||
|
* fa-inverse = PROCESSED), so the filter matches what the board shows.
|
||||||
|
* - calori = RecoReco metadata recoreco_enabled=1, caldup = recoreco_clone=1.
|
||||||
|
* - *all / any* = the OR-unions of the above.
|
||||||
|
*
|
||||||
|
* Cross-plugin: the calendar side reads RecoReco metadata keys. If RecoReco is not installed the
|
||||||
|
* metadata sets are empty, so `cal*` matches nothing and `evt*` still works (event-only degrade).
|
||||||
|
*
|
||||||
|
* One attribute + one value -> single-value path, so the predicates below are grouped in a scoped
|
||||||
|
* beginOr/closeOr and the whole group still ANDs with any other search attribute.
|
||||||
|
*/
|
||||||
|
class TaskRecurringFilter extends BaseFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
public function setDatabase(Database $db)
|
||||||
|
{
|
||||||
|
$this->db = $db;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAttributes()
|
||||||
|
{
|
||||||
|
return array('recurring');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function apply()
|
||||||
|
{
|
||||||
|
$value = strtolower(trim((string) $this->value));
|
||||||
|
$system = substr($value, 0, 3); // evt | cal | any
|
||||||
|
$role = substr($value, 3, 3); // ori | dup | all
|
||||||
|
|
||||||
|
if (! in_array($system, array('evt', 'cal', 'any'), true) ||
|
||||||
|
! in_array($role, array('ori', 'dup', 'all'), true)) {
|
||||||
|
return $this; // unknown/typo value -> no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
$wantEvt = $system === 'evt' || $system === 'any';
|
||||||
|
$wantCal = $system === 'cal' || $system === 'any';
|
||||||
|
$wantOri = $role === 'ori' || $role === 'all';
|
||||||
|
$wantDup = $role === 'dup' || $role === 'all';
|
||||||
|
|
||||||
|
$this->query->beginOr();
|
||||||
|
|
||||||
|
if ($wantEvt && $wantOri) {
|
||||||
|
$this->query->eq(TaskModel::TABLE.'.recurrence_status', TaskModel::RECURRING_STATUS_PENDING);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($wantEvt && $wantDup) {
|
||||||
|
$this->query->eq(TaskModel::TABLE.'.recurrence_status', TaskModel::RECURRING_STATUS_PROCESSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($wantCal && $wantOri) {
|
||||||
|
$this->query->in(TaskModel::TABLE.'.id', $this->metaIds('recoreco_enabled'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($wantCal && $wantDup) {
|
||||||
|
$this->query->in(TaskModel::TABLE.'.id', $this->metaIds('recoreco_clone'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->query->closeOr();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task ids carrying a RecoReco metadata flag set to 1. Returns a non-matching sentinel when the
|
||||||
|
* set is empty so the IN clause matches nothing (RecoReco absent -> event-only degrade).
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function metaIds($name)
|
||||||
|
{
|
||||||
|
$ids = $this->db->table('task_has_metadata')
|
||||||
|
->eq('name', $name)
|
||||||
|
->eq('value', '1')
|
||||||
|
->findAllByColumn('task_id');
|
||||||
|
|
||||||
|
return ! empty($ids) ? $ids : array(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,17 +27,27 @@ class OrganonProjectHelper extends Base
|
|||||||
// Config toggles for the auto-managed custom-filter groups.
|
// Config toggles for the auto-managed custom-filter groups.
|
||||||
const SHOW_ALL_FILTER_KEY = 'organon_tweaks_show_all_filter';
|
const SHOW_ALL_FILTER_KEY = 'organon_tweaks_show_all_filter';
|
||||||
const MONTH_FILTERS_KEY = 'organon_tweaks_month_filters';
|
const MONTH_FILTERS_KEY = 'organon_tweaks_month_filters';
|
||||||
|
const RECURRING_FILTERS_KEY = 'organon_tweaks_recurring_filters';
|
||||||
|
|
||||||
// Reserved custom-filter groups: each row is [name, query, append]. The NAME is reserved by the
|
// 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
|
// 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
|
// 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).
|
// (append 0) so it clears; the rest are APPEND (1) so clicks stack (distinct attributes AND).
|
||||||
|
// Names are prefixed by group so the dropdown -- which core sorts alphabetically by name
|
||||||
|
// (CustomFilterModel::getAll -> asc(name)) -- clusters them: Board < Month < Recurring.
|
||||||
const GROUP_SHOW_ALL = array(
|
const GROUP_SHOW_ALL = array(
|
||||||
array('Show all tasks', '', 0),
|
array('Board: show all tasks', '', 0),
|
||||||
);
|
);
|
||||||
const GROUP_MONTH = array(
|
const GROUP_MONTH = array(
|
||||||
array('Due this month', 'duemonth:this', 1),
|
array('Month: due this month', 'duemonth:this', 1),
|
||||||
array('Around this month', 'aroundmonth:this', 1),
|
array('Month: around this month', 'aroundmonth:this', 1),
|
||||||
|
);
|
||||||
|
const GROUP_RECURRING = array(
|
||||||
|
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),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,6 +145,10 @@ class OrganonProjectHelper extends Base
|
|||||||
if ((int) $this->configModel->get(self::MONTH_FILTERS_KEY, 1) === 1) {
|
if ((int) $this->configModel->get(self::MONTH_FILTERS_KEY, 1) === 1) {
|
||||||
$this->ensureGroup($project_id, self::GROUP_MONTH);
|
$this->ensureGroup($project_id, self::GROUP_MONTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((int) $this->configModel->get(self::RECURRING_FILTERS_KEY, 1) === 1) {
|
||||||
|
$this->ensureGroup($project_id, self::GROUP_RECURRING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function ensureGroup($project_id, array $rows)
|
private function ensureGroup($project_id, array $rows)
|
||||||
|
|||||||
23
Plugin.php
23
Plugin.php
@@ -58,26 +58,29 @@ class Plugin extends Base
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-managed shared custom filters (v1.5 "Show all tasks", v1.6 month filters). Ensured
|
// Auto-managed shared custom filters (v1.5 "Show all tasks", v1.6 month filters, v1.7
|
||||||
// lazily whenever a project header renders (covers old + new boards); each group is gated by
|
// recurring filters). Ensured lazily whenever a project header renders (covers old + new
|
||||||
// its own config toggle inside OrganonProjectHelper::ensureBoardFilters(). The toggles also
|
// boards); each group is gated by its own config toggle inside
|
||||||
// bulk add/remove the filters (ConfigController).
|
// OrganonProjectHelper::ensureBoardFilters(). The toggles also bulk add/remove the filters.
|
||||||
$showAllFilters = (int) $this->configModel->get('organon_tweaks_show_all_filter', 1) === 1;
|
$showAllFilters = (int) $this->configModel->get('organon_tweaks_show_all_filter', 1) === 1;
|
||||||
$monthFilters = (int) $this->configModel->get('organon_tweaks_month_filters', 1) === 1;
|
$monthFilters = (int) $this->configModel->get('organon_tweaks_month_filters', 1) === 1;
|
||||||
if ($showAllFilters || $monthFilters) {
|
$recurringFilters = (int) $this->configModel->get('organon_tweaks_recurring_filters', 1) === 1;
|
||||||
|
if ($showAllFilters || $monthFilters || $recurringFilters) {
|
||||||
$this->template->hook->attach('template:project:header:before', 'organonTweaks:project_header/board_filters');
|
$this->template->hook->attach('template:project:header:before', 'organonTweaks:project_header/board_filters');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom board-search keywords, registered into the task lexer by extending its factory
|
// Custom board-search keywords, registered into the task lexer by extending its factory
|
||||||
// service (Pimple preserves factory status on extend, so the lexer stays per-request fresh).
|
// service (Pimple preserves factory status on extend, so the lexer stays per-request fresh).
|
||||||
// `duemonth:VALUE` = a whole calendar month; `aroundmonth:VALUE` = that month padded by 10
|
// `duemonth:VALUE` / `aroundmonth:VALUE` = a calendar month (padded by 10 days for around);
|
||||||
// days on each side. Always available to type, independent of the bookmark toggles above.
|
// `recurring:XY` = classify recurrence (X system evt/cal/any, Y role ori/dup/all). Always
|
||||||
// Guarded so a future Kanboard service rename no-ops (keyword absent) instead of breaking.
|
// available to type, independent of the bookmark toggles above. Guarded so a future Kanboard
|
||||||
|
// service rename no-ops (keyword absent) instead of breaking.
|
||||||
if (isset($this->container['taskLexer'])) {
|
if (isset($this->container['taskLexer'])) {
|
||||||
$this->container->extend('taskLexer', function ($builder, $c) {
|
$this->container->extend('taskLexer', function ($builder, $c) {
|
||||||
return $builder
|
return $builder
|
||||||
->withFilter((new \Kanboard\Plugin\OrganonTweaks\Filter\TaskMonthRangeFilter())->setAttribute('duemonth')->setPadDays(0))
|
->withFilter((new \Kanboard\Plugin\OrganonTweaks\Filter\TaskMonthRangeFilter())->setAttribute('duemonth')->setPadDays(0))
|
||||||
->withFilter((new \Kanboard\Plugin\OrganonTweaks\Filter\TaskMonthRangeFilter())->setAttribute('aroundmonth')->setPadDays(10));
|
->withFilter((new \Kanboard\Plugin\OrganonTweaks\Filter\TaskMonthRangeFilter())->setAttribute('aroundmonth')->setPadDays(10))
|
||||||
|
->withFilter((new \Kanboard\Plugin\OrganonTweaks\Filter\TaskRecurringFilter())->setDatabase($c['db']));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +109,7 @@ class Plugin extends Base
|
|||||||
|
|
||||||
public function getPluginVersion()
|
public function getPluginVersion()
|
||||||
{
|
{
|
||||||
return '1.6.1';
|
return '1.7.2';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPluginHomepage()
|
public function getPluginHomepage()
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -93,3 +93,18 @@ name). No build step and no database migration.
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
AGPL-3.0. See LICENSE.
|
AGPL-3.0. See LICENSE.
|
||||||
|
|
||||||
|
## More Kanboard plugins by Ruben (drbeco)
|
||||||
|
|
||||||
|
All free and AGPL-3.0, at [code.beco.cc](https://code.beco.cc/beco):
|
||||||
|
|
||||||
|
- **[RecoReco](https://code.beco.cc/beco/RecoReco)** -- calendar-scheduled recurring cards (yearly,
|
||||||
|
monthly, weekly, daily), driven by the card due date.
|
||||||
|
- **[FinanceBuddy](https://code.beco.cc/beco/FinanceBuddy)** -- attach a money value (debit/credit
|
||||||
|
and installments) to cards, shown on the card and totalled per column.
|
||||||
|
- **[BulkMoveTasks](https://code.beco.cc/beco/BulkMoveTasks)** -- move every task from one board
|
||||||
|
column to another in a single action.
|
||||||
|
- **[ShrinkVertically](https://code.beco.cc/beco/ShrinkVertically)** -- shrink vertically-collapsed
|
||||||
|
board columns so the horizontal scrollbar stays within reach.
|
||||||
|
- **[TweakDrag](https://code.beco.cc/beco/TweakDrag)** -- board drag and touch niceties:
|
||||||
|
drag-to-scroll, a wider column gap, and smoother card dragging.
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<p class="form-help"><?= t('"Due this month" and "Around this month" (duemonth: / aroundmonth: keywords).') ?></p>
|
<p class="form-help"><?= t('"Due this month" and "Around this month" (duemonth: / aroundmonth: keywords).') ?></p>
|
||||||
|
|
||||||
<?= $this->form->checkbox('organon_tweaks_recurring_filters', t('Add recurring filters to every board'), 1, isset($values['organon_tweaks_recurring_filters']) && $values['organon_tweaks_recurring_filters'] == 1) ?>
|
<?= $this->form->checkbox('organon_tweaks_recurring_filters', t('Add recurring filters to every board'), 1, isset($values['organon_tweaks_recurring_filters']) && $values['organon_tweaks_recurring_filters'] == 1) ?>
|
||||||
<p class="form-help"><?= t('Filter by recurrence (recurring: keyword) -- not active yet.') ?></p>
|
<p class="form-help"><?= t('Adds recurrence bookmarks. Also a recurring:XY search keyword -- X = evt/cal/any (native/calendar/both), Y = ori/dup/all (template/duplicate/both). Example: recurring:evtori.') ?></p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
|
|||||||
Reference in New Issue
Block a user