mirror of
https://github.com/maxpozdeev/mytinytodo.git
synced 2026-03-11 08:55:27 +00:00
* can use weekday for duedate in smart syntax; can use ! and @ for duedate prefix (GH-26)
This commit is contained in:
parent
6b882d13b2
commit
28bd570436
3 changed files with 234 additions and 147 deletions
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
require_once(MTTINC. 'markup.php');
|
||||
require_once(MTTINC. 'smartsyntax.php');
|
||||
|
||||
class TasksController extends ApiController {
|
||||
|
||||
|
|
@ -240,7 +241,7 @@ class TasksController extends ApiController {
|
|||
'tags' => '',
|
||||
'duedate' => '',
|
||||
);
|
||||
if (Config::get('smartsyntax') != 0 && (false !== $a = $this->parseSmartSyntax($t['title'])))
|
||||
if (Config::get('smartsyntax') != 0 && (false !== $a = parseSmartSyntax($t['title'])))
|
||||
{
|
||||
$t['title'] = (string) ($a['title'] ?? '');
|
||||
$t['prio'] = (int) ($a['prio'] ?? 0);
|
||||
|
|
@ -325,7 +326,7 @@ class TasksController extends ApiController {
|
|||
$duedate = null;
|
||||
if (Config::get('smartsyntax') != 0)
|
||||
{
|
||||
$a = $this->parseSmartSyntax($title);
|
||||
$a = parseSmartSyntax($title);
|
||||
if ($a === false) {
|
||||
return $t;
|
||||
}
|
||||
|
|
@ -371,7 +372,7 @@ class TasksController extends ApiController {
|
|||
$prio = (int)($this->req->jsonBody['prio'] ?? 0);
|
||||
if ($prio < -1) $prio = -1;
|
||||
elseif ($prio > 2) $prio = 2;
|
||||
$duedate = $this->parseDuedate(trim( $this->req->jsonBody['duedate'] ?? '' ));
|
||||
$duedate = MTTSmartSyntax::parseDuedate(trim( $this->req->jsonBody['duedate'] ?? '' ));
|
||||
$t = array();
|
||||
$t['total'] = 0;
|
||||
if ($title == '') {
|
||||
|
|
@ -409,7 +410,7 @@ class TasksController extends ApiController {
|
|||
$prio = (int)($this->req->jsonBody['prio'] ?? 0);
|
||||
if ($prio < -1) $prio = -1;
|
||||
elseif ($prio > 2) $prio = 2;
|
||||
$duedate = $this->parseDuedate(trim( $this->req->jsonBody['duedate'] ?? '' ));
|
||||
$duedate = MTTSmartSyntax::parseDuedate(trim( $this->req->jsonBody['duedate'] ?? '' ));
|
||||
$t = array();
|
||||
$t['total'] = 0;
|
||||
if ($title == '') {
|
||||
|
|
@ -633,56 +634,6 @@ class TasksController extends ApiController {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
private function parseDuedate($s): ?string
|
||||
{
|
||||
$df2 = Config::get('dateformat2');
|
||||
if (max((int)strpos($df2,'n'), (int)strpos($df2,'m')) > max((int)strpos($df2,'d'), (int)strpos($df2,'j'))) $formatDayFirst = true;
|
||||
else $formatDayFirst = false;
|
||||
|
||||
$y = $m = $d = 0;
|
||||
if (preg_match("|^(\d+)-(\d+)-(\d+)\b|", $s, $ma)) {
|
||||
$y = (int)$ma[1]; $m = (int)$ma[2]; $d = (int)$ma[3];
|
||||
}
|
||||
elseif (preg_match("|^(\d+)\/(\d+)\/(\d+)\b|", $s, $ma))
|
||||
{
|
||||
if($formatDayFirst) {
|
||||
$d = (int)$ma[1]; $m = (int)$ma[2]; $y = (int)$ma[3];
|
||||
} else {
|
||||
$m = (int)$ma[1]; $d = (int)$ma[2]; $y = (int)$ma[3];
|
||||
}
|
||||
}
|
||||
elseif (preg_match("|^(\d+)\.(\d+)\.(\d+)\b|", $s, $ma)) {
|
||||
$d = (int)$ma[1]; $m = (int)$ma[2]; $y = (int)$ma[3];
|
||||
}
|
||||
elseif (preg_match("|^(\d+)\.(\d+)\b|", $s, $ma)) {
|
||||
$d = (int)$ma[1]; $m = (int)$ma[2];
|
||||
$a = explode(',', date('Y,m,d'));
|
||||
if( $m<(int)$a[1] || ($m==(int)$a[1] && $d<(int)$a[2]) ) $y = (int)$a[0]+1;
|
||||
else $y = (int)$a[0];
|
||||
}
|
||||
elseif (preg_match("|^(\d+)\/(\d+)\b|", $s, $ma))
|
||||
{
|
||||
if($formatDayFirst) {
|
||||
$d = (int)$ma[1]; $m = (int)$ma[2];
|
||||
} else {
|
||||
$m = (int)$ma[1]; $d = (int)$ma[2];
|
||||
}
|
||||
$a = explode(',', date('Y,m,d'));
|
||||
if( $m<(int)$a[1] || ($m==(int)$a[1] && $d<(int)$a[2]) ) $y = (int)$a[0]+1;
|
||||
else $y = (int)$a[0];
|
||||
}
|
||||
else return null;
|
||||
if ($y < 100) $y = 2000 + $y;
|
||||
elseif ($y < 1000 || $y > 2099) $y = 2000 + (int)substr((string)$y, -2);
|
||||
if ($m > 12) $m = 12;
|
||||
$maxdays = $this->daysInMonth($m,$y);
|
||||
if ($m < 10) $m = '0'.$m;
|
||||
if ($d > $maxdays) $d = $maxdays;
|
||||
elseif ($d < 10) $d = '0'.$d;
|
||||
return "$y-$m-$d";
|
||||
}
|
||||
|
||||
private function prepareDuedate($duedate): array
|
||||
{
|
||||
$lang = Lang::instance();
|
||||
|
|
@ -692,19 +643,30 @@ class TasksController extends ApiController {
|
|||
return $a;
|
||||
}
|
||||
$ad = explode('-', $duedate);
|
||||
$at = explode('-', date('Y-m-d'));
|
||||
$a['timestamp'] = mktime(0,0,0, (int)$ad[1], (int)$ad[2], (int)$ad[0]);
|
||||
$diff = mktime(0,0,0, (int)$ad[1], (int)$ad[2], (int)$ad[0]) - mktime(0,0,0, (int)$at[1], (int)$at[2], (int)$at[0]);
|
||||
$y = (int)$ad[0];
|
||||
$m = (int)$ad[1];
|
||||
$d = (int)$ad[2];
|
||||
$a['timestamp'] = mktime(0, 0, 0, $m, $d, $y);
|
||||
|
||||
if ($diff < -604800 && $ad[0] == $at[0]) { $a['class'] = 'past'; $a['str'] = formatDate3(Config::get('dateformatshort'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); }
|
||||
elseif ($diff < -604800) { $a['class'] = 'past'; $a['str'] = formatDate3(Config::get('dateformat2'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); }
|
||||
elseif ($diff < -86400) { $a['class'] = 'past'; $a['str'] = sprintf($lang->get('daysago'),ceil(abs($diff)/86400)); }
|
||||
elseif ($diff < 0) { $a['class'] = 'past'; $a['str'] = $lang->get('yesterday'); }
|
||||
elseif ($diff < 86400) { $a['class'] = 'today'; $a['str'] = $lang->get('today'); }
|
||||
elseif ($diff < 172800) { $a['class'] = 'today'; $a['str'] = $lang->get('tomorrow'); }
|
||||
elseif ($diff < 691200) { $a['class'] = 'soon'; $a['str'] = sprintf($lang->get('indays'),ceil($diff/86400)); }
|
||||
elseif ($ad[0] == $at[0]) { $a['class'] = 'future'; $a['str'] = formatDate3(Config::get('dateformatshort'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); }
|
||||
else { $a['class'] = 'future'; $a['str'] = formatDate3(Config::get('dateformat2'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); }
|
||||
$oToday = new DateTimeImmutable(date("Y-m-d"));
|
||||
$oDue = new DateTimeImmutable($duedate);
|
||||
$oDiff = $oToday->diff($oDue);
|
||||
if ($oDiff === false) {
|
||||
return $a;
|
||||
}
|
||||
$thisYear = ((int)$oToday->format('Y') == $y);
|
||||
$days = $oDiff->days;
|
||||
if ($oDiff->invert) $days *= -1;
|
||||
|
||||
if ($days < -7 && !$thisYear) { $a['class'] = 'past'; $a['str'] = formatDate3(Config::get('dateformat2'), $y, $m, $d, $lang); }
|
||||
elseif ($days < -7) { $a['class'] = 'past'; $a['str'] = formatDate3(Config::get('dateformatshort'), $y, $m, $d, $lang); }
|
||||
elseif ($days < -1) { $a['class'] = 'past'; $a['str'] = sprintf($lang->get('daysago'), abs($days)); }
|
||||
elseif ($days == -1) { $a['class'] = 'past'; $a['str'] = $lang->get('yesterday'); }
|
||||
elseif ($days == 0) { $a['class'] = 'today'; $a['str'] = $lang->get('today'); }
|
||||
elseif ($days == 1) { $a['class'] = 'today'; $a['str'] = $lang->get('tomorrow'); }
|
||||
elseif ($days <= 7) { $a['class'] = 'soon'; $a['str'] = sprintf($lang->get('indays'), $days); }
|
||||
elseif ($thisYear) { $a['class'] = 'future'; $a['str'] = formatDate3(Config::get('dateformatshort'), $y, $m, $d, $lang); }
|
||||
else { $a['class'] = 'future'; $a['str'] = formatDate3(Config::get('dateformat2'), $y, $m, $d, $lang); }
|
||||
|
||||
#avoid short year
|
||||
$fmt = str_replace('y', 'Y', Config::get('dateformat2'));
|
||||
|
|
@ -726,14 +688,6 @@ class TasksController extends ApiController {
|
|||
return (int)$s;
|
||||
}
|
||||
|
||||
private function daysInMonth(int $m, int $y = 0): int
|
||||
{
|
||||
if ($y == 0) $y = (int)date('Y');
|
||||
$a = array(1=>31,(($y-2000)%4?28:29),31,30,31,30,31,31,30,31,30,31);
|
||||
if (isset($a[$m])) return $a[$m];
|
||||
else return 0;
|
||||
}
|
||||
|
||||
private function getTagId($tag)
|
||||
{
|
||||
$db = DBConnection::instance();
|
||||
|
|
@ -787,79 +741,6 @@ class TasksController extends ApiController {
|
|||
}
|
||||
}
|
||||
|
||||
private function parseSmartSyntax($title): array
|
||||
{
|
||||
$a = [
|
||||
'prio' => 0,
|
||||
'title' => $title,
|
||||
'tags' => '',
|
||||
'duedate' => null,
|
||||
];
|
||||
// priority
|
||||
if ( preg_match("|^([-+]{1}\d+)(.+)|", $a['title'], $m) ) {
|
||||
$a['prio'] = (int) $m[1];
|
||||
if ( $a['prio'] < -1 ) $a['prio'] = -1;
|
||||
elseif ( $a['prio'] > 2 ) $a['prio'] = 2;
|
||||
$a['title'] = trim($m[2]);
|
||||
}
|
||||
// duedate
|
||||
if ( preg_match("|(.+)@(\S+)$|", $a['title'], $m) ) {
|
||||
$rest = $m[1];
|
||||
$duepre = $m[2];
|
||||
$duedate = $this->findDuedate($duepre);
|
||||
if ($duedate) {
|
||||
$a['duedate'] = $duedate;
|
||||
$a['title'] = $rest;
|
||||
}
|
||||
}
|
||||
// tags
|
||||
$tags = [];
|
||||
$a['title'] = trim( preg_replace_callback(
|
||||
"/(?:^|\s+)#([^#\s]+)/",
|
||||
function ($matches) use (&$tags) {
|
||||
$tags[] = $matches[1];
|
||||
return '';
|
||||
},
|
||||
$a['title']
|
||||
) );
|
||||
if (count($tags) > 0) {
|
||||
$a['tags'] = implode( ',' , $tags );
|
||||
}
|
||||
|
||||
do_filter('parseSmartSyntax', $title, $a);
|
||||
|
||||
return $a;
|
||||
}
|
||||
|
||||
private function findDuedate(string $s): ?string
|
||||
{
|
||||
$duedate = null;
|
||||
if (preg_match("|(\d+)([dwmy]{1})|",$s, $m)) { // 5d,2w...
|
||||
$count = (int)$m[1];
|
||||
$period = $m[2];
|
||||
if ($period == 'd' || $period == 'w') { // days, weeks
|
||||
if ($period == 'w') $count *= 7;
|
||||
$duedate = date("Y-m-d", time() + 86400*$count);
|
||||
}
|
||||
else if ($period == 'm' || $period == 'y') { //months,years
|
||||
if ($period == 'y') $count *= 12;
|
||||
$a = explode(',', date('Y,m,d'));
|
||||
$y = (int)$a[0];
|
||||
$m = (int)$a[1] + $count;
|
||||
$d = (int)$a[2];
|
||||
if ($m > 12) {
|
||||
$yy = (int)floor($m/12);
|
||||
$y += $yy;
|
||||
$m = $m - $yy*12;
|
||||
}
|
||||
$d = min($d, $this->daysInMonth($m, $y));
|
||||
$duedate = "$y-$m-$d";
|
||||
}
|
||||
}
|
||||
else {
|
||||
$duedate = $this->parseDuedate($s);
|
||||
}
|
||||
return $duedate;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,16 @@ function formatDate3($format, $ay, $am, $ad, $lang)
|
|||
return strtr($format, array('Y'=>$Y, 'y'=>$y, 'F'=>$F, 'M'=>$M, 'n'=>$n, 'm'=>$m, 'd'=>$d, 'j'=>$j));
|
||||
}
|
||||
|
||||
function daysInMonth(int $m, int $y = 0): int
|
||||
{
|
||||
if ($y == 0) $y = (int)date('Y');
|
||||
$isLeap = (0 == $y % 4) && ((0 != $y % 100) || (0 == $y % 400));
|
||||
$a = array(1=>31, ($isLeap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
|
||||
if (isset($a[$m])) return $a[$m];
|
||||
else return 0;
|
||||
}
|
||||
|
||||
|
||||
function getRequestUri()
|
||||
{
|
||||
// Do not use HTTP_X_REWRITE_URL due to CVE-2018-14773
|
||||
|
|
|
|||
196
src/includes/smartsyntax.php
Normal file
196
src/includes/smartsyntax.php
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
/*
|
||||
This file is a part of myTinyTodo.
|
||||
(C) Copyright 2023 Max Pozdeev <maxpozdeev@gmail.com>
|
||||
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
|
||||
*/
|
||||
|
||||
class MTTSmartSyntax implements MTTSmartSyntaxInterface
|
||||
{
|
||||
protected $tagPrefix = '#';
|
||||
protected $duedatePrefix = '@!';
|
||||
|
||||
/** @var MTTSmartSyntaxInterface */
|
||||
protected static $instance;
|
||||
|
||||
public static function instance(): MTTSmartSyntaxInterface
|
||||
{
|
||||
if (!isset(static::$instance)) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
public function parse(string $title): array
|
||||
{
|
||||
$a = [
|
||||
'prio' => 0,
|
||||
'title' => $title,
|
||||
'tags' => '',
|
||||
'duedate' => null,
|
||||
];
|
||||
// priority
|
||||
if ( preg_match("|^([-+]{1}\d+)(.+)|", $a['title'], $m) ) {
|
||||
$a['prio'] = (int) $m[1];
|
||||
if ( $a['prio'] < -1 ) $a['prio'] = -1;
|
||||
elseif ( $a['prio'] > 2 ) $a['prio'] = 2;
|
||||
$a['title'] = trim($m[2]);
|
||||
}
|
||||
// duedate
|
||||
if ( preg_match("|(.+)[{$this->duedatePrefix}]{1}(\S+)$|", $a['title'], $m) ) {
|
||||
$rest = $m[1];
|
||||
$duepre = $m[2];
|
||||
$duedate = $this->findDuedate($duepre);
|
||||
if ($duedate) {
|
||||
$a['duedate'] = $duedate;
|
||||
$a['title'] = $rest;
|
||||
}
|
||||
}
|
||||
// tags
|
||||
$tags = [];
|
||||
$a['title'] = trim( preg_replace_callback(
|
||||
"/(?:^|\s+)[{$this->tagPrefix}]{1}([^{$this->tagPrefix}\s]+)/",
|
||||
function ($matches) use (&$tags) {
|
||||
$tags[] = $matches[1];
|
||||
return '';
|
||||
},
|
||||
$a['title']
|
||||
) );
|
||||
if (count($tags) > 0) {
|
||||
$a['tags'] = implode( ',' , $tags );
|
||||
}
|
||||
return $a;
|
||||
}
|
||||
|
||||
private function findDuedate(string $s): ?string
|
||||
{
|
||||
if (preg_match("|^(\d+)([dwmy]{1})$|",$s, $m)) { // 5d,2w...
|
||||
$count = (int)$m[1];
|
||||
$period = $m[2];
|
||||
if ($period == 'd' || $period == 'w') { // days, weeks
|
||||
if ($period == 'w') $count *= 7;
|
||||
return date("Y-m-d", time() + 86400*$count);
|
||||
}
|
||||
else if ($period == 'm' || $period == 'y') { //months,years
|
||||
if ($period == 'y') $count *= 12;
|
||||
$a = explode(',', date('Y,m,d'));
|
||||
$y = (int)$a[0];
|
||||
$m = (int)$a[1] + $count;
|
||||
$d = (int)$a[2];
|
||||
if ($m > 12) {
|
||||
$yy = (int)floor($m/12);
|
||||
$y += $yy;
|
||||
$m = $m - $yy*12;
|
||||
}
|
||||
$d = min($d, daysInMonth($m, $y));
|
||||
return "$y-$m-$d";
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $duedate = $this->parseDuedate($s)) {
|
||||
return $duedate;
|
||||
}
|
||||
|
||||
$lang = Lang::instance();
|
||||
//TODO: add 3-letter short?
|
||||
$needle = mb_strtolower($s);
|
||||
$wd = null;
|
||||
foreach ($lang->get('days_min') as $idx => $weekday) {
|
||||
if ($needle === mb_strtolower($weekday)) {
|
||||
$wd = $idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (null === $wd) {
|
||||
foreach ($lang->get('days_long') as $idx => $weekday) {
|
||||
if ($needle === mb_strtolower($weekday)) {
|
||||
$wd = $idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (null !== $wd) {
|
||||
$curWD = (int)date('w');
|
||||
$daysAdd = 0;
|
||||
if ($wd <= $curWD) { //next week
|
||||
$daysAdd = 7 - ($curWD - $wd);
|
||||
}
|
||||
else { //current week
|
||||
$daysAdd = $wd - $curWD;
|
||||
}
|
||||
$oDue = new DateTime();
|
||||
$oDue->add( new DateInterval("P{$daysAdd}D") );
|
||||
return $oDue->format('Y-m-d');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse input string as a duedate and return in format "Y-m-d".
|
||||
* Return null if fail.
|
||||
* @param string $s
|
||||
* @return null|string
|
||||
*/
|
||||
public static function parseDuedate(string $s): ?string
|
||||
{
|
||||
$df2 = Config::get('dateformat2');
|
||||
if (max((int)strpos($df2,'n'), (int)strpos($df2,'m')) > max((int)strpos($df2,'d'), (int)strpos($df2,'j'))) $formatDayFirst = true;
|
||||
else $formatDayFirst = false;
|
||||
|
||||
$y = $m = $d = 0;
|
||||
if (preg_match("|^(\d+)-(\d+)-(\d+)\b|", $s, $ma)) {
|
||||
$y = (int)$ma[1]; $m = (int)$ma[2]; $d = (int)$ma[3];
|
||||
}
|
||||
elseif (preg_match("|^(\d+)\/(\d+)\/(\d+)\b|", $s, $ma))
|
||||
{
|
||||
if($formatDayFirst) {
|
||||
$d = (int)$ma[1]; $m = (int)$ma[2]; $y = (int)$ma[3];
|
||||
} else {
|
||||
$m = (int)$ma[1]; $d = (int)$ma[2]; $y = (int)$ma[3];
|
||||
}
|
||||
}
|
||||
elseif (preg_match("|^(\d+)\.(\d+)\.(\d+)\b|", $s, $ma)) {
|
||||
$d = (int)$ma[1]; $m = (int)$ma[2]; $y = (int)$ma[3];
|
||||
}
|
||||
elseif (preg_match("|^(\d+)\.(\d+)\b|", $s, $ma)) {
|
||||
$d = (int)$ma[1]; $m = (int)$ma[2];
|
||||
$a = explode(',', date('Y,m,d'));
|
||||
if( $m<(int)$a[1] || ($m==(int)$a[1] && $d<(int)$a[2]) ) $y = (int)$a[0]+1;
|
||||
else $y = (int)$a[0];
|
||||
}
|
||||
elseif (preg_match("|^(\d+)\/(\d+)\b|", $s, $ma))
|
||||
{
|
||||
if($formatDayFirst) {
|
||||
$d = (int)$ma[1]; $m = (int)$ma[2];
|
||||
} else {
|
||||
$m = (int)$ma[1]; $d = (int)$ma[2];
|
||||
}
|
||||
$a = explode(',', date('Y,m,d'));
|
||||
if( $m<(int)$a[1] || ($m==(int)$a[1] && $d<(int)$a[2]) ) $y = (int)$a[0]+1;
|
||||
else $y = (int)$a[0];
|
||||
}
|
||||
else return null;
|
||||
if ($y < 100) $y = 2000 + $y;
|
||||
elseif ($y < 1000 || $y > 2099) $y = 2000 + (int)substr((string)$y, -2);
|
||||
if ($m > 12) $m = 12;
|
||||
$maxdays = daysInMonth($m,$y);
|
||||
if ($m < 10) $m = '0'.$m;
|
||||
if ($d > $maxdays) $d = $maxdays;
|
||||
elseif ($d < 10) $d = '0'.$d;
|
||||
return "$y-$m-$d";
|
||||
}
|
||||
}
|
||||
|
||||
interface MTTSmartSyntaxInterface
|
||||
{
|
||||
public function parse(string $title): array;
|
||||
}
|
||||
|
||||
function parseSmartSyntax(string $title): ?array
|
||||
{
|
||||
$a = MTTSmartSyntax::instance()->parse($title);
|
||||
do_filter('parseSmartSyntax', $title, $a);
|
||||
return $a;
|
||||
}
|
||||
Loading…
Reference in a new issue