├── .gitignore ├── .hgignore ├── COPYRIGHT ├── LICENSE ├── README.md ├── URL ├── ajax.php ├── class.db.mysql.php ├── class.db.sqlite3.php ├── common.php ├── db └── .htaccess ├── db_sample ├── README.md ├── config.php └── todolist.db ├── drawdown ├── LICENSE ├── README.md ├── drawdown.js └── index.html ├── export.php ├── favicon.ico ├── feed.php ├── index.php ├── init.php ├── jquery ├── index.html ├── jquery-1.4.4.min.js ├── jquery-ui-1.8.7.custom.min.js └── jquery.autocomplete-1.1.js ├── lang ├── .htaccess ├── class.default.php ├── de.php ├── en.php └── ru.php ├── mytinytodo.js ├── mytinytodo_ajax_storage.js ├── mytinytodo_lang.php ├── settings.php ├── setup.php ├── themes ├── default │ ├── images │ │ ├── COPYRIGHT │ │ ├── arrdown.gif │ │ ├── arrdown2.gif │ │ ├── buttons.png │ │ ├── calendar.png │ │ ├── closetag.gif │ │ ├── corner_left.gif │ │ ├── corner_right.gif │ │ ├── fixme.png │ │ ├── icons.gif │ │ ├── index.html │ │ ├── loading1_24.gif │ │ ├── mzl.png │ │ ├── page_white_text.png │ │ ├── tab_hover.gif │ │ ├── time.png │ │ └── user.png │ ├── index.php │ ├── markdown.css │ ├── pda.css │ ├── print.css │ ├── style.css │ ├── style_rtl.css │ └── tags.css └── index.html ├── tmp └── .htaccess └── version.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | .htaccess 3 | .htusers 4 | .idea 5 | db/config.php 6 | db/todolist*.db 7 | tmp/* 8 | scripts 9 | db/backup.db 10 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | src/db/todolist.db 4 | src/tmp/sessions/sess* 5 | src/db/config.php 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | This program is free software; you can redistribute it and/or modify 2 | it under the terms of the GNU General Public License as published by 3 | the Free Software Foundation; either version 2 of the License, or (at 4 | your option) any later version. 5 | 6 | This program is distributed in the hope that it will be useful, but 7 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 8 | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 9 | for more details. 10 | 11 | You should have received a copy of the GNU General Public License 12 | along with this program as the file LICENSE; if not, please see 13 | http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 14 | 15 | 16 | myTDX 17 | -------------- 18 | Url: http://www.tecrd.com 19 | Copyright: 2017 Jérémie FRANCOIS 20 | License: GPL version 2 (see LICENSE file) 21 | 22 | 23 | myTinyTodo 24 | -------------- 25 | Url: http://www.mytinytodo.net/ 26 | Copyright: 2009,2010 Max Pozdeev 27 | License: GPL version 2 (see LICENSE file) 28 | 29 | 30 | myTinyTodo uses other works: 31 | 32 | jQuery 33 | -------------- 34 | Url: http://jquery.com/ 35 | Copyright: 2009 John Resig 36 | License: Dual licensed under the MIT and GPL version 2 licenses (see http://docs.jquery.com/License) 37 | 38 | jQuery UI 39 | -------------- 40 | Url: http://jqueryui.com/ 41 | Copyright: 2009 jQuery UI Authors (see http://jqueryui.com/about) 42 | License: Dual licensed under the MIT and GPL version 2 licenses (see http://jqueryui.com/about) 43 | 44 | Autocomplete - jQuery plugin 45 | ------------------------------ 46 | Url: http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/ 47 | Copyright: 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer 48 | License: Dual licensed under the MIT and GPL licenses: 49 | * http://www.opensource.org/licenses/mit-license.php 50 | * http://www.gnu.org/licenses/gpl.html 51 | 52 | 53 | This software contains images by 3d-parties. See file themes/default/images/COPYRIGHT for details. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **myTDX** (my tiny todo extended) 2 | 3 | As the name suggests, this project is heavily based on an old but very well done ajax todolist 4 | written by maxpozdeev/mytinytodo (http://www.mytinytodo.net/). It also works quite well on mobile phones. 5 | The older project was resumed years after being frozen, and now the two variants have diverged significantly. 6 | 7 | To see it in action ASAP, just rename "db_sample/" as "db/" to bypass the quick setup. 8 | 9 | My fork adds some important features, especially for *sharing with others*, including developers. 10 | I wanted a small, fast, no-nonsense bugtracking system which anyone can understand, including my 11 | clients and the end users. 12 | 13 | - most notably, tasks now have visible identifiers to refer to. There was no way to point to tasks 14 | unambiguously (new in v1.5.3: an option in the Settings lets you hide them). 15 | 16 | - in addition to existing URL, notes can also embed cross-references to other tasks (just use #taskid). 17 | When you click on them you are redirected to the respective task. 18 | 19 | - this cross-reference is done via an enhanced search. You can now look for "#123" or simply its number 20 | to go to the respective task (and its notes will be opened). Looking for non-numerical text will search 21 | the titles and notes. 22 | 23 | - the incoming index URL also can provide the search string (use "?i=taskid" or "?s=keyword"). Thus, 24 | pointing directly to a specific task is done with ?i=123. This is convenient in order to send links 25 | by email, e.g. When you want all related tasks and cross-references you may prefer "?s=123" 26 | There is a *very annoying bug* though, as URLs cannot point and open a task that is not in the first list. 27 | 28 | - 1.5.2: you can write markdown in the notes (it can be deactivated in the settings) 29 | 30 | - the styles of tags can be customized easily, in addition to a few special prefixes: =state, @user, !highlight. 31 | I use them like "=acknoledged" or "=closed" for bug tracking, or "!discussion" to highlight the task, and 32 | someone can visibly be assigned to a task with @joe. 33 | 34 | - the tag list at the end of a task being edited now also shows tags *from other lists*. There is a setting 35 | to change this, but it helps keep things tidy. The grayed tags are "borrowed" from the other lists. 36 | 37 | - Finally, I added a backup system in case you are using sqlite (which I highly recommend here unless you 38 | really have a reason to use mysql). The duration of backup files is set in the settings, but their restoration 39 | must be done manually if ever something terrible happens (easy, it is one single file to rename). 40 | -------------------------------------------------------------------------------- /URL: -------------------------------------------------------------------------------- 1 | http://www.mytinytodo.net/ 2 | -------------------------------------------------------------------------------- /class.db.mysql.php: -------------------------------------------------------------------------------- 1 | 5 | (C) Copyright 2017 fork myTDX by Jérémie FRANCOIS 6 | Licensed under the GNU GPL v2 license. See file COPYRIGHT for details. 7 | */ 8 | 9 | 10 | class DatabaseResult_Mysql 11 | { 12 | 13 | var $parent; 14 | var $q; 15 | var $query; 16 | var $rows = NULL; 17 | var $affected = NULL; 18 | var $prefix = ''; 19 | 20 | function __construct($query, &$h, $resultless = 0) 21 | { 22 | $this->parent = $h; 23 | $this->query = $query; 24 | 25 | $this->q = mysql_query($query, $this->parent->dbh); 26 | 27 | if(!$this->q) 28 | { 29 | throw new Exception($this->parent->error()); 30 | } 31 | } 32 | 33 | function affected() 34 | { 35 | if(is_null($this->affected)) 36 | { 37 | $this->affected = mysql_affected_rows($this->parent->dbh); 38 | } 39 | return $this->affected; 40 | } 41 | 42 | function fetch_row() 43 | { 44 | return mysql_fetch_row($this->q); 45 | } 46 | 47 | function fetch_assoc() 48 | { 49 | return mysql_fetch_assoc($this->q); 50 | } 51 | 52 | function rows() 53 | { 54 | if (!is_null($this -> rows)) return $this->rows; 55 | $this->rows = mysql_num_rows($this->q); 56 | return $this->rows; 57 | } 58 | } 59 | 60 | class Database_Mysql 61 | { 62 | var $dbh; 63 | var $error_str; 64 | 65 | function __construct() 66 | { 67 | } 68 | 69 | function connect($host, $user, $pass, $db) 70 | { 71 | if(!$this->dbh = @mysql_connect($host,$user,$pass)) 72 | { 73 | throw new Exception(mysql_error()); 74 | } 75 | if( @!mysql_select_db($db, $this->dbh) ) 76 | { 77 | throw new Exception($this->error()); 78 | } 79 | return true; 80 | } 81 | 82 | function last_insert_id() 83 | { 84 | return mysql_insert_id($this->dbh); 85 | } 86 | 87 | function error() 88 | { 89 | return mysql_error($this->dbh); 90 | } 91 | 92 | function sq($query, $p = NULL) 93 | { 94 | $q = $this->_dq($query, $p); 95 | 96 | if($q->rows()) $res = $q->fetch_row(); 97 | else return NULL; 98 | 99 | if(sizeof($res) > 1) return $res; 100 | else return $res[0]; 101 | } 102 | 103 | function sqa($query, $p = NULL) 104 | { 105 | $q = $this->_dq($query, $p); 106 | 107 | if($q->rows()) $res = $q->fetch_assoc(); 108 | else return NULL; 109 | 110 | if(sizeof($res) > 1) return $res; 111 | else return $res[0]; 112 | } 113 | 114 | function dq($query, $p = NULL) 115 | { 116 | return $this->_dq($query, $p); 117 | } 118 | 119 | function ex($query, $p = NULL) 120 | { 121 | return $this->_dq($query, $p, 1); 122 | } 123 | 124 | private function _dq($query, $p = NULL, $resultless = 0) 125 | { 126 | if(!isset($p)) $p = array(); 127 | elseif(!is_array($p)) $p = array($p); 128 | 129 | $m = explode('?', $query); 130 | 131 | if(sizeof($p)>0) 132 | { 133 | if(sizeof($m)< sizeof($p)+1) { 134 | throw new Exception("params to set MORE than query params"); 135 | } 136 | if(sizeof($m)> sizeof($p)+1) { 137 | throw new Exception("params to set LESS than query params"); 138 | } 139 | $query = ""; 140 | for($i=0; $iquote($p[$i])); 142 | } 143 | $query .= $m[$i]; 144 | } 145 | return new DatabaseResult_Mysql($query, $this, $resultless); 146 | } 147 | 148 | function affected() 149 | { 150 | return mysql_affected_rows($this->dbh); 151 | } 152 | 153 | function quote($s) 154 | { 155 | return '\''. addslashes($s). '\''; 156 | } 157 | 158 | function quoteForLike($format, $s) 159 | { 160 | $s = str_replace(array('%','_'), array('\%','\_'), addslashes($s)); 161 | return '\''. sprintf($format, $s). '\''; 162 | } 163 | 164 | function table_exists($table) 165 | { 166 | $table = addslashes($table); 167 | $q = mysql_query("SELECT 1 FROM `$table` WHERE 1=0", $this->dbh); 168 | if($q === false) return false; 169 | else return true; 170 | } 171 | } 172 | 173 | ?> -------------------------------------------------------------------------------- /class.db.sqlite3.php: -------------------------------------------------------------------------------- 1 | 5 | (C) Copyright 2017 fork myTDX by Jérémie FRANCOIS 6 | Licensed under the GNU GPL v2 license. See file COPYRIGHT for details. 7 | */ 8 | 9 | class DatabaseResult_Sqlite3 10 | { 11 | private $parent; 12 | private $q; 13 | var $query; 14 | var $prefix; 15 | 16 | function __construct($query, &$h, $resultless = 0) 17 | { 18 | $this->parent = $h; 19 | $this->parent->lastQuery = $this->query = $query; 20 | 21 | if($resultless) 22 | { 23 | $r = $this->parent->dbh->exec($query); 24 | if($r === false) { 25 | $ei = $this->parent->dbh->errorInfo(); 26 | throw new Exception($ei[2]); 27 | } 28 | $this->parent->affected = $r; 29 | } 30 | else 31 | { 32 | $this->q = $this->parent->dbh->query($query); 33 | if(!$this->q) { 34 | $ei = $this->parent->dbh->errorInfo(); 35 | throw new Exception($ei[2]); 36 | } 37 | $this->parent->affected = $this->q->rowCount(); 38 | } 39 | } 40 | 41 | function fetch_row() 42 | { 43 | return $this->q->fetch(PDO::FETCH_NUM); 44 | } 45 | 46 | function fetch_assoc() 47 | { 48 | return $this->q->fetch(PDO::FETCH_ASSOC); 49 | } 50 | 51 | } 52 | 53 | class Database_Sqlite3 54 | { 55 | var $dbh; 56 | var $affected = null; 57 | var $lastQuery; 58 | 59 | function __construct() 60 | { 61 | } 62 | 63 | function connect($filename) 64 | { 65 | try { 66 | $this->dbh = new PDO("sqlite:$filename"); 67 | } 68 | catch(PDOException $e) { 69 | throw new Exception($e->getMessage()); 70 | } 71 | return true; 72 | } 73 | 74 | function sq($query, $p = NULL) 75 | { 76 | $q = $this->_dq($query, $p); 77 | 78 | $res = $q->fetch_row(); 79 | if($res === false) return NULL; 80 | 81 | if(sizeof($res) > 1) return $res; 82 | else return $res[0]; 83 | } 84 | 85 | function sqa($query, $p = NULL) 86 | { 87 | $q = $this->_dq($query, $p); 88 | 89 | $res = $q->fetch_assoc(); 90 | if($res === false) return NULL; 91 | 92 | if(sizeof($res) > 1) return $res; 93 | else return $res[0]; 94 | } 95 | 96 | function dq($query, $p = NULL) 97 | { 98 | return $this->_dq($query, $p); 99 | } 100 | 101 | /* 102 | for resultless queries like INSERT,UPDATE 103 | */ 104 | function ex($query, $p = NULL) 105 | { 106 | return $this->_dq($query, $p, 1); 107 | } 108 | 109 | private function _dq($query, $p = NULL, $resultless = 0) 110 | { 111 | if(!isset($p)) $p = array(); 112 | elseif(!is_array($p)) $p = array($p); 113 | 114 | $m = explode('?', $query); 115 | 116 | if(sizeof($p)>0) 117 | { 118 | if(sizeof($m)< sizeof($p)+1) { 119 | throw new Exception("params to set MORE than query params"); 120 | } 121 | if(sizeof($m)> sizeof($p)+1) { 122 | throw new Exception("params to set LESS than query params"); 123 | } 124 | $query = ""; 125 | for($i=0; $iquote($p[$i])); 127 | } 128 | $query .= $m[$i]; 129 | } 130 | return new DatabaseResult_Sqlite3($query, $this, $resultless); 131 | } 132 | 133 | function affected() 134 | { 135 | return $this->affected; 136 | } 137 | 138 | function quote($s) 139 | { 140 | return $this->dbh->quote($s); 141 | } 142 | 143 | function quoteForLike($format, $s) 144 | { 145 | $s = str_replace(array('\\','%','_'), array('\\\\','\%','\_'), $s); 146 | return $this->dbh->quote(sprintf($format, $s)). " ESCAPE '\'"; 147 | } 148 | 149 | function last_insert_id() 150 | { 151 | return $this->dbh->lastInsertId(); 152 | } 153 | 154 | function table_exists($table) 155 | { 156 | $table = $this->dbh->quote($table); 157 | $q = $this->dbh->query("SELECT 1 FROM $table WHERE 1=0"); 158 | if($q === false) return false; 159 | else return true; 160 | } 161 | } 162 | 163 | ?> -------------------------------------------------------------------------------- /common.php: -------------------------------------------------------------------------------- 1 | 5 | (C) Copyright 2017 fork myTDX by Jérémie FRANCOIS 6 | Licensed under the GNU GPL v2 license. See file COPYRIGHT for details. 7 | */ 8 | 9 | function get_version() 10 | { 11 | return trim(file_get_contents('version.txt')); 12 | } 13 | 14 | function htmlarray($a, $exclude=null) 15 | { 16 | htmlarray_ref($a, $exclude); 17 | return $a; 18 | } 19 | 20 | function htmlarray_ref(&$a, $exclude=null) 21 | { 22 | if(!$a) return; 23 | if(!is_array($a)) { 24 | $a = htmlspecialchars($a); 25 | return; 26 | } 27 | reset($a); 28 | if($exclude && !is_array($exclude)) $exclude = array($exclude); 29 | foreach($a as $k=>$v) 30 | { 31 | if(is_array($v)) $a[$k] = htmlarray($v, $exclude); 32 | elseif(!$exclude) $a[$k] = htmlspecialchars($v); 33 | elseif(!in_array($k, $exclude)) $a[$k] = htmlspecialchars($v); 34 | } 35 | return; 36 | } 37 | 38 | function _post($param,$defvalue = '') 39 | { 40 | if(!isset($_POST[$param])) { 41 | return $defvalue; 42 | } 43 | else { 44 | return $_POST[$param]; 45 | } 46 | } 47 | 48 | function _get($param,$defvalue = '') 49 | { 50 | if(!isset($_GET[$param])) { 51 | return $defvalue; 52 | } 53 | else { 54 | return $_GET[$param]; 55 | } 56 | } 57 | 58 | class Config 59 | { 60 | public static $params = array( 61 | 'db' => array('default'=>'sqlite', 'type'=>'s'), 62 | 'mysql.host' => array('default'=>'localhost', 'type'=>'s'), 63 | 'mysql.db' => array('default'=>'mytinytodo', 'type'=>'s'), 64 | 'mysql.user' => array('default'=>'user', 'type'=>'s'), 65 | 'mysql.password' => array('default'=>'', 'type'=>'s'), 66 | 'prefix' => array('default'=>'', 'type'=>'s'), 67 | 'url' => array('default'=>'', 'type'=>'s'), 68 | 'mtt_url' => array('default'=>'', 'type'=>'s'), 69 | 'title' => array('default'=>'', 'type'=>'s'), 70 | 'lang' => array('default'=>'en', 'type'=>'s'), 71 | 'password' => array('default'=>'', 'type'=>'s'), 72 | 'smartsyntax' => array('default'=>1, 'type'=>'i'), 73 | 'markdown' => array('default'=>1, 'type'=>'i'), 74 | 'timezone' => array('default'=>'UTC', 'type'=>'s'), 75 | 'autotag' => array('default'=>1, 'type'=>'i'), 76 | 'duedateformat' => array('default'=>1, 'type'=>'i'), 77 | 'firstdayofweek' => array('default'=>1, 'type'=>'i'), 78 | 'session' => array('default'=>'files', 'type'=>'s', 'options'=>array('files','default')), 79 | 'clock' => array('default'=>24, 'type'=>'i', 'options'=>array(12,24)), 80 | 'dateformat' => array('default'=>'j M Y', 'type'=>'s'), 81 | 'dateformat2' => array('default'=>'n/j/y', 'type'=>'s'), 82 | 'dateformatshort' => array('default'=>'j M', 'type'=>'s'), 83 | 'template' => array('default'=>'default', 'type'=>'s'), 84 | 'showdate' => array('default'=>0, 'type'=>'i'), 85 | 'alientags' => array('default'=>0, 'type'=>'i'), 86 | 'taskxrefs' => array('default'=>0, 'type'=>'i'), 87 | 'dbbackup' => array('default'=>30, 'type'=>'i', 'options'=>array(-1,0,1,7,30,365)), 88 | ); 89 | 90 | public static $config; 91 | 92 | public static function loadConfig($config) 93 | { 94 | self::$config = $config; 95 | } 96 | 97 | public static function get($key) 98 | { 99 | if(isset(self::$config[$key])) return self::$config[$key]; 100 | elseif(isset(self::$params[$key])) return self::$params[$key]['default']; 101 | else return null; 102 | } 103 | 104 | public static function set($key, $value) 105 | { 106 | self::$config[$key] = $value; 107 | } 108 | 109 | public static function save() 110 | { 111 | $s = ''; 112 | foreach(self::$params as $param=>$v) 113 | { 114 | if(!isset(self::$config[$param])) $val = $v['default']; 115 | elseif(isset($v['options']) && !in_array(self::$config[$param], $v['options'])) $val = $v['default']; 116 | else $val = self::$config[$param]; 117 | if($v['type']=='i') { 118 | $s .= "\$config['$param'] = ".(int)$val.";\n"; 119 | } 120 | else { 121 | $s .= "\$config['$param'] = '".str_replace(array("\\","'"),array("\\\\","\\'"),$val)."';\n"; 122 | } 123 | } 124 | $f = fopen(MTTPATH. 'db/config.php', 'w'); 125 | if($f === false) throw new Exception("Error while saving config file"); 126 | fwrite($f, ""); 127 | fclose($f); 128 | } 129 | } 130 | 131 | function formatDate3($format, $ay, $am, $ad, $lang) 132 | { 133 | # F - month long, M - month short 134 | # m - month 2-digit, n - month 1-digit 135 | # d - day 2-digit, j - day 1-digit 136 | $ml = $lang->get('months_long'); 137 | $ms = $lang->get('months_short'); 138 | $Y = $ay; 139 | $y = $Y < 2010 ? '0'.($Y-2000) : $Y-2000; 140 | $n = $am; 141 | $m = $n < 10 ? '0'.$n : $n; 142 | $F = $ml[$am-1]; 143 | $M = $ms[$am-1]; 144 | $j = $ad; 145 | $d = $j < 10 ? '0'.$j : $j; 146 | return strtr($format, array('Y'=>$Y, 'y'=>$y, 'F'=>$F, 'M'=>$M, 'n'=>$n, 'm'=>$m, 'd'=>$d, 'j'=>$j)); 147 | } 148 | 149 | function url_dir($url) 150 | { 151 | if(false !== $p = strpos($url, '?')) $url = substr($url,0,$p); # to avoid parse errors on strange query strings 152 | $p = parse_url($url, PHP_URL_PATH); 153 | if($p == '') return '/'; 154 | if(substr($p,-1) == '/') return $p; 155 | if(false !== $pos = strrpos($p,'/')) return substr($p,0,$pos+1); 156 | return '/'; 157 | } 158 | 159 | function escapeTags($s) 160 | { 161 | $c1 = chr(1); 162 | $c2 = chr(2); 163 | $s = preg_replace("~([\s\S]*?)~i", "${c1}b${c2}\$1${c1}/b${c2}", $s); 164 | $s = preg_replace("~([\s\S]*?)~i", "${c1}i${c2}\$1${c1}/i${c2}", $s); 165 | $s = preg_replace("~([\s\S]*?)~i", "${c1}u${c2}\$1${c1}/u${c2}", $s); 166 | $s = preg_replace("~([\s\S]*?)~i", "${c1}s${c2}\$1${c1}/s${c2}", $s); 167 | $s = str_replace(array($c1, $c2), array('<','>'), htmlspecialchars($s)); 168 | return $s; 169 | } 170 | 171 | /* found in comments on http://www.php.net/manual/en/function.uniqid.php#94959 */ 172 | function generateUUID() 173 | { 174 | return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', 175 | mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), 176 | mt_rand(0, 0x0fff) | 0x4000, 177 | mt_rand(0, 0x3fff) | 0x8000, 178 | mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) 179 | ); 180 | } 181 | 182 | class DBConnection 183 | { 184 | protected static $instance; 185 | 186 | public static function init($instance) 187 | { 188 | self::$instance = $instance; 189 | return $instance; 190 | } 191 | 192 | public static function instance() 193 | { 194 | if (!isset(self::$instance)) { 195 | //$c = __CLASS__; 196 | $c = 'DBConnection'; 197 | self::$instance = new $c; 198 | } 199 | return self::$instance; 200 | } 201 | } 202 | 203 | function are_files_identical($fn1, $fn2) 204 | { 205 | if(filesize($fn1) !== filesize($fn2)) return FALSE; 206 | if(filetype($fn1) !== filetype($fn2)) return FALSE; 207 | if(!$fp1 = fopen($fn1, 'rb')) return FALSE; 208 | if(!$fp2 = fopen($fn2, 'rb')) { fclose($fp1); return FALSE; } 209 | $same = TRUE; 210 | while(!feof($fp1) and !feof($fp2)) 211 | if(fread($fp1, 16384) !== fread($fp2, 16384)) { $same = FALSE; break; } 212 | if(feof($fp1) !== feof($fp2)) $same = FALSE; 213 | fclose($fp1); 214 | fclose($fp2); 215 | return $same; 216 | } 217 | 218 | ?> 219 | -------------------------------------------------------------------------------- /db/.htaccess: -------------------------------------------------------------------------------- 1 | deny from all -------------------------------------------------------------------------------- /db_sample/README.md: -------------------------------------------------------------------------------- 1 | This folder is not used per se. You may want to use the config.php and todolist.db files 2 | to start with, by copying them into your own db/ folder. They are configured for sqlite, 3 | and the entries contain a few informative tips. 4 | -------------------------------------------------------------------------------- /db_sample/config.php: -------------------------------------------------------------------------------- 1 | Block quotes, including 14 | > > nested block quotes. 15 | 16 | ``` 17 | Fenced code blocks 18 | ``` 19 | 20 | Indented code blocks 21 | 22 | 1. Numbered lists 23 | - Unordered lists 24 | - Nested in other lists 25 | a. Lettered lists are an extension to the spec. 26 | b. They may be useful for legal documents. 27 | 2. Another entry in my numbered list. 28 | 29 | | Tables | Tables | Tables | 30 | | ------ | ------ | ------ | 31 | | Cell 1 | Cell 2 | Cell 3 | 32 | | Cell 4 | Cell 5 | Cell 6 | 33 | | Cell 7 | Cell 8 | Cell 9 | 34 | 35 | [Links](https://github.com/adamvleggett/drawdown) 36 | 37 | Images: 38 | 39 | ![Images](https://img.icons8.com/ios/452/stack-of-photos.png) 40 | 41 | --- 42 | 43 | ### Summary... 44 | 45 | These are the supported features: 46 | 47 | - Block quotes 48 | - Code blocks 49 | - Links 50 | - Images 51 | - Headings 52 | - Lists (including lettered lists) 53 | - Bold 54 | - Italic 55 | - Strikethrough 56 | - Monospace 57 | - Subscript 58 | - Horizontal rule 59 | - Tables 60 | 61 | Unsupported Markdown features at this time: 62 | 63 | - Line blocks 64 | - Definition lists 65 | - Footnotes 66 | - Twitter/Facebook/YouTube embed 67 | - Inline math equations 68 | 69 | To use: 70 | 71 | element.innerHTML = markdown(text); -------------------------------------------------------------------------------- /drawdown/drawdown.js: -------------------------------------------------------------------------------- 1 | /** 2 | * drawdown.js 3 | * (c) Adam Leggett 4 | */ 5 | 6 | 7 | ;function markdown(src) { 8 | 9 | var rx_lt = //g; 11 | var rx_space = /\t|\r|\uf8ff/g; 12 | var rx_escape = /\\([\\\|`*_{}\[\]()#+\-~])/g; 13 | var rx_hr = /^([*\-=_] *){3,}$/gm; 14 | var rx_blockquote = /\n *> *([^]*?)(?=(\n|$){2})/g; 15 | var rx_list = /\n( *)(?:[*\-+]|((\d+)|([a-z])|[A-Z])[.)]) +([^]*?)(?=(\n|$){2})/g; 16 | var rx_listjoin = /<\/(ol|ul)>\n\n<\1>/g; 17 | var rx_highlight = /(^|[^A-Za-z\d\\])(([*_])|(~)|(\^)|(--)|(\+\+)|`)(\2?)([^<]*?)\2\8(?!\2)(?=\W|_|$)/g; 18 | var rx_code = /\n((```|~~~).*\n?([^]*?)\n?\2|(( .*?\n)+))/g; 19 | var rx_link = /((!?)\[(.*?)\]\((.*?)( ".*")?\)|\\([\\`*_{}\[\]()#+\-.!~]))/g; 20 | var rx_table = /\n(( *\|.*?\| *\n)+)/g; 21 | var rx_thead = /^.*\n( *\|( *\:?-+\:?-+\:? *\|)* *\n|)/; 22 | var rx_row = /.*\n/g; 23 | var rx_cell = /\||(.*?[^\\])\|/g; 24 | var rx_heading = /(?=^|>|\n)([>\s]*?)(#{1,6}) (.*?)( #*)? *(?=\n|$)/g; 25 | var rx_para = /(?=^|>|\n)\s*\n+([^<]+?)\n+\s*(?=\n|<|$)/g; 26 | var rx_stash = /-\d+\uf8ff/g; 27 | 28 | function replace(rex, fn) { 29 | src = src.replace(rex, fn); 30 | } 31 | 32 | function element(tag, content) { 33 | return '<' + tag + '>' + content + ''; 34 | } 35 | 36 | function blockquote(src) { 37 | return src.replace(rx_blockquote, function(all, content) { 38 | return element('blockquote', blockquote(highlight(content.replace(/^ *> */gm, '')))); 39 | }); 40 | } 41 | 42 | function list(src) { 43 | return src.replace(rx_list, function(all, ind, ol, num, low, content) { 44 | var entry = element('li', highlight(content.split( 45 | RegExp('\n ?' + ind + '(?:(?:\\d+|[a-zA-Z])[.)]|[*\\-+]) +', 'g')).map(list).join('
  • '))); 46 | 47 | return '\n' + (ol 48 | ? '
      ' 50 | : parseInt(ol,36) - 9 + '" style="list-style-type:' + (low ? 'low' : 'upp') + 'er-alpha">') + entry + '
    ' 51 | : element('ul', entry)); 52 | }); 53 | } 54 | 55 | function highlight(src) { 56 | return src.replace(rx_highlight, function(all, _, p1, emp, sub, sup, small, big, p2, content) { 57 | return _ + element( 58 | emp ? (p2 ? 'strong' : 'em') 59 | : sub ? (p2 ? 's' : 'sub') 60 | : sup ? 'sup' 61 | : small ? 'small' 62 | : big ? 'big' 63 | : 'code', 64 | highlight(content)); 65 | }); 66 | } 67 | 68 | function unesc(str) { 69 | return str.replace(rx_escape, '$1'); 70 | } 71 | 72 | var stash = []; 73 | var si = 0; 74 | 75 | src = '\n' + src + '\n'; 76 | 77 | replace(rx_lt, '<'); 78 | replace(rx_gt, '>'); 79 | replace(rx_space, ' '); 80 | 81 | // blockquote 82 | src = blockquote(src); 83 | 84 | // horizontal rule 85 | replace(rx_hr, '
    '); 86 | 87 | // list 88 | src = list(src); 89 | replace(rx_listjoin, ''); 90 | 91 | // code 92 | replace(rx_code, function(all, p1, p2, p3, p4) { 93 | stash[--si] = element('pre', element('code', p3||p4.replace(/^ /gm, ''))); 94 | return si + '\uf8ff'; 95 | }); 96 | 97 | // link or image 98 | replace(rx_link, function(all, p1, p2, p3, p4, p5, p6) { 99 | stash[--si] = p4 100 | ? p2 101 | ? '' + p3 + '' 102 | : '' + unesc(highlight(p3)) + '' 103 | : p6; 104 | return si + '\uf8ff'; 105 | }); 106 | 107 | // table 108 | replace(rx_table, function(all, table) { 109 | var sep = table.match(rx_thead)[1]; 110 | return '\n' + element('table', 111 | table.replace(rx_row, function(row, ri) { 112 | return row == sep ? '' : element('tr', row.replace(rx_cell, function(all, cell, ci) { 113 | return ci ? element(sep && !ri ? 'th' : 'td', unesc(highlight(cell || ''))) : '' 114 | })) 115 | }) 116 | ) 117 | }); 118 | 119 | // heading 120 | replace(rx_heading, function(all, _, p1, p2) { return _ + element('h' + p1.length, unesc(highlight(p2))) }); 121 | 122 | // paragraph 123 | replace(rx_para, function(all, content) { return element('p', unesc(highlight(content))) }); 124 | 125 | // stash 126 | replace(rx_stash, function(all) { return stash[parseInt(all)] }); 127 | 128 | return src.trim(); 129 | }; 130 | -------------------------------------------------------------------------------- /drawdown/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 81 | drawdown test 82 | 83 | 84 | 157 |
    158 | 159 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /export.php: -------------------------------------------------------------------------------- 1 | 5 | (C) Copyright 2017 fork myTDX by Jérémie FRANCOIS 6 | Licensed under the GNU GPL v2 license. See file COPYRIGHT for details. 7 | */ 8 | 9 | //$dontStartSession = 1; 10 | require_once('./init.php'); 11 | 12 | $onlyPublishedList = false; 13 | if(!have_write_access()) $onlyPublishedList = true; 14 | 15 | $listId = (int)_get('list'); 16 | $listData = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$listId ". ($onlyPublishedList ? "AND published=1" : "") ); 17 | if(!$listData) { 18 | die("No such list or access denied"); 19 | } 20 | 21 | $sqlSort = "ORDER BY compl ASC, "; 22 | if($listData['sorting'] == 1) $sqlSort .= "prio DESC, ddn ASC, duedate ASC, ow ASC"; 23 | elseif($listData['sorting'] == 2) $sqlSort .= "ddn ASC, duedate ASC, prio DESC, ow ASC"; 24 | else $sqlSort .= "ow ASC"; 25 | 26 | $data = array(); 27 | $q = $db->dq("SELECT *, duedate IS NULL AS ddn FROM {$db->prefix}todolist WHERE list_id=$listId $sqlSort"); 28 | while($r = $q->fetch_assoc($q)) 29 | { 30 | $data[] = $r; 31 | } 32 | 33 | $format = _get('format'); 34 | 35 | if($format == 'ical') printICal($listData, $data); 36 | else printCSV($listData, $data); 37 | 38 | 39 | function have_write_access() 40 | { 41 | if(Config::get('password') == '') return true; 42 | if(is_logged()) return true; 43 | return false; 44 | } 45 | 46 | 47 | function printCSV($listData, $data) 48 | { 49 | $s = /*chr(0xEF).chr(0xBB).chr(0xBF).*/ "Completed,Priority,Task,Notes,Tags,Due,DateCreated,DateCompleted\n"; 50 | foreach($data as $r) 51 | { 52 | $s .= ($r['compl']?'1':'0'). ','. $r['prio']. ','. escape_csv($r['title']). ','. 53 | escape_csv($r['note']). ','. escape_csv($r['tags']). ','. $r['duedate']. 54 | ','. date('Y-m-d H:i:s O',$r['d_created']). ','. ($r['d_completed'] ? date('Y-m-d H:i:s O',$r['d_completed']) :''). "\n"; 55 | } 56 | header('Content-type: text/csv; charset=utf-8'); 57 | header('Content-disposition: attachment; filename=list_'.$listData['id'].'.csv'); 58 | print $s; 59 | } 60 | 61 | function escape_csv($v) 62 | { 63 | return '"'.str_replace('"', '""', $v).'"'; 64 | } 65 | 66 | function printICal($listData, $data) 67 | { 68 | $mttToIcalPrio = array("1" => 5, "2" => 1); 69 | $s = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nMETHOD:PUBLISH\r\nCALSCALE:GREGORIAN\r\nPRODID:-//myTinyTodo//iCalendar Export v1.4//EN\r\n". 70 | "X-WR-CALNAME:". $listData['name']. "\r\nX-MTT-TIMEZONE:".Config::get('timezone')."\r\n"; 71 | # to-do 72 | foreach($data as $r) 73 | { 74 | $a = array(); 75 | $a[] = "BEGIN:VTODO"; 76 | $a[] = "UID:". $r['uuid']; 77 | $a[] = "CREATED:". gmdate('Ymd\THis\Z', $r['d_created']); 78 | $a[] = "DTSTAMP:". gmdate('Ymd\THis\Z', $r['d_edited']); 79 | $a[] = "LAST-MODIFIED:". gmdate('Ymd\THis\Z', $r['d_edited']); 80 | $a[] = utf8chunks("SUMMARY:". $r['title']); 81 | if($r['duedate']) { 82 | $dda = explode('-', $r['duedate']); 83 | $a[] = "DUE;VALUE=DATE:".sprintf("%u%02u%02u", $dda[0], $dda[1], $dda[2]); 84 | } 85 | # Apple's iCal priorities: low-9, medium-5, high-1 86 | if($r['prio'] > 0 && isset($mttToIcalPrio[$r['prio']])) $a[] = "PRIORITY:". $mttToIcalPrio[$r['prio']]; 87 | $a[] = "X-MTT-PRIORITY:". $r['prio']; 88 | 89 | $descr = array(); 90 | if($r['tags'] != '') $descr[] = Lang::instance()->get('tags'). ": ". str_replace(',', ', ', $r['tags']); 91 | if($r['note'] != '') $descr[] = Lang::instance()->get('note'). ": ". $r['note']; 92 | if($descr) $a[] = utf8chunks("DESCRIPTION:". str_replace("\n", '\\n', implode("\n",$descr))); 93 | 94 | if($r['compl']) { 95 | $a[] = "STATUS:COMPLETED"; #used in Sunbird 96 | $a[] = "COMPLETED:". gmdate('Ymd\THis\Z', $r['d_completed']); 97 | #$a[] = "PERCENT-COMPLETE:100"; #used in Sunbird 98 | } 99 | if($r['tags'] != '') $a[] = utf8chunks("X-MTT-TAGS:". $r['tags']); 100 | $a[] = "END:VTODO\r\n"; 101 | $s .= implode("\r\n", $a); 102 | } 103 | # events 104 | foreach($data as $r) 105 | { 106 | if(!$r['duedate'] || $r['compl']) continue; # skip tasks completed and without duedate 107 | $a = array(); 108 | $a[] = "BEGIN:VEVENT"; 109 | $a[] = "UID:_". $r['uuid']; # do not duplicate VTODO UID 110 | $a[] = "CREATED:". gmdate('Ymd\THis\Z', $r['d_created']); 111 | $a[] = "DTSTAMP:". gmdate('Ymd\THis\Z', $r['d_edited']); 112 | $a[] = "LAST-MODIFIED:". gmdate('Ymd\THis\Z', $r['d_edited']); 113 | $a[] = utf8chunks("SUMMARY:". $r['title']); 114 | if($r['prio'] > 0 && isset($mttToIcalPrio[$r['prio']])) $a[] = "PRIORITY:". $mttToIcalPrio[$r['prio']]; 115 | $dda = explode('-', $r['duedate']); 116 | $a[] = "DTSTART;VALUE=DATE:".sprintf("%u%02u%02u", $dda[0], $dda[1], $dda[2]); 117 | $a[] = "DTEND;VALUE=DATE:".date('Ymd', mktime(1,1,1,$dda[1],$dda[2],$dda[0]) + 86400); 118 | $descr = array(); 119 | if($r['tags'] != '') $descr[] = Lang::instance()->get('tags'). ": ". str_replace(',', ', ', $r['tags']); 120 | if($r['note'] != '') $descr[] = Lang::instance()->get('note'). ": ". $r['note']; 121 | if($descr) $a[] = utf8chunks("DESCRIPTION:". str_replace("\n", '\\n', implode("\n",$descr))); 122 | $a[] = "END:VEVENT\r\n"; 123 | $s .= implode("\r\n", $a); 124 | } 125 | $s .= "END:VCALENDAR\r\n"; 126 | header('Content-type: text/calendar; charset=utf-8'); 127 | header('Content-disposition: attachment; filename=list_'.$listData['id'].'.ics'); 128 | print $s; 129 | } 130 | 131 | function utf8chunks($text, $chunklen=75, $delimiter="\r\n\t") 132 | { 133 | if($text == '') return ''; 134 | preg_match_all('/./u', $text, $m); 135 | $chars = $m[0]; 136 | $a = array(); 137 | $s = ''; 138 | $max = count($chars); 139 | for($i=0; $i<$max; $i++) 140 | { 141 | $ch = $chars[$i]; 142 | if(strlen($s) + strlen($ch) > $chunklen) { # line should be not more than $chunklen bytes 143 | $a[] = $s; 144 | $s = $ch; 145 | } 146 | else $s .= $ch; 147 | } 148 | if($s != '') $a[] = $s; 149 | return implode($delimiter, $a); 150 | } 151 | 152 | ?> -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/favicon.ico -------------------------------------------------------------------------------- /feed.php: -------------------------------------------------------------------------------- 1 | 5 | (C) Copyright 2017 fork myTDX by Jérémie FRANCOIS 6 | Licensed under the GNU GPL v2 license. See file COPYRIGHT for details. 7 | */ 8 | 9 | $dontStartSession = 1; 10 | require_once('./init.php'); 11 | 12 | $lang = Lang::instance(); 13 | 14 | $listId = (int)_get('list'); 15 | 16 | $listData = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$listId"); 17 | if($needAuth && (!$listData || !$listData['published'])) { 18 | die("Access denied!
    List is not published."); 19 | } 20 | if(!$listData) { 21 | die("No such list"); 22 | } 23 | 24 | $feedType = _get('feed'); 25 | $sqlWhere = ''; 26 | if($feedType == 'completed') { 27 | $listData['_uid_field'] = 'd_completed'; 28 | $listData['_feed_descr'] = $lang->get('feed_completed_tasks'); 29 | $sqlWhere = 'AND compl=1'; 30 | } 31 | elseif($feedType == 'modified') { 32 | $listData['_uid_field'] = 'd_edited'; 33 | $listData['_feed_descr'] = $lang->get('feed_modified_tasks'); 34 | } 35 | elseif($feedType == 'current') { 36 | $listData['_uid_field'] = 'd_created'; 37 | $listData['_feed_descr'] = $lang->get('feed_new_tasks'); 38 | $sqlWhere = 'AND compl=0'; 39 | } 40 | else { 41 | $listData['_uid_field'] = 'd_created'; 42 | $listData['_feed_descr'] = $lang->get('feed_new_tasks'); 43 | } 44 | 45 | $listData['_feed_title'] = sprintf($lang->get('feed_title'), $listData['name']) . ' - '. $listData['_feed_descr']; 46 | htmlarray_ref($listData); 47 | 48 | $data = array(); 49 | $q = $db->dq("SELECT * FROM {$db->prefix}todolist WHERE list_id=$listId $sqlWhere ORDER BY ". $listData['_uid_field'] ." DESC LIMIT 100"); 50 | while($r = $q->fetch_assoc($q)) 51 | { 52 | if($r['prio'] > 0) $r['prio'] = '+'.$r['prio']; 53 | $a = array(); 54 | if($r['prio']) $a[] = $lang->get('priority'). ": $r[prio]"; 55 | if($r['duedate'] != '') { 56 | $ad = explode('-', $r['duedate']); 57 | $a[] = $lang->get('due'). ": ".formatDate3(Config::get('dateformat'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); 58 | } 59 | if($r['tags'] != '') $a[] = $lang->get('tags'). ": ". str_replace(',', ', ', $r['tags']); 60 | if($r['compl']) $a[] = $lang->get('taskdate_completed'). ": ". timestampToDatetime($r['d_completed']); 61 | $r['title'] = strip_tags($r['title']); 62 | $r['note'] = escapeTags($r['note']); 63 | $r['_descr'] = nl2br($r['note']). ($a && $r['note']!='' ? "

    " : ""). implode("
    ", htmlarray($a)); 64 | $data[] = $r; 65 | } 66 | 67 | printRss($listData, $data); 68 | 69 | 70 | function printRss($listData, $data) 71 | { 72 | $link = get_mttinfo('url'). "?list=". $listData['id']; 73 | $buildDate = gmdate('r'); 74 | 75 | $s = "\n\n\n". 76 | "$listData[_feed_title]\n$link\n$listData[_feed_descr]\n". 77 | "$buildDate\n\n"; 78 | 79 | foreach($data as $v) 80 | { 81 | $d = gmdate('r', $v[$listData['_uid_field']]); 82 | $guid = $listData['id'].'-'.$v['id'].'-'.$v[$listData['_uid_field']]; 83 | 84 | $s .= "\n<![CDATA[". str_replace("]]>", "]]]]><![CDATA[>", $v['title']). "]]>\n". 85 | "$link\n". 86 | "$d\n". 87 | "\n". 88 | "$guid\n". 89 | "\n"; 90 | } 91 | 92 | $s .= "\n"; 93 | 94 | header("Content-type: text/xml; charset=utf-8"); 95 | print $s; 96 | } 97 | 98 | ?> -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 4 | (C) Copyright 2017 fork myTDX by Jérémie FRANCOIS 5 | Licensed under the GNU GPL v2 license. See file COPYRIGHT for details. 6 | */ 7 | 8 | if(!is_file('db/config.php') || !is_file('db/todolist.db')) 9 | die('Bad or missing configuration. Did you run the setup?'); 10 | 11 | require_once('./init.php'); 12 | 13 | $lang = Lang::instance(); 14 | 15 | if($lang->rtl()) Config::set('rtl', 1); 16 | 17 | if(!is_int(Config::get('firstdayofweek')) || Config::get('firstdayofweek')<0 || Config::get('firstdayofweek')>6) Config::set('firstdayofweek', 1); 18 | 19 | define('TEMPLATEPATH', MTTPATH. 'themes/'.Config::get('template').'/'); 20 | 21 | require(TEMPLATEPATH. 'index.php'); 22 | 23 | -------------------------------------------------------------------------------- /init.php: -------------------------------------------------------------------------------- 1 | 4 | (C) Copyright 2017 fork myTDX by Jérémie FRANCOIS 5 | Licensed under the GNU GPL v2 license. See file COPYRIGHT for details. 6 | */ 7 | 8 | if(!defined('MTTPATH')) define('MTTPATH', dirname(__FILE__) .'/'); 9 | 10 | require_once(MTTPATH. 'common.php'); 11 | require_once(MTTPATH. 'db/config.php'); 12 | 13 | ini_set('display_errors', 'On'); 14 | 15 | if(!isset($config)) global $config; 16 | Config::loadConfig($config); 17 | unset($config); 18 | 19 | date_default_timezone_set(Config::get('timezone')); 20 | 21 | # MySQL Database Connection 22 | if(Config::get('db') == 'mysql') 23 | { 24 | require_once(MTTPATH. 'class.db.mysql.php'); 25 | $db = DBConnection::init(new Database_Mysql); 26 | $db->connect(Config::get('mysql.host'), Config::get('mysql.user'), Config::get('mysql.password'), Config::get('mysql.db')); 27 | $db->dq("SET NAMES utf8"); 28 | } 29 | 30 | # SQLite3 (pdo_sqlite) 31 | elseif(Config::get('db') == 'sqlite') 32 | { 33 | require_once(MTTPATH. 'class.db.sqlite3.php'); 34 | $db = DBConnection::init(new Database_Sqlite3); 35 | $db->connect(MTTPATH. 'db/todolist.db'); 36 | } 37 | else { 38 | # It seems not installed 39 | die("Not installed. Run setup.php first."); 40 | } 41 | $db->prefix = Config::get('prefix'); 42 | 43 | //User can override language setting by cookies 44 | if(isset($_COOKIE['lang']) && preg_match("/^[a-z-]+$/i", $_COOKIE['lang']) && file_exists('lang/'. $_COOKIE['lang']. '.php')) { 45 | Config::set('lang', $_COOKIE['lang']); 46 | } 47 | 48 | require_once(MTTPATH. 'lang/class.default.php'); 49 | require_once(MTTPATH. 'lang/'.Config::get('lang').'.php'); 50 | 51 | $_mttinfo = array(); 52 | 53 | $needAuth = (Config::get('password') != '') ? 1 : 0; 54 | if($needAuth && !isset($dontStartSession)) 55 | { 56 | if(Config::get('session') == 'files') 57 | { 58 | session_save_path(MTTPATH. 'tmp/sessions'); 59 | ini_set('session.gc_maxlifetime', '1209600'); # 14 days session file minimum lifetime 60 | ini_set('session.gc_probability', 1); 61 | ini_set('session.gc_divisor', 10); 62 | } 63 | 64 | ini_set('session.use_cookies', true); 65 | ini_set('session.use_only_cookies', true); 66 | session_set_cookie_params(1209600, url_dir(Config::get('url')=='' ? $_SERVER['REQUEST_URI'] : Config::get('url'))); # 14 days session cookie lifetime 67 | session_name('mtt-session'); 68 | session_start(); 69 | } 70 | 71 | function is_logged() 72 | { 73 | if(!isset($_SESSION['logged']) || !$_SESSION['logged']) return false; 74 | return true; 75 | } 76 | 77 | function is_readonly() 78 | { 79 | $needAuth = (Config::get('password') != '') ? 1 : 0; 80 | if($needAuth && !is_logged()) return true; 81 | return false; 82 | } 83 | 84 | function timestampToDatetime($timestamp) 85 | { 86 | $format = Config::get('dateformat') .' '. (Config::get('clock') == 12 ? 'g:i A' : 'H:i'); 87 | return formatTime($format, $timestamp); 88 | } 89 | 90 | function formatTime($format, $timestamp=0) 91 | { 92 | $lang = Lang::instance(); 93 | if($timestamp == 0) $timestamp = time(); 94 | $newformat = strtr($format, array('F'=>'%1', 'M'=>'%2')); 95 | $adate = explode(',', date('n,'.$newformat, $timestamp), 2); 96 | $s = $adate[1]; 97 | if($newformat != $format) 98 | { 99 | $am = (int)$adate[0]; 100 | $ml = $lang->get('months_long'); 101 | $ms = $lang->get('months_short'); 102 | $F = $ml[$am-1]; 103 | $M = $ms[$am-1]; 104 | $s = strtr($s, array('%1'=>$F, '%2'=>$M)); 105 | } 106 | return $s; 107 | } 108 | 109 | function _e($s) 110 | { 111 | echo Lang::instance()->get($s); 112 | } 113 | 114 | function __($s) 115 | { 116 | return Lang::instance()->get($s); 117 | } 118 | 119 | function mttinfo($v) 120 | { 121 | global $_mttinfo; 122 | if(!isset($_mttinfo[$v])) { 123 | echo get_mttinfo($v); 124 | } else { 125 | echo $_mttinfo[$v]; 126 | } 127 | } 128 | 129 | function get_mttinfo($v) 130 | { 131 | global $_mttinfo; 132 | if(isset($_mttinfo[$v])) return $_mttinfo[$v]; 133 | switch($v) 134 | { 135 | case 'template_url': 136 | $_mttinfo['template_url'] = get_mttinfo('mtt_url'). 'themes/'. Config::get('template') . '/'; 137 | return $_mttinfo['template_url']; 138 | case 'url': 139 | $_mttinfo['url'] = Config::get('url'); 140 | if($_mttinfo['url'] == '') 141 | $_mttinfo['url'] = 'http://'.$_SERVER['HTTP_HOST'] .($_SERVER['SERVER_PORT'] != 80 ? ':'.$_SERVER['SERVER_PORT'] : ''). 142 | url_dir(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $_SERVER['SCRIPT_NAME']); 143 | return $_mttinfo['url']; 144 | case 'mtt_url': 145 | $_mttinfo['mtt_url'] = Config::get('mtt_url'); 146 | if($_mttinfo['mtt_url'] == '') $_mttinfo['mtt_url'] = url_dir(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $_SERVER['SCRIPT_NAME']); 147 | return $_mttinfo['mtt_url']; 148 | case 'title': 149 | $_mttinfo['title'] = (Config::get('title') != '') ? htmlarray(Config::get('title')) : __('My Tiny Todolist'); 150 | return $_mttinfo['title']; 151 | } 152 | } 153 | 154 | function jsonExit($data) 155 | { 156 | header('Content-type: application/json; charset=utf-8'); 157 | echo json_encode($data); 158 | exit; 159 | } 160 | 161 | ?> -------------------------------------------------------------------------------- /jquery/index.html: -------------------------------------------------------------------------------- 1 | Place for jQuery UI -------------------------------------------------------------------------------- /jquery/jquery.autocomplete-1.1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Autocomplete plugin 1.1 3 | * 4 | * Copyright (c) 2009 Jörn Zaefferer 5 | * 6 | * Dual licensed under the MIT and GPL licenses: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.gnu.org/licenses/gpl.html 9 | * 10 | * Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $ 11 | */ 12 | 13 | /* 14 | * Changes for myTinyTodo, www.mytinytodo.net 15 | * Copyright (c) 2010 Max Pozdeev 16 | * Dual licensed under the MIT and GPL licenses 17 | */ 18 | 19 | ;(function($) { 20 | 21 | $.fn.extend({ 22 | autocomplete: function(urlOrData, options) { 23 | var isUrl = typeof urlOrData == "string"; 24 | var isFunc = typeof urlOrData == "function"; 25 | options = $.extend({}, $.Autocompleter.defaults, { 26 | url: isUrl ? urlOrData : null, 27 | data: !isUrl && !isFunc ? urlOrData : null, 28 | func: isFunc ? urlOrData : null, 29 | delay: isUrl ? $.Autocompleter.defaults.delay : 10, 30 | max: options && !options.scroll ? 10 : 150 31 | }, options); 32 | 33 | // if highlight is set to false, replace it with a do-nothing function 34 | options.highlight = options.highlight || function(value) { return value; }; 35 | 36 | // if the formatMatch option is not specified, then use formatItem for backwards compatibility 37 | options.formatMatch = options.formatMatch || options.formatItem; 38 | 39 | return this.each(function() { 40 | new $.Autocompleter(this, options); 41 | }); 42 | }, 43 | result: function(handler) { 44 | return this.bind("result", handler); 45 | }, 46 | search: function(handler) { 47 | return this.trigger("search", [handler]); 48 | }, 49 | flushCache: function() { 50 | return this.trigger("flushCache"); 51 | }, 52 | setOptions: function(options){ 53 | return this.trigger("setOptions", [options]); 54 | }, 55 | unautocomplete: function() { 56 | return this.trigger("unautocomplete"); 57 | } 58 | }); 59 | 60 | $.Autocompleter = function(input, options) { 61 | 62 | var KEY = { 63 | UP: 38, 64 | DOWN: 40, 65 | DEL: 46, 66 | TAB: 9, 67 | RETURN: 13, 68 | ESC: 27, 69 | COMMA: 188, 70 | PAGEUP: 33, 71 | PAGEDOWN: 34, 72 | BACKSPACE: 8 73 | }; 74 | 75 | // Create $ object for input element 76 | var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); 77 | 78 | var timeout; 79 | var previousValue = ""; 80 | var cache = $.Autocompleter.Cache(options); 81 | var hasFocus = 0; 82 | var lastKeyPressCode; 83 | var config = { 84 | mouseDownOnSelect: false 85 | }; 86 | var select = $.Autocompleter.Select(options, input, selectCurrent, config); 87 | 88 | var blockSubmit; 89 | 90 | // prevent form submit in opera when selecting with return key 91 | $.browser.opera && $(input.form).bind("submit.autocomplete", function() { 92 | if (blockSubmit) { 93 | blockSubmit = false; 94 | return false; 95 | } 96 | }); 97 | 98 | // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all 99 | $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { 100 | // a keypress means the input has focus 101 | // avoids issue where input had focus before the autocomplete was applied 102 | hasFocus = 1; 103 | // track last key pressed 104 | lastKeyPressCode = event.keyCode; 105 | switch(event.keyCode) { 106 | 107 | case KEY.UP: 108 | event.preventDefault(); 109 | if ( select.visible() ) { 110 | select.prev(); 111 | } else { 112 | onChange(0, true); 113 | } 114 | break; 115 | 116 | case KEY.DOWN: 117 | event.preventDefault(); 118 | if ( select.visible() ) { 119 | select.next(); 120 | } else { 121 | onChange(0, true); 122 | } 123 | break; 124 | 125 | case KEY.PAGEUP: 126 | event.preventDefault(); 127 | if ( select.visible() ) { 128 | select.pageUp(); 129 | } else { 130 | onChange(0, true); 131 | } 132 | break; 133 | 134 | case KEY.PAGEDOWN: 135 | event.preventDefault(); 136 | if ( select.visible() ) { 137 | select.pageDown(); 138 | } else { 139 | onChange(0, true); 140 | } 141 | break; 142 | 143 | // matches also semicolon 144 | case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: 145 | case KEY.TAB: 146 | case KEY.RETURN: 147 | if( selectCurrent() ) { 148 | // stop default to prevent a form submit, Opera needs special handling 149 | event.preventDefault(); 150 | blockSubmit = true; 151 | return false; 152 | } 153 | break; 154 | 155 | case KEY.ESC: 156 | select.hide(); 157 | break; 158 | 159 | default: 160 | clearTimeout(timeout); 161 | timeout = setTimeout(onChange, options.delay); 162 | break; 163 | } 164 | }).focus(function(){ 165 | // track whether the field has focus, we shouldn't process any 166 | // results if the field no longer has focus 167 | hasFocus++; 168 | }).blur(function() { 169 | hasFocus = 0; 170 | if (!config.mouseDownOnSelect) { 171 | hideResults(); 172 | } 173 | }).click(function() { 174 | // show select when clicking in a focused field 175 | if ( hasFocus++ > 1 && !select.visible() ) { 176 | onChange(0, true); 177 | } 178 | }).bind("search", function() { 179 | // TODO why not just specifying both arguments? 180 | var fn = (arguments.length > 1) ? arguments[1] : null; 181 | function findValueCallback(q, data) { 182 | var result; 183 | if( data && data.length ) { 184 | for (var i=0; i < data.length; i++) { 185 | if( data[i].result.toLowerCase() == q.toLowerCase() ) { 186 | result = data[i]; 187 | break; 188 | } 189 | } 190 | } 191 | if( typeof fn == "function" ) fn(result); 192 | else $input.trigger("result", result && [result.data, result.value]); 193 | } 194 | $.each(trimWords($input.val()), function(i, value) { 195 | request(value, findValueCallback, findValueCallback); 196 | }); 197 | }).bind("flushCache", function() { 198 | cache.flush(); 199 | }).bind("setOptions", function() { 200 | $.extend(options, arguments[1]); 201 | // if we've updated the data, repopulate 202 | if ( "data" in arguments[1] ) 203 | cache.populate(); 204 | }).bind("unautocomplete", function() { 205 | select.unbind(); 206 | $input.unbind(); 207 | $(input.form).unbind(".autocomplete"); 208 | }); 209 | 210 | 211 | function selectCurrent() { 212 | var selected = select.selected(); 213 | if( !selected ) 214 | return false; 215 | 216 | var v = selected.result; 217 | previousValue = v; 218 | 219 | if ( options.multiple ) { 220 | var words = trimWords($input.val()); 221 | if ( words.length > 1 ) { 222 | var seperator = options.multipleSeparator.length; 223 | var cursorAt = $(input).selection().start; 224 | var wordAt, progress = 0; 225 | $.each(words, function(i, word) { 226 | progress += word.length; 227 | if (cursorAt <= progress) { 228 | wordAt = i; 229 | return false; 230 | } 231 | progress += seperator; 232 | }); 233 | words[wordAt] = v; 234 | // TODO this should set the cursor to the right position, but it gets overriden somewhere 235 | //$.Autocompleter.Selection(input, progress + seperator, progress + seperator); 236 | v = words.join( options.multipleSeparator ); 237 | } 238 | v += options.multipleSeparator; 239 | } 240 | 241 | $input.val(v); 242 | hideResultsNow(); 243 | $input.trigger("result", [selected.data, selected.value]); 244 | return true; 245 | } 246 | 247 | function onChange(crap, skipPrevCheck) { 248 | if( lastKeyPressCode == KEY.DEL ) { 249 | select.hide(); 250 | return; 251 | } 252 | 253 | var currentValue = $input.val(); 254 | 255 | if ( !skipPrevCheck && currentValue == previousValue ) 256 | return; 257 | 258 | previousValue = currentValue; 259 | 260 | currentValue = lastWord(currentValue); 261 | if ( currentValue.length >= options.minChars) { 262 | $input.addClass(options.loadingClass); 263 | if (!options.matchCase) 264 | currentValue = currentValue.toLowerCase(); 265 | request(currentValue, receiveData, hideResultsNow); 266 | } else { 267 | stopLoading(); 268 | select.hide(); 269 | } 270 | }; 271 | 272 | function trimWords(value) { 273 | if (!value) 274 | return [""]; 275 | if (!options.multiple) 276 | return [$.trim(value)]; 277 | return $.map(value.split(options.multipleSeparator), function(word) { 278 | return $.trim(value).length ? $.trim(word) : null; 279 | }); 280 | } 281 | 282 | function lastWord(value) { 283 | if ( !options.multiple ) 284 | return value; 285 | var words = trimWords(value); 286 | if (words.length == 1) 287 | return words[0]; 288 | var cursorAt = $(input).selection().start; 289 | if (cursorAt == value.length) { 290 | words = trimWords(value) 291 | } else { 292 | words = trimWords(value.replace(value.substring(cursorAt), "")); 293 | } 294 | return words[words.length - 1]; 295 | } 296 | 297 | // fills in the input box w/the first match (assumed to be the best match) 298 | // q: the term entered 299 | // sValue: the first matching result 300 | function autoFill(q, sValue){ 301 | // autofill in the complete box w/the first match as long as the user hasn't entered in more data 302 | // if the last user key pressed was backspace, don't autofill 303 | if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { 304 | // fill in the value (keep the case the user has typed) 305 | $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); 306 | // select the portion of the value not typed by the user (so the next character will erase) 307 | $(input).selection(previousValue.length, previousValue.length + sValue.length); 308 | } 309 | }; 310 | 311 | function hideResults() { 312 | clearTimeout(timeout); 313 | timeout = setTimeout(hideResultsNow, 200); 314 | }; 315 | 316 | function hideResultsNow() { 317 | var wasVisible = select.visible(); 318 | select.hide(); 319 | clearTimeout(timeout); 320 | stopLoading(); 321 | if (options.mustMatch) { 322 | // call search and run callback 323 | $input.search( 324 | function (result){ 325 | // if no value found, clear the input box 326 | if( !result ) { 327 | if (options.multiple) { 328 | var words = trimWords($input.val()).slice(0, -1); 329 | $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); 330 | } 331 | else { 332 | $input.val( "" ); 333 | $input.trigger("result", null); 334 | } 335 | } 336 | } 337 | ); 338 | } 339 | }; 340 | 341 | function receiveData(q, data) { 342 | if ( data && data.length && hasFocus ) { 343 | stopLoading(); 344 | select.display(data, q); 345 | autoFill(q, data[0].value); 346 | select.show(); 347 | } else { 348 | hideResultsNow(); 349 | } 350 | }; 351 | 352 | function request(term, success, failure) { 353 | if (!options.matchCase) 354 | term = term.toLowerCase(); 355 | var data = cache.load(term); 356 | // recieve the cached data 357 | if (data && data.length) { 358 | success(term, data); 359 | } 360 | // if function was supplied 361 | else if(options.func) 362 | { 363 | var extraParams = { 364 | timestamp: +new Date() 365 | }; 366 | $.each(options.extraParams, function(key, param) { 367 | extraParams[key] = typeof param == "function" ? param() : param; 368 | }); 369 | 370 | options.func({ 371 | data: $.extend({ 372 | q: lastWord(term), 373 | limit: options.max 374 | }, extraParams)}, 375 | function(data){ 376 | var parsed = options.parse && options.parse(data) || parse(data); 377 | cache.add(term, parsed) 378 | success(term, parsed); 379 | } 380 | ); 381 | } 382 | // if an AJAX url has been supplied, try loading the data now 383 | else if( (typeof options.url == "string") && (options.url.length > 0) ){ 384 | 385 | var extraParams = { 386 | timestamp: +new Date() 387 | }; 388 | $.each(options.extraParams, function(key, param) { 389 | extraParams[key] = typeof param == "function" ? param() : param; 390 | }); 391 | 392 | $.ajax({ 393 | // try to leverage ajaxQueue plugin to abort previous requests 394 | mode: "abort", 395 | // limit abortion to this input 396 | port: "autocomplete" + input.name, 397 | dataType: options.dataType, 398 | url: options.url, 399 | data: $.extend({ 400 | q: lastWord(term), 401 | limit: options.max 402 | }, extraParams), 403 | success: function(data) { 404 | var parsed = options.parse && options.parse(data) || parse(data); 405 | cache.add(term, parsed); 406 | success(term, parsed); 407 | } 408 | }); 409 | } else { 410 | // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match 411 | select.emptyList(); 412 | failure(term); 413 | } 414 | }; 415 | 416 | function parse(data) { 417 | var parsed = []; 418 | var rows = data.split("\n"); 419 | for (var i=0; i < rows.length; i++) { 420 | var row = $.trim(rows[i]); 421 | if (row) { 422 | row = row.split("|"); 423 | parsed[parsed.length] = { 424 | data: row, 425 | value: row[0], 426 | result: options.formatResult && options.formatResult(row, row[0]) || row[0] 427 | }; 428 | } 429 | } 430 | return parsed; 431 | }; 432 | 433 | function stopLoading() { 434 | $input.removeClass(options.loadingClass); 435 | }; 436 | 437 | }; 438 | 439 | $.Autocompleter.defaults = { 440 | inputClass: "ac_input", 441 | resultsClass: "ac_results", 442 | loadingClass: "ac_loading", 443 | minChars: 1, 444 | delay: 400, 445 | matchCase: false, 446 | matchSubset: true, 447 | matchContains: false, 448 | cacheLength: 10, 449 | max: 100, 450 | mustMatch: false, 451 | extraParams: {}, 452 | selectFirst: true, 453 | formatItem: function(row) { return row[0]; }, 454 | formatMatch: null, 455 | autoFill: false, 456 | width: 0, 457 | multiple: false, 458 | multipleSeparator: ", ", 459 | highlight: function(value, term) { 460 | return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); 461 | }, 462 | scroll: true, 463 | scrollHeight: 180 464 | }; 465 | 466 | $.Autocompleter.Cache = function(options) { 467 | 468 | var data = {}; 469 | var length = 0; 470 | 471 | function matchSubset(s, sub) { 472 | if (!options.matchCase) 473 | s = s.toLowerCase(); 474 | var i = s.indexOf(sub); 475 | if (options.matchContains == "word"){ 476 | i = s.toLowerCase().search("\\b" + sub.toLowerCase()); 477 | } 478 | if (i == -1) return false; 479 | return i == 0 || options.matchContains; 480 | }; 481 | 482 | function add(q, value) { 483 | if (length > options.cacheLength){ 484 | flush(); 485 | } 486 | if (!data[q]){ 487 | length++; 488 | } 489 | data[q] = value; 490 | } 491 | 492 | function populate(){ 493 | if( !options.data ) return false; 494 | // track the matches 495 | var stMatchSets = {}, 496 | nullData = 0; 497 | 498 | // no url was specified, we need to adjust the cache length to make sure it fits the local data store 499 | if( !options.url && !options.func ) options.cacheLength = 1; 500 | 501 | // track all options for minChars = 0 502 | stMatchSets[""] = []; 503 | 504 | // loop through the array and create a lookup structure 505 | for ( var i = 0, ol = options.data.length; i < ol; i++ ) { 506 | var rawValue = options.data[i]; 507 | // if rawValue is a string, make an array otherwise just reference the array 508 | rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; 509 | 510 | var value = options.formatMatch(rawValue, i+1, options.data.length); 511 | if ( value === false ) 512 | continue; 513 | 514 | var firstChar = value.charAt(0).toLowerCase(); 515 | // if no lookup array for this character exists, look it up now 516 | if( !stMatchSets[firstChar] ) 517 | stMatchSets[firstChar] = []; 518 | 519 | // if the match is a string 520 | var row = { 521 | value: value, 522 | data: rawValue, 523 | result: options.formatResult && options.formatResult(rawValue) || value 524 | }; 525 | 526 | // push the current match into the set list 527 | stMatchSets[firstChar].push(row); 528 | 529 | // keep track of minChars zero items 530 | if ( nullData++ < options.max ) { 531 | stMatchSets[""].push(row); 532 | } 533 | }; 534 | 535 | // add the data items to the cache 536 | $.each(stMatchSets, function(i, value) { 537 | // increase the cache size 538 | options.cacheLength++; 539 | // add to the cache 540 | add(i, value); 541 | }); 542 | } 543 | 544 | // populate any existing data 545 | setTimeout(populate, 25); 546 | 547 | function flush(){ 548 | data = {}; 549 | length = 0; 550 | } 551 | 552 | return { 553 | flush: flush, 554 | add: add, 555 | populate: populate, 556 | load: function(q) { 557 | if (!options.cacheLength || !length) 558 | return null; 559 | /* 560 | * if dealing w/local data and matchContains than we must make sure 561 | * to loop through all the data collections looking for matches 562 | */ 563 | if( (!options.url && !options.func) && options.matchContains ){ 564 | // track all matches 565 | var csub = []; 566 | // loop through all the data grids for matches 567 | for( var k in data ){ 568 | // don't search through the stMatchSets[""] (minChars: 0) cache 569 | // this prevents duplicates 570 | if( k.length > 0 ){ 571 | var c = data[k]; 572 | $.each(c, function(i, x) { 573 | // if we've got a match, add it to the array 574 | if (matchSubset(x.value, q)) { 575 | csub.push(x); 576 | } 577 | }); 578 | } 579 | } 580 | return csub; 581 | } else 582 | // if the exact item exists, use it 583 | if (data[q]){ 584 | return data[q]; 585 | } else 586 | if (options.matchSubset) { 587 | for (var i = q.length - 1; i >= options.minChars; i--) { 588 | var c = data[q.substr(0, i)]; 589 | if (c) { 590 | var csub = []; 591 | $.each(c, function(i, x) { 592 | if (matchSubset(x.value, q)) { 593 | csub[csub.length] = x; 594 | } 595 | }); 596 | return csub; 597 | } 598 | } 599 | } 600 | return null; 601 | } 602 | }; 603 | }; 604 | 605 | $.Autocompleter.Select = function (options, input, select, config) { 606 | var CLASSES = { 607 | ACTIVE: "ac_over" 608 | }; 609 | 610 | var listItems, 611 | active = -1, 612 | data, 613 | term = "", 614 | needsInit = true, 615 | element, 616 | list; 617 | 618 | // Create results 619 | function init() { 620 | if (!needsInit) 621 | return; 622 | element = $("
    ") 623 | .hide() 624 | .addClass(options.resultsClass) 625 | .css("position", "absolute") 626 | .appendTo(document.body); 627 | 628 | list = $("
      ").appendTo(element).mouseover( function(event) { 629 | if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { 630 | active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); 631 | $(target(event)).addClass(CLASSES.ACTIVE); 632 | } 633 | }).click(function(event) { 634 | $(target(event)).addClass(CLASSES.ACTIVE); 635 | select(); 636 | // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus 637 | input.focus(); 638 | return false; 639 | }).mousedown(function() { 640 | config.mouseDownOnSelect = true; 641 | }).mouseup(function() { 642 | config.mouseDownOnSelect = false; 643 | }); 644 | 645 | if( options.width > 0 ) 646 | element.css("width", options.width); 647 | 648 | needsInit = false; 649 | } 650 | 651 | function target(event) { 652 | var element = event.target; 653 | while(element && element.tagName.toUpperCase() != "LI") 654 | element = element.parentNode; 655 | // more fun with IE, sometimes event.target is empty, just ignore it then 656 | if(!element) 657 | return []; 658 | return element; 659 | } 660 | 661 | function moveSelect(step) { 662 | listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); 663 | movePosition(step); 664 | var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); 665 | if(options.scroll) { 666 | var offset = 0; 667 | listItems.slice(0, active).each(function() { 668 | offset += this.offsetHeight; 669 | }); 670 | if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { 671 | list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); 672 | } else if(offset < list.scrollTop()) { 673 | list.scrollTop(offset); 674 | } 675 | } 676 | }; 677 | 678 | function movePosition(step) { 679 | active += step; 680 | if (active < 0) { 681 | active = listItems.size() - 1; 682 | } else if (active >= listItems.size()) { 683 | active = 0; 684 | } 685 | } 686 | 687 | function limitNumberOfItems(available) { 688 | return options.max && options.max < available 689 | ? options.max 690 | : available; 691 | } 692 | 693 | function fillList() { 694 | list.empty(); 695 | var max = limitNumberOfItems(data.length); 696 | for (var i=0; i < max; i++) { 697 | if (!data[i]) 698 | continue; 699 | var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term); 700 | if ( formatted === false ) 701 | continue; 702 | var li = $("
    • ").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0]; 703 | $.data(li, "ac_data", data[i]); 704 | } 705 | listItems = list.find("li"); 706 | if ( options.selectFirst ) { 707 | listItems.slice(0, 1).addClass(CLASSES.ACTIVE); 708 | active = 0; 709 | } 710 | // apply bgiframe if available 711 | if ( $.fn.bgiframe ) 712 | list.bgiframe(); 713 | } 714 | 715 | return { 716 | display: function(d, q) { 717 | init(); 718 | data = d; 719 | term = q; 720 | fillList(); 721 | }, 722 | next: function() { 723 | moveSelect(1); 724 | }, 725 | prev: function() { 726 | moveSelect(-1); 727 | }, 728 | pageUp: function() { 729 | if (active != 0 && active - 8 < 0) { 730 | moveSelect( -active ); 731 | } else { 732 | moveSelect(-8); 733 | } 734 | }, 735 | pageDown: function() { 736 | if (active != listItems.size() - 1 && active + 8 > listItems.size()) { 737 | moveSelect( listItems.size() - 1 - active ); 738 | } else { 739 | moveSelect(8); 740 | } 741 | }, 742 | hide: function() { 743 | element && element.hide(); 744 | listItems && listItems.removeClass(CLASSES.ACTIVE); 745 | active = -1; 746 | }, 747 | visible : function() { 748 | return element && element.is(":visible"); 749 | }, 750 | current: function() { 751 | return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); 752 | }, 753 | show: function() { 754 | var offset = $(input).offset(); 755 | element.css({ 756 | width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), 757 | top: offset.top + input.offsetHeight, 758 | left: offset.left 759 | }).show(); 760 | if(options.scroll) { 761 | list.scrollTop(0); 762 | list.css({ 763 | maxHeight: options.scrollHeight, 764 | overflow: 'auto' 765 | }); 766 | 767 | if($.browser.msie && typeof document.body.style.maxHeight === "undefined") { 768 | var listHeight = 0; 769 | listItems.each(function() { 770 | listHeight += this.offsetHeight; 771 | }); 772 | var scrollbarsVisible = listHeight > options.scrollHeight; 773 | list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight ); 774 | if (!scrollbarsVisible) { 775 | // IE doesn't recalculate width when scrollbar disappears 776 | listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) ); 777 | } 778 | } 779 | 780 | } 781 | }, 782 | selected: function() { 783 | var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); 784 | return selected && selected.length && $.data(selected[0], "ac_data"); 785 | }, 786 | emptyList: function (){ 787 | list && list.empty(); 788 | }, 789 | unbind: function() { 790 | element && element.remove(); 791 | } 792 | }; 793 | }; 794 | 795 | $.fn.selection = function(start, end) { 796 | if (start !== undefined) { 797 | return this.each(function() { 798 | if( this.createTextRange ){ 799 | var selRange = this.createTextRange(); 800 | if (end === undefined || start == end) { 801 | selRange.move("character", start); 802 | selRange.select(); 803 | } else { 804 | selRange.collapse(true); 805 | selRange.moveStart("character", start); 806 | selRange.moveEnd("character", end); 807 | selRange.select(); 808 | } 809 | } else if( this.setSelectionRange ){ 810 | this.setSelectionRange(start, end); 811 | } else if( this.selectionStart ){ 812 | this.selectionStart = start; 813 | this.selectionEnd = end; 814 | } 815 | }); 816 | } 817 | var field = this[0]; 818 | if ( field.createTextRange ) { 819 | var range = document.selection.createRange(), 820 | orig = field.value, 821 | teststring = "<->", 822 | textLength = range.text.length; 823 | range.text = teststring; 824 | var caretAt = field.value.indexOf(teststring); 825 | field.value = orig; 826 | this.selection(caretAt, caretAt + textLength); 827 | return { 828 | start: caretAt, 829 | end: caretAt + textLength 830 | } 831 | } else if( field.selectionStart !== undefined ){ 832 | return { 833 | start: field.selectionStart, 834 | end: field.selectionEnd 835 | } 836 | } 837 | }; 838 | 839 | })(jQuery); -------------------------------------------------------------------------------- /lang/.htaccess: -------------------------------------------------------------------------------- 1 | deny from all -------------------------------------------------------------------------------- /lang/class.default.php: -------------------------------------------------------------------------------- 1 | "Are you sure you want to delete the task?", 15 | 'confirmLeave' => "There can be unsaved data. Do you really want to leave?", 16 | 'actionNoteSave' => "save", 17 | 'actionNoteCancel' => "cancel", 18 | 'error' => "Some error occurred (click for details)", 19 | 'denied' => "Access denied", 20 | 'invalidpass' => "Wrong password", 21 | 'tagfilter' => "Tag:", 22 | 'addList' => "Create new list", 23 | 'addListDefault' => "Todo", 24 | 'renameList' => "Rename list", 25 | 'deleteList' => "This will delete current list with all tasks in it.\\nAre you sure?", 26 | 'clearCompleted' => "This will delete all completed tasks in the list.\\nAre you sure?", 27 | 'settingsSaved' => "Settings saved. Reloading...", 28 | ); 29 | 30 | private $default_inc = array 31 | ( 32 | 'My Tiny Todolist' => "My Tiny Todolist", 33 | 'htab_newtask' => "New task", 34 | 'htab_search' => "Search", 35 | 'btn_add' => "Add", 36 | 'btn_search' => "Search", 37 | 'advanced_add' => "Advanced", 38 | 'searching' => "Searching for", 39 | 'tasks' => "Tasks", 40 | 'taskdate_inline_created' => "Created %s", 41 | 'taskdate_inline_completed' => "Completed %s", 42 | 'taskdate_inline_duedate' => "Due %s", 43 | 'taskdate_created' => "Created", 44 | 'taskdate_completed' => "Completed", 45 | 'go_back' => "<< Back", 46 | 'edit_task' => "Edit Task", 47 | 'add_task' => "New Task", 48 | 'priority' => "Priority", 49 | 'task' => "Task", 50 | 'note' => "Note", 51 | 'tags' => "Tags", 52 | 'tags_descr' => "(prefixes: =state, @user, ~fix, !highlight)", 53 | 'save' => "Save", 54 | 'cancel' => "Cancel", 55 | 'password' => "Password", 56 | 'btn_login' => "Login", 57 | 'a_login' => "Login", 58 | 'a_logout' => "Logout", 59 | 'public_tasks' => "Public Tasks", 60 | 'tagcloud' => "Tags", 61 | 'tagfilter_cancel' => "cancel filter", 62 | 'sortByHand' => "Sort by hand", 63 | 'sortByPriority' => "Sort by priority", 64 | 'sortByDueDate' => "Sort by due date", 65 | 'sortByDateCreated' => "Sort by date created", 66 | 'sortByDateModified' => "Sort by date modified", 67 | 'due' => "Due", 68 | 'daysago' => "%d days ago", 69 | 'indays' => "in %d days", 70 | 'months_short' => array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"), 71 | 'months_long' => array("January","February","March","April","May","June","July","August","September","October","November","December"), 72 | 'days_min' => array("Su","Mo","Tu","We","Th","Fr","Sa"), 73 | 'days_long' => array("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"), 74 | 'today' => "today", 75 | 'yesterday' => "yesterday", 76 | 'tomorrow' => "tomorrow", 77 | 'f_past' => "Overdue", 78 | 'f_today' => "Today and tomorrow", 79 | 'f_soon' => "Soon", 80 | 'action_edit' => "Edit", 81 | 'action_note' => "Edit Note", 82 | 'action_delete' => "Delete", 83 | 'action_priority' => "Priority", 84 | 'action_move' => "Move to", 85 | 'notes' => "Notes:", 86 | 'notes_show' => "Show", 87 | 'notes_hide' => "Hide", 88 | 'list_new' => "New list", 89 | 'list_rename' => "Rename list", 90 | 'list_delete' => "Delete list", 91 | 'list_publish' => "Publish list", 92 | 'list_hide' => "Hide list", 93 | 'list_showcompleted' => "Show completed tasks", 94 | 'list_clearcompleted' => "Clear completed tasks", 95 | 'list_select' => "Select list", 96 | 'list_export' => "Export", 97 | 'list_export_csv' => "CSV", 98 | 'list_export_ical' => "iCalendar", 99 | 'list_rssfeed' => "RSS Feed", 100 | 'alltags' => "All tags:", 101 | 'alltags_show' => "Show all", 102 | 'alltags_hide' => "Hide all", 103 | 'a_settings' => "Settings", 104 | 'rss_feed' => "RSS Feed", 105 | 'feed_title' => "%s", 106 | 'feed_completed_tasks' => "Completed tasks", 107 | 'feed_modified_tasks' => "Modified tasks", 108 | 'feed_new_tasks' => "New tasks", 109 | 'alltasks' => "All tasks", 110 | 111 | /* Settings */ 112 | 'set_header' => "Settings", 113 | 'set_title' => "Title", 114 | 'set_title_descr' => "(specify if you want to change default title)", 115 | 'set_language' => "Language", 116 | 'set_protection' => "Password protection", 117 | 'set_enabled' => "Enabled", 118 | 'set_disabled' => "Disabled", 119 | 'set_newpass' => "New password", 120 | 'set_newpass_descr' => "(leave blank to keep current password, separate multiple passwords with blanks)", 121 | 'set_smartsyntax' => "Smart syntax", 122 | 'set_smartsyntax_descr' => "(/priority/ task /tags/)", 123 | 'set_markdown' => "Use markdown in notes", 124 | 'set_timezone' => "Time zone", 125 | 'set_autotag' => "Autotagging", 126 | 'set_autotag_descr' => "(automatically adds tag of current tag filter to newly created task)", 127 | 'set_sessions' => "Session handling mechanism", 128 | 'set_sessions_php' => "PHP", 129 | 'set_sessions_files' => "Files", 130 | 'set_firstdayofweek' => "First day of week", 131 | 'set_custom' => "Custom", 132 | 'set_date' => "Date format", 133 | 'set_date2' => "Short Date format", 134 | 'set_shortdate' => "Short Date (current year)", 135 | 'set_clock' => "Clock format", 136 | 'set_12hour' => "12-hour", 137 | 'set_24hour' => "24-hour", 138 | 'set_7day' => "One week", 139 | 'set_1month' => "One month", 140 | 'set_1year' => "One year", 141 | 'set_always' => "Indefinitely", 142 | 'set_submit' => "Submit changes", 143 | 'set_cancel' => "Cancel", 144 | 'set_showdate' => "Show task date in list", 145 | 'set_alientags' => "Show tags from other lists", 146 | 'set_taskxrefs' => "Show task identifiers", 147 | 'set_dbbackup' => "Keep backups for", 148 | 'set_dbbackup_descr' => "This works only with sqlite databases", 149 | ); 150 | 151 | var $js = array(); 152 | var $inc = array(); 153 | 154 | function makeJS() 155 | { 156 | $a = array(); 157 | foreach($this->default_js as $k=>$v) 158 | { 159 | if(isset($this->js[$k])) $v = $this->js[$k]; 160 | 161 | if(is_array($v)) { 162 | $a[] = "$k: ". $v[0]; 163 | } else { 164 | $a[] = "$k: \"". str_replace('"','\\"',$v). "\""; 165 | } 166 | } 167 | $t = array(); 168 | foreach($this->get('days_min') as $v) { $t[] = '"'.str_replace('"','\\"',$v).'"'; } 169 | $a[] = "daysMin: [". implode(',', $t). "]"; 170 | $t = array(); 171 | foreach($this->get('days_long') as $v) { $t[] = '"'.str_replace('"','\\"',$v).'"'; } 172 | $a[] = "daysLong: [". implode(',', $t). "]"; 173 | $t = array(); 174 | foreach($this->get('months_long') as $v) { $t[] = '"'.str_replace('"','\\"',$v).'"'; } 175 | $a[] = "monthsLong: [". implode(',', $t). "]"; 176 | $a[] = $this->_2js('tags'); 177 | $a[] = $this->_2js('tasks'); 178 | $a[] = $this->_2js('f_past'); 179 | $a[] = $this->_2js('f_today'); 180 | $a[] = $this->_2js('f_soon'); 181 | return "{\n". implode(",\n", $a). "\n}"; 182 | } 183 | 184 | function _2js($v) 185 | { 186 | return "$v: \"". str_replace('"','\\"',$this->get($v)). "\""; 187 | } 188 | 189 | function get($key) 190 | { 191 | if(isset($this->inc[$key])) return $this->inc[$key]; 192 | if(isset($this->default_inc[$key])) return $this->default_inc[$key]; 193 | return $key; 194 | } 195 | 196 | function rtl() 197 | { 198 | return $this->rtl ? 1 : 0; 199 | } 200 | 201 | public static function instance() 202 | { 203 | if (!isset(self::$instance)) { 204 | //$c = __CLASS__; 205 | $c = 'Lang'; 206 | self::$instance = new $c; 207 | } 208 | return self::$instance; 209 | } 210 | } 211 | 212 | ?> 213 | -------------------------------------------------------------------------------- /lang/de.php: -------------------------------------------------------------------------------- 1 | 'Willst Du die Aufgabe wirklich löschen?', 17 | 'confirmLeave' => 'Einige Daten wurden noch nicht gespeichert. Willst du die Seite wirklich verlassen?', 18 | 'actionNoteSave' => 'speichern', 19 | 'actionNoteCancel' => 'abbrechen', 20 | 'error' => 'Fehler aufgetreten (für Details klicken)', 21 | 'denied' => 'Zugriff verweigert', 22 | 'invalidpass' => 'Falsches Passwort', 23 | 'tagfilter' => 'Schlagwort:', 24 | 'addList' => 'Neue Liste anlegen', 25 | 'renameList' => 'Liste umbenennen', 26 | 'deleteList' => 'Die Liste wird mit allen Aufgaben gelöscht.\\nBist Du sicher?', 27 | 'clearCompleted' => 'Alle abgeschlossenen Aufgaben dieser Liste werden gelöscht.\\nBist Du sicher?', 28 | 'settingsSaved' => 'Einstellungen gespeichert. Aktualisierung...', 29 | ); 30 | 31 | var $inc = array 32 | ( 33 | 'htab_newtask' => 'Neue Aufgabe', 34 | 'htab_search' => 'Suche', 35 | 'btn_add' => 'Hinzufügen', 36 | 'btn_search' => 'Suche', 37 | 'advanced_add' => 'Erweitert', 38 | 'searching' => 'Suche nach', 39 | 'tasks' => 'Aufgabe', 40 | 'taskdate_inline_created' => 'hinzugefügt am %s', 41 | 'taskdate_inline_completed' => 'Erledigt am %s', 42 | 'taskdate_inline_duedate' => 'Fällig am %s', 43 | 'taskdate_created' => 'Erstellt', 44 | 'taskdate_completed' => 'Erledigt', 45 | 'go_back' => '<< Zurück', 46 | 'edit_task' => 'Aufgabe bearbeiten', 47 | 'add_task' => 'Neue Aufgabe', 48 | 'priority' => 'Priorität', 49 | 'task' => 'Aufgabe', 50 | 'note' => 'Notiz', 51 | 'tags' => 'Schlagwörter', 52 | 'tags_descr' => "(prefixes: =state, @user, ~fix, !highlight)", 53 | 'save' => 'Speichern', 54 | 'cancel' => 'Abbrechen', 55 | 'password' => 'Passwort', 56 | 'btn_login' => 'Login', 57 | 'a_login' => 'Login', 58 | 'a_logout' => 'Logout', 59 | 'public_tasks' => 'Öffentliche Aufgabe', 60 | 'tagcloud' => 'Tags', 61 | 'tagfilter_cancel' => 'Filter aufheben', 62 | 'sortByHand' => 'Manuell sortieren', 63 | 'sortByPriority' => 'Nach Priorität sortieren', 64 | 'sortByDueDate' => 'Nach Fälligkeitsdatum sortieren', 65 | 'sortByDateCreated' => 'Nach Erstelldatum sortieren', 66 | 'sortByDateModified' => 'Nach Änderungsdatum sortieren', 67 | 'due' => 'Fällig', 68 | 'daysago' => 'vor %d Tagen', 69 | 'indays' => 'in %d Tagen', 70 | 'months_short' => array('Jan','Feb','Mrz','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'), 71 | 'months_long' => array('Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'), 72 | 'days_min' => array('So','Mo','Di','Mi','Do','Fr','Sa'), 73 | 'days_long' => array('Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'), 74 | 'today' => 'heute', 75 | 'yesterday' => 'gestern', 76 | 'tomorrow' => 'morgen', 77 | 'f_past' => 'Überfällig', 78 | 'f_today' => 'Heute und morgen', 79 | 'f_soon' => 'Bald', 80 | 'action_edit' => 'Bearbeiten', 81 | 'action_note' => 'Notiz bearbeiten', 82 | 'action_delete' => 'Löschen', 83 | 'action_priority' => 'Priorität', 84 | 'action_move' => 'Verschieben nach', 85 | 'notes' => 'Notizen:', 86 | 'notes_show' => 'Anzeigen', 87 | 'notes_hide' => 'Verbergen', 88 | 'list_new' => 'Neue Liste', 89 | 'list_rename' => 'Liste umbenennen', 90 | 'list_delete' => 'Liste löschen', 91 | 'list_publish' => 'Liste veröffentlichen', 92 | 'list_hide' => 'Liste ausblenden', 93 | 'list_showcompleted' => 'Abgeschlossene Aufgaben anzeigen', 94 | 'list_clearcompleted' => 'Abgeschlossene Aufgaben löschen', 95 | 'list_select' => 'Liste auswählen', 96 | 'list_export' => 'Export', 97 | 'list_export_csv' => 'CSV', 98 | 'list_export_ical' => 'iCalendar', 99 | 'list_rssfeed' => 'RSS Feed', 100 | 'alltags' => 'Alle Schlagwörter:', 101 | 'alltags_show' => 'Alle anzeigen', 102 | 'alltags_hide' => 'Alle verbergen', 103 | 'a_settings' => 'Einstellungen', 104 | 'rss_feed' => 'RSS Feed', 105 | 'feed_title' => '%s', 106 | 'feed_completed_tasks' => 'Abgeschlossene Aufgabe', 107 | 'feed_modified_tasks' => 'Geänderte Aufgaben', 108 | 'feed_new_tasks' => 'Neue Aufgaben', 109 | 'alltasks' => 'Alle Aufgaben', 110 | 111 | /* Settings */ 112 | 'set_header' => 'Einstellungen', 113 | 'set_title' => 'Titel', 114 | 'set_title_descr' => '(angeben, um Standardtitel zu ändern)', 115 | 'set_language' => 'Sprache', 116 | 'set_protection' => 'Passwortschutz', 117 | 'set_enabled' => 'Aktiviert', 118 | 'set_disabled' => 'Deaktiviert', 119 | 'set_newpass' => 'Neues Passwort', 120 | 'set_newpass_descr' => '(leer lassen, um aktuelles Passwort nicht zu ändern)', 121 | 'set_smartsyntax' => 'Smartsyntax', 122 | 'set_smartsyntax_descr' => '(/Priorität/ Aufgabe /Schlagwörter/)', 123 | 'set_timezone' => 'Zeitzone', 124 | 'set_autotag' => 'Automatische Schlagwörter', 125 | 'set_autotag_descr' => '(fügt Schlagwort des aktuellen Filters automatisch der neu erstellten Aufgabe hinzu)', 126 | 'set_sessions' => 'Sessionhandling-Mechanismus', 127 | 'set_sessions_php' => 'PHP', 128 | 'set_sessions_files' => 'Dateien', 129 | 'set_firstdayofweek' => 'Erster Tag der Woche', 130 | 'set_custom' => 'benutzerdefiniert', 131 | 'set_date' => 'Datumsformat', 132 | 'set_date2' => 'Kurzes Datumsformat', 133 | 'set_shortdate' => 'Kurzes Datumsformat (aktuelles Jahr)', 134 | 'set_clock' => 'Zeitformat', 135 | 'set_12hour' => '12 Stunden', 136 | 'set_24hour' => '24 Stunden', 137 | 'set_7day' => '1 Woche', 138 | 'set_1month' => '1 Monat', 139 | 'set_1year' => '1 Jahr', 140 | 'set_nobackup' => 'Keine Sicherung', 141 | 'set_always' => 'Unbegrenzt', 142 | 'set_submit' => 'Änderungen speichern', 143 | 'set_cancel' => 'Abbrechen', 144 | 'set_showdate' => 'Aufgabendatum in Liste anzeigen', 145 | 'set_alientags' => 'Schlagwörter von anderen Listen anzeigen', 146 | 'set_dbbackup' => 'Backups aufbewahren für', 147 | 'set_dbbackup_descr' => 'Funktioniert nur mit SQLite!', 148 | ); 149 | } 150 | -------------------------------------------------------------------------------- /lang/en.php: -------------------------------------------------------------------------------- 1 | "Are you sure you want to delete the task?", 17 | 'confirmLeave' => "There can be unsaved data. Do you really want to leave?", 18 | 'actionNoteSave' => "save", 19 | 'actionNoteCancel' => "cancel", 20 | 'error' => "Some error occurred (click for details)", 21 | 'denied' => "Access denied", 22 | 'invalidpass' => "Wrong password", 23 | 'tagfilter' => "Tag:", 24 | 'addList' => "Create new list", 25 | 'renameList' => "Rename list", 26 | 'deleteList' => "This will delete current list with all tasks in it.\\nAre you sure?", 27 | 'clearCompleted' => "This will delete all completed tasks in the list.\\nAre you sure?", 28 | 'settingsSaved' => "Settings saved. Reloading...", 29 | ); 30 | 31 | var $inc = array 32 | ( 33 | 'htab_newtask' => "New task", 34 | 'htab_search' => "Search", 35 | 'btn_add' => "Add", 36 | 'btn_search' => "Search", 37 | 'advanced_add' => "Advanced", 38 | 'searching' => "Searching for", 39 | 'tasks' => "Tasks", 40 | 'taskdate_inline_created' => "Created %s", 41 | 'taskdate_inline_completed' => "Completed %s", 42 | 'taskdate_inline_duedate' => "Due %s", 43 | 'taskdate_created' => "Created", 44 | 'taskdate_completed' => "Completed", 45 | 'go_back' => "<< Back", 46 | 'edit_task' => "Edit Task", 47 | 'add_task' => "New Task", 48 | 'priority' => "Priority", 49 | 'task' => "Task", 50 | 'note' => "Note", 51 | 'tags' => "Tags", 52 | 'tags_descr' => "(prefixes: =state, @user, ~fix, !highlight)", 53 | 'save' => "Save", 54 | 'cancel' => "Cancel", 55 | 'password' => "Password", 56 | 'btn_login' => "Login", 57 | 'a_login' => "Login", 58 | 'a_logout' => "Logout", 59 | 'public_tasks' => "Public Tasks", 60 | 'tagcloud' => "Tags", 61 | 'tagfilter_cancel' => "cancel filter", 62 | 'sortByHand' => "Sort by hand", 63 | 'sortByPriority' => "Sort by priority", 64 | 'sortByDueDate' => "Sort by due date", 65 | 'sortByDateCreated' => "Sort by date created", 66 | 'sortByDateModified' => "Sort by date modified", 67 | 'due' => "Due", 68 | 'daysago' => "%d days ago", 69 | 'indays' => "in %d days", 70 | 'months_short' => array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"), 71 | 'months_long' => array("January","February","March","April","May","June","July","August","September","October","November","December"), 72 | 'days_min' => array("Su","Mo","Tu","We","Th","Fr","Sa"), 73 | 'days_long' => array("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"), 74 | 'today' => "today", 75 | 'yesterday' => "yesterday", 76 | 'tomorrow' => "tomorrow", 77 | 'f_past' => "Overdue", 78 | 'f_today' => "Today and tomorrow", 79 | 'f_soon' => "Soon", 80 | 'action_edit' => "Edit", 81 | 'action_note' => "Edit Note", 82 | 'action_delete' => "Delete", 83 | 'action_priority' => "Priority", 84 | 'action_move' => "Move to", 85 | 'notes' => "Notes:", 86 | 'notes_show' => "Show", 87 | 'notes_hide' => "Hide", 88 | 'list_new' => "New list", 89 | 'list_rename' => "Rename list", 90 | 'list_delete' => "Delete list", 91 | 'list_publish' => "Publish list", 92 | 'list_hide' => "Hide list", 93 | 'list_showcompleted' => "Show completed tasks", 94 | 'list_clearcompleted' => "Clear completed tasks", 95 | 'list_select' => "Select list", 96 | 'list_export' => "Export", 97 | 'list_export_csv' => "CSV", 98 | 'list_export_ical' => "iCalendar", 99 | 'list_rssfeed' => "RSS Feed", 100 | 'alltags' => "All tags:", 101 | 'alltags_show' => "Show all", 102 | 'alltags_hide' => "Hide all", 103 | 'a_settings' => "Settings", 104 | 'rss_feed' => "RSS Feed", 105 | 'feed_title' => "%s", 106 | 'feed_completed_tasks' => "Completed tasks", 107 | 'feed_modified_tasks' => "Modified tasks", 108 | 'feed_new_tasks' => "New tasks", 109 | 'alltasks' => "All tasks", 110 | 111 | /* Settings */ 112 | 'set_header' => "Settings", 113 | 'set_title' => "Title", 114 | 'set_title_descr' => "(specify if you want to change default title)", 115 | 'set_language' => "Language", 116 | 'set_protection' => "Password protection", 117 | 'set_enabled' => "Enabled", 118 | 'set_disabled' => "Disabled", 119 | 'set_newpass' => "New password", 120 | 'set_newpass_descr' => "(leave blank to keep current password, separate multiple passwords with blanks)", 121 | 'set_smartsyntax' => "Smart syntax", 122 | 'set_smartsyntax_descr' => "(/priority/ task /tags/)", 123 | 'set_markdown' => "Use markdown in notes", 124 | 'set_timezone' => "Time zone", 125 | 'set_autotag' => "Autotagging", 126 | 'set_autotag_descr' => "(automatically adds tag of current tag filter to newly created task)", 127 | 'set_sessions' => "Session handling mechanism", 128 | 'set_sessions_php' => "PHP", 129 | 'set_sessions_files' => "Files", 130 | 'set_firstdayofweek' => "First day of week", 131 | 'set_custom' => "Custom", 132 | 'set_date' => "Date format", 133 | 'set_date2' => "Short Date format", 134 | 'set_shortdate' => "Short Date (current year)", 135 | 'set_clock' => "Clock format", 136 | 'set_12hour' => "12-hour", 137 | 'set_24hour' => "24-hour", 138 | 'set_7day' => "One week", 139 | 'set_1month' => "One month", 140 | 'set_1year' => "One year", 141 | 'set_always' => "Indefinitely", 142 | 'set_nobackup' => "no backup", 143 | 'set_submit' => "Submit changes", 144 | 'set_cancel' => "Cancel", 145 | 'set_showdate' => "Show task date in list", 146 | 'set_alientags' => "Show tags from other lists", 147 | 'set_taskxrefs' => "Show task identifiers", 148 | 'set_dbbackup' => "Keep backups for", 149 | 'set_dbbackup_descr' => "This works only with sqlite databases", 150 | ); 151 | } 152 | -------------------------------------------------------------------------------- /lang/ru.php: -------------------------------------------------------------------------------- 1 | 'Вы действительно хотите удалить задачу?', 17 | 'confirmLeave' => 'На странице могут быть несохраненные данные. Вы действительно хотите закрыть страницу?', 18 | 'actionNoteSave' => 'сохранить', 19 | 'actionNoteCancel' => 'отмена', 20 | 'error' => 'Ошибка', 21 | 'denied' => 'Доступ запрещен', 22 | 'invalidpass' => 'Неверный пароль', 23 | 'tagfilter' => 'Тег:', 24 | 'addList' => 'Новый список', 25 | 'renameList' => 'Переименовать список', 26 | 'deleteList' => 'Вы действительно хотите удалить этот список со всеми задачами?', 27 | 'clearCompleted' => 'Удалить все выполненные задачи из списка?', 28 | 'settingsSaved' => 'Настройки сохранены. Перезагрузка...', 29 | ); 30 | 31 | var $inc = array 32 | ( 33 | 'htab_newtask' => 'Новая задача', 34 | 'htab_search' => 'Поиск', 35 | 'btn_add' => 'Добавить', 36 | 'btn_search' => 'Искать', 37 | 'advanced_add' => 'Расширенная форма', 38 | 'searching' => 'Поиск', 39 | 'tasks' => 'Задачи', 40 | 'taskdate_inline_created' => 'добавлена %s', 41 | 'taskdate_inline_completed' => 'Завершена %s', 42 | 'taskdate_inline_duedate' => 'В срок %s', 43 | 'taskdate_created' => 'Дата создания', 44 | 'taskdate_completed' => 'Дата завершения', 45 | 'go_back' => '<< Назад', 46 | 'edit_task' => 'Редактирование задачи', 47 | 'add_task' => 'Новая задача', 48 | 'priority' => 'Приоритет', 49 | 'task' => 'Задача', 50 | 'note' => 'Заметка', 51 | 'tags' => 'Теги', 52 | 'save' => 'Сохранить', 53 | 'cancel' => 'Отмена', 54 | 'password' => 'Пароль', 55 | 'btn_login' => 'Войти', 56 | 'a_login' => 'Вход', 57 | 'a_logout' => 'Выйти', 58 | 'public_tasks' => 'Опубликованные задачи', 59 | 'tagcloud' => 'Теги', 60 | 'tagfilter_cancel' => 'отменить фильтр по тегу', 61 | 'sortByHand' => 'Сортировка вручную', 62 | 'sortByPriority' => 'Сортировка по приоритету', 63 | 'sortByDueDate' => 'Сортировка по сроку', 64 | 'sortByDateCreated' => 'Сортировка по дате добавления', 65 | 'sortByDateModified' => 'Сортировка по дате изменения', 66 | 'due' => 'Срок', 67 | 'daysago' => '%d дн. назад', 68 | 'indays' => 'через %d дн.', 69 | 'months_short' => array('Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'), 70 | 'months_long' => array('Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'), 71 | 'days_min' => array('Вс','Пн','Вт','Ср','Чт','Пт','Сб'), 72 | 'days_long' => array('Воскресенье','Понедельник','Вторник','Среда','Четверг','Пятница','Суббота'), 73 | 'today' => 'сегодня', 74 | 'yesterday' => 'вчера', 75 | 'tomorrow' => 'завтра', 76 | 'f_past' => 'Просроченные', 77 | 'f_today' => 'Сегодня и завтра', 78 | 'f_soon' => 'Скоро', 79 | 'action_edit' => 'Редактировать', 80 | 'action_note' => 'Заметка', 81 | 'action_delete' => 'Удалить', 82 | 'action_priority' => 'Приоритет', 83 | 'action_move' => 'Переместить в', 84 | 'notes' => 'Заметки:', 85 | 'notes_show' => 'Показать', 86 | 'notes_hide' => 'Скрыть', 87 | 'list_new' => 'Новый список', 88 | 'list_rename' => 'Переименовать список', 89 | 'list_delete' => 'Удалить список', 90 | 'list_publish' => 'Опубликовать список', 91 | 'list_showcompleted' => 'Показать завершенные задачи', 92 | 'list_clearcompleted' => 'Удалить завершенные задачи', 93 | 'list_select' => 'Выбрать список', 94 | 'list_export' => 'Экспортировать', 95 | 'list_export_csv' => 'CSV', 96 | 'list_export_ical' => 'iCalendar', 97 | 'list_rssfeed' => 'RSS-лента', 98 | 'alltags' => 'Все теги:', 99 | 'alltags_show' => 'Показать все', 100 | 'alltags_hide' => 'Скрыть все', 101 | 'a_settings' => 'Настройки', 102 | 'rss_feed' => 'RSS-лента', 103 | 'feed_title' => '%s', 104 | 'feed_completed_tasks' => 'Завершенные задачи', 105 | 'feed_modified_tasks' => 'Изменившиеся задачи', 106 | 'feed_new_tasks' => 'Новые задачи', 107 | 'alltasks' => 'Все задачи', 108 | 'set_header' => 'Настройки', 109 | 'set_title' => 'Заголовок страницы', 110 | 'set_title_descr' => '(если поле не заполнено, будет использован заголовок по-умолчанию)', 111 | 'set_language' => 'Язык (Language)', 112 | 'set_protection' => 'Парольная защита', 113 | 'set_enabled' => 'Включено', 114 | 'set_disabled' => 'Выключено', 115 | 'set_newpass' => 'Новый пароль', 116 | 'set_newpass_descr' => '(не заполняйте поле если не хотите менять текущий пароль)', 117 | 'set_smartsyntax' => 'Smart syntax', 118 | 'set_smartsyntax_descr' => '(возможность использовать синтаксис: /приоритет/ задача /теги/)', 119 | 'set_timezone' => 'Часовой пояс', 120 | 'set_autotag' => 'Autotagging', 121 | 'set_autotag_descr' => '(автодобавление текущего тега из фильтра в новую задачу)', 122 | 'set_sessions' => 'Хранилище сессий', 123 | 'set_sessions_php' => 'PHP', 124 | 'set_sessions_files' => 'Файлы', 125 | 'set_firstdayofweek' => 'Первый день недели', 126 | 'set_custom' => 'другой', 127 | 'set_date' => 'Формат даты', 128 | 'set_date2' => 'Формат короткой даты', 129 | 'set_shortdate' => 'Короткая дата (в текущем году)', 130 | 'set_clock' => 'Формат часов', 131 | 'set_12hour' => '12-часовой', 132 | 'set_24hour' => '24-часовой', 133 | 'set_submit' => 'Сохранить изменения', 134 | 'set_cancel' => 'Отмена', 135 | 'set_showdate' => 'Показывать дату создания задачи', 136 | ); 137 | } 138 | -------------------------------------------------------------------------------- /mytinytodo_ajax_storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | (C) Copyright 2009-2010 myTinyTodo by Max Pozdeev 3 | (C) Copyright 2017 fork myTDX by Jérémie FRANCOIS 4 | Licensed under the GNU GPL v2 license. See file COPYRIGHT for details. 5 | */ 6 | 7 | // AJAX myTinyTodo Storage 8 | 9 | (function(){ 10 | 11 | var mtt; 12 | 13 | function mytinytodoStorageAjax(amtt) 14 | { 15 | this.mtt = mtt = amtt; 16 | } 17 | 18 | window.mytinytodoStorageAjax = mytinytodoStorageAjax; 19 | 20 | mytinytodoStorageAjax.prototype = 21 | { 22 | /* required method */ 23 | request:function(action, params, callback) 24 | { 25 | if(!this[action]) throw "Unknown storage action: "+action; 26 | 27 | this[action](params, function(json){ 28 | if(json.denied) mtt.errorDenied(); 29 | if(callback) callback.call(mtt, json) 30 | }); 31 | }, 32 | 33 | 34 | loadLists: function(params, callback) 35 | { 36 | $.getJSON(this.mtt.mttUrl+'ajax.php?loadLists'+'&rnd='+Math.random(), callback); 37 | }, 38 | 39 | 40 | loadTasks: function(params, callback) 41 | { 42 | var q = ''; 43 | if(params.search && params.search != '') q += '&s='+encodeURIComponent(params.search); 44 | if(params.tag && params.tag != '') q += '&t='+encodeURIComponent(params.tag); 45 | if(params.setCompl && params.setCompl != 0) q += '&setCompl=1'; 46 | q += '&rnd='+Math.random(); 47 | 48 | /* $.getJSON(mtt.mttUrl+'ajax.php?loadTasks&list='+params.list+'&compl='+params.compl+'&sort='+params.sort+'&tz='+params.tz+q, function(json){ 49 | callback.call(mtt, json); 50 | }) 51 | */ 52 | 53 | $.getJSON(this.mtt.mttUrl+'ajax.php?loadTasks&list='+params.list+'&compl='+params.compl+'&sort='+params.sort+q, callback); 54 | }, 55 | 56 | 57 | newTask: function(params, callback) 58 | { 59 | $.post(this.mtt.mttUrl+'ajax.php?newTask', 60 | { list:params.list, title: params.title, tag:params.tag }, callback, 'json'); 61 | }, 62 | 63 | 64 | fullNewTask: function(params, callback) 65 | { 66 | $.post(this.mtt.mttUrl+'ajax.php?fullNewTask', 67 | { list:params.list, title:params.title, note:params.note, prio:params.prio, tags:params.tags, duedate:params.duedate }, 68 | callback, 'json'); 69 | }, 70 | 71 | 72 | editTask: function(params, callback) 73 | { 74 | $.post(this.mtt.mttUrl+'ajax.php?editTask='+params.id, 75 | { id:params.id, title:params.title, note:params.note, prio:params.prio, tags:params.tags, duedate:params.duedate }, 76 | callback, 'json'); 77 | }, 78 | 79 | 80 | editNote: function(params, callback) 81 | { 82 | $.post(this.mtt.mttUrl+'ajax.php?editNote='+params.id, {id:params.id, note: params.note}, callback, 'json'); 83 | }, 84 | 85 | 86 | completeTask: function(params, callback) 87 | { 88 | $.post(this.mtt.mttUrl+'ajax.php?completeTask='+params.id, { id:params.id, compl:params.compl }, callback, 'json'); 89 | }, 90 | 91 | 92 | deleteTask: function(params, callback) 93 | { 94 | $.post(this.mtt.mttUrl+'ajax.php?deleteTask='+params.id, { id:params.id }, callback, 'json'); 95 | }, 96 | 97 | 98 | setPrio: function(params, callback) 99 | { 100 | $.getJSON(this.mtt.mttUrl+'ajax.php?setPrio='+params.id+'&prio='+params.prio+'&rnd='+Math.random(), callback); 101 | }, 102 | 103 | 104 | setSort: function(params, callback) 105 | { 106 | $.post(this.mtt.mttUrl+'ajax.php?setSort', { list:params.list, sort:params.sort }, callback, 'json'); 107 | }, 108 | 109 | changeOrder: function(params, callback) 110 | { 111 | var order = ''; 112 | for(var i in params.order) { 113 | order += params.order[i].id +'='+ params.order[i].diff + '&'; 114 | } 115 | $.post(this.mtt.mttUrl+'ajax.php?changeOrder', { order:order }, callback, 'json'); 116 | }, 117 | 118 | tagCloud: function(params, callback) 119 | { 120 | $.getJSON(this.mtt.mttUrl+'ajax.php?tagCloud&list='+params.list+'&rnd='+Math.random(), callback); 121 | }, 122 | 123 | moveTask: function(params, callback) 124 | { 125 | $.post(this.mtt.mttUrl+'ajax.php?moveTask', { id:params.id, from:params.from, to:params.to }, callback, 'json'); 126 | }, 127 | 128 | parseTaskStr: function(params, callback) 129 | { 130 | $.post(this.mtt.mttUrl+'ajax.php?parseTaskStr', { list:params.list, title:params.title, tag:params.tag }, callback, 'json'); 131 | }, 132 | 133 | 134 | // Lists 135 | addList: function(params, callback) 136 | { 137 | $.post(this.mtt.mttUrl+'ajax.php?addList', { name:params.name }, callback, 'json'); 138 | 139 | }, 140 | 141 | renameList: function(params, callback) 142 | { 143 | $.post(this.mtt.mttUrl+'ajax.php?renameList', { list:params.list, name:params.name }, callback, 'json'); 144 | }, 145 | 146 | deleteList: function(params, callback) 147 | { 148 | $.post(this.mtt.mttUrl+'ajax.php?deleteList', { list:params.list }, callback, 'json'); 149 | }, 150 | 151 | publishList: function(params, callback) 152 | { 153 | $.post(this.mtt.mttUrl+'ajax.php?publishList', { list:params.list, publish:params.publish }, callback, 'json'); 154 | }, 155 | 156 | setShowNotesInList: function(params, callback) 157 | { 158 | $.post(this.mtt.mttUrl+'ajax.php?setShowNotesInList', { list:params.list, shownotes:params.shownotes }, callback, 'json'); 159 | }, 160 | 161 | setHideList: function(params, callback) 162 | { 163 | $.post(this.mtt.mttUrl+'ajax.php?setHideList', { list:params.list, hide:params.hide }, callback, 'json'); 164 | }, 165 | 166 | changeListOrder: function(params, callback) 167 | { 168 | $.post(this.mtt.mttUrl+'ajax.php?changeListOrder', { order:params.order }, callback, 'json'); 169 | }, 170 | 171 | clearCompletedInList: function(params, callback) 172 | { 173 | $.post(this.mtt.mttUrl+'ajax.php?clearCompletedInList', { list:params.list }, callback, 'json'); 174 | } 175 | 176 | }; 177 | 178 | })(); -------------------------------------------------------------------------------- /mytinytodo_lang.php: -------------------------------------------------------------------------------- 1 | makeJS() .");"; 11 | 12 | ?> -------------------------------------------------------------------------------- /settings.php: -------------------------------------------------------------------------------- 1 | 5 | (C) Copyright 2017 fork myTDX by Jérémie FRANCOIS 6 | Licensed under the GNU GPL v2 license. See file COPYRIGHT for details. 7 | */ 8 | 9 | require_once('./init.php'); 10 | 11 | $lang = Lang::instance(); 12 | 13 | if($needAuth && !is_logged()) 14 | { 15 | die("Access denied!
      Disable password protection or Log in."); 16 | } 17 | 18 | if(isset($_POST['save'])) 19 | { 20 | $t = array(); 21 | $langs = getLangs(); 22 | Config::$params['lang']['options'] = array_keys($langs); 23 | Config::set('lang', _post('lang')); 24 | 25 | // in Demo mode we can set only language by cookies 26 | if(defined('MTTDEMO')) { 27 | setcookie('lang', Config::get('lang'), 0, url_dir(Config::get('url')=='' ? $_SERVER['REQUEST_URI'] : Config::get('url'))); 28 | $t['saved'] = 1; 29 | jsonExit($t); 30 | } 31 | 32 | if(isset($_POST['password']) && $_POST['password'] != '') Config::set('password', $_POST['password']); 33 | elseif(!_post('allowpassword')) Config::set('password', ''); 34 | Config::set('smartsyntax', (int)_post('smartsyntax')); 35 | Config::set('markdown', (int)_post('markdown')); 36 | // Do not set invalid timezone 37 | try { 38 | $tz = trim(_post('timezone')); 39 | $testTZ = new DateTimeZone($tz); //will throw Exception on invalid timezone 40 | Config::set('timezone', $tz); 41 | } 42 | catch (Exception $e) { 43 | } 44 | Config::set('autotag', (int)_post('autotag')); 45 | Config::set('session', _post('session')); 46 | Config::set('firstdayofweek', (int)_post('firstdayofweek')); 47 | Config::set('clock', (int)_post('clock')); 48 | Config::set('dateformat', _post('dateformat')); 49 | Config::set('dateformat2', _post('dateformat2')); 50 | Config::set('dateformatshort', _post('dateformatshort')); 51 | Config::set('title', trim(_post('title'))); 52 | Config::set('showdate', (int)_post('showdate')); 53 | Config::set('alientags', (int)_post('alientags')); 54 | Config::set('taskxrefs', (int)_post('taskxrefs')); 55 | Config::set('dbbackup', (int)_post('dbbackup')); 56 | Config::save(); 57 | $t['saved'] = 1; 58 | jsonExit($t); 59 | } 60 | 61 | 62 | function _c($key) 63 | { 64 | return Config::get($key); 65 | } 66 | 67 | function getLangs($withContents = 0) 68 | { 69 | if (!$h = opendir(MTTPATH. 'lang')) return false; 70 | $a = array(); 71 | while(false !== ($file = readdir($h))) 72 | { 73 | if(preg_match('/(.+)\.php$/', $file, $m) && $file != 'class.default.php') { 74 | $a[$m[1]] = $m[1]; 75 | if($withContents) { 76 | $a[$m[1]] = getLangDetails(MTTPATH. 'lang'. DIRECTORY_SEPARATOR. $file, $m[1]); 77 | $a[$m[1]]['name'] = $a[$m[1]]['original_name']; 78 | $a[$m[1]]['title'] = $a[$m[1]]['language']; 79 | } 80 | } 81 | } 82 | closedir($h); 83 | return $a; 84 | } 85 | 86 | function getLangDetails($filename, $default) 87 | { 88 | $contents = file_get_contents($filename); 89 | $a = array('language'=>$default, 'original_name'=>$default); 90 | if(preg_match("|/\\*\s*myTinyTodo language pack([\s\S]+?)\\*/|", $contents, $m)) 91 | { 92 | $str = $m[1]; 93 | if(preg_match("|Language\s*:\s*(.+)|i", $str, $m)) { 94 | $a['language'] = trim($m[1]); 95 | } 96 | if(preg_match("|Original name\s*:\s*(.+)|i", $str, $m)) { 97 | $a['original_name'] = trim($m[1]); 98 | } 99 | } 100 | return $a; 101 | } 102 | 103 | function selectOptions($a, $value, $default=null) 104 | { 105 | if(!$a) return ''; 106 | $s = ''; 107 | if($default !== null && !isset($a[$value])) $value = $default; 108 | foreach($a as $k=>$v) { 109 | $s .= ''; 110 | } 111 | return $s; 112 | } 113 | 114 | /* 115 | @param array $a array of id=>array(name, optional title) 116 | @param mixed $key Key of OPTION to be selected 117 | @param mixed $default Default key if $key is not present in $a 118 | */ 119 | function selectOptionsA($a, $key, $default=null) 120 | { 121 | if(!$a) return ''; 122 | $s = ''; 123 | if($default !== null && !isset($a[$key])) $key = $default; 124 | foreach($a as $k=>$v) { 125 | $s .= ''; 128 | } 129 | return $s; 130 | } 131 | 132 | function timezoneIdentifiers() 133 | { 134 | $zones = DateTimeZone::listIdentifiers(); 135 | $a = array(); 136 | foreach($zones as $v) { 137 | $a[$v] = $v; 138 | } 139 | return $a; 140 | } 141 | 142 | header('Content-type:text/html; charset=utf-8'); 143 | ?> 144 | 145 |
      146 | 147 |

      148 | 149 | 150 | 151 |
      152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 183 | 184 | 185 | 186 | 190 | 191 | 192 | 193 | 197 | 198 | 199 | 200 | 204 | 205 | 206 | 207 | 210 | 211 | 212 | 213 | 216 | 217 | 218 | 219 | 226 | 227 | 228 | 229 | 238 | 239 | 240 | 241 | 247 | 248 | 249 | 250 | 253 | 254 | 255 | 256 | 260 | 261 | 262 | 263 | 264 | 268 | 269 | 270 | 271 | 272 | 276 | 277 | 278 | 279 | 280 | 290 | 291 | 297 |
      :
      :
      : 168 |
      169 |
      170 |
      :
      />
      :
      180 |
      181 | 182 |
      :
      187 |
      188 | 189 |
      :
      194 |
      195 | 196 |
      : 201 |
      202 | (<mytinytodo_dir>/tmp/sessions) 203 |
      : 208 | 209 |
      : 214 | 215 |
      : 220 | 221 | 225 |
      : 230 | 231 | 237 |
      : 242 | 243 | 246 |
      : 251 | 252 |
      : 257 |
      258 | 259 |
      : 265 |
      266 | 267 |
      : 273 |
      274 | 275 |
      :
      281 | 289 |
      292 | 293 | 294 | 295 | 296 |
      298 | 299 |
      300 | -------------------------------------------------------------------------------- /setup.php: -------------------------------------------------------------------------------- 1 | 5 | (C) Copyright 2017 fork myTDX by Jérémie FRANCOIS 6 | Licensed under the GNU GPL v2 license. See file COPYRIGHT for details. 7 | */ 8 | 9 | set_exception_handler('myExceptionHandler'); 10 | 11 | if(!is_file('./db/config.php')) 12 | copy('db_sample/config.php','db/config.php') or die ('Could not write to '.getcwd(). 13 | '/db/
      Please create the directory, and make sure the web server can write to it.'); 14 | 15 | # Check old config file (prior v1.3) 16 | require_once('./db/config.php'); 17 | if(!isset($config['db'])) 18 | { 19 | if(isset($config['mysql'])) { 20 | $config['db'] = 'mysql'; 21 | $config['mysql.host'] = $config['mysql'][0]; 22 | $config['mysql.db'] = $config['mysql'][3]; 23 | $config['mysql.user'] = $config['mysql'][1]; 24 | $config['mysql.password'] = $config['mysql'][2]; 25 | } else { 26 | $config['db'] = 'sqlite'; 27 | } 28 | if(isset($config['allow']) && $config['allow'] == 'read') $config['allowread'] = 1; 29 | } 30 | 31 | if($config['db'] != '') 32 | { 33 | require_once('./init.php'); 34 | if($needAuth && !is_logged()) 35 | { 36 | die("Access denied!
      Disable password protection or Log in."); 37 | } 38 | $dbtype = (strtolower(get_class($db)) == 'database_mysql') ? 'mysql' : 'sqlite'; 39 | } 40 | else 41 | { 42 | if(!defined('MTTPATH')) define('MTTPATH', dirname(__FILE__) .'/'); 43 | require_once(MTTPATH. 'common.php'); 44 | Config::loadConfig($config); 45 | unset($config); 46 | 47 | $db = 0; 48 | $dbtype = ''; 49 | } 50 | 51 | $lastDatabaseVersion = '1.4'; // the last *database* schema revision, hence rarely what get_version() returns! 52 | echo 'myTDX '.get_version().' Setup'; 53 | echo '

      myTDX '.get_version().' setup



      '; 54 | 55 | # determine current installed version 56 | $this_db_ver = get_db_ver($db, $dbtype); 57 | if(!$this_db_ver) 58 | { 59 | # Which DB to select 60 | if(!isset($_POST['installdb']) && !isset($_POST['install'])) 61 | { 62 | exitMessage("
      Select database type to use:

      63 |

      64 |
      65 |
      "); 71 | } 72 | elseif(isset($_POST['installdb'])) 73 | { 74 | # Save configuration 75 | $dbtype = ($_POST['installdb'] == 'mysql') ? 'mysql' : 'sqlite'; 76 | Config::set('db', $dbtype); 77 | if($dbtype == 'mysql') { 78 | Config::set('mysql.host', _post('mysql_host')); 79 | Config::set('mysql.db', _post('mysql_db')); 80 | Config::set('mysql.user', _post('mysql_user')); 81 | Config::set('mysql.password', _post('mysql_password')); 82 | Config::set('prefix', trim(_post('prefix'))); 83 | } 84 | if(!testConnect($error)) { 85 | exitMessage("Database connection error: $error"); 86 | } 87 | if(!is_writable('./db/config.php')) { 88 | exitMessage("Config file ('db/config.php') is not writable."); 89 | } 90 | Config::save(); 91 | exitMessage("This will create myTinyTodo database
      "); 92 | } 93 | 94 | # install database 95 | if($dbtype == 'mysql') 96 | { 97 | try 98 | { 99 | 100 | $db->ex( 101 | "CREATE TABLE {$db->prefix}lists ( 102 | `id` INT UNSIGNED NOT NULL auto_increment, 103 | `uuid` CHAR(36) NOT NULL default '', 104 | `ow` INT NOT NULL default 0, 105 | `name` VARCHAR(50) NOT NULL default '', 106 | `d_created` INT UNSIGNED NOT NULL default 0, 107 | `d_edited` INT UNSIGNED NOT NULL default 0, 108 | `sorting` TINYINT UNSIGNED NOT NULL default 0, 109 | `published` TINYINT UNSIGNED NOT NULL default 0, 110 | `taskview` INT UNSIGNED NOT NULL default 0, 111 | PRIMARY KEY(`id`), 112 | UNIQUE KEY(`uuid`) 113 | ) CHARSET=utf8 "); 114 | 115 | 116 | $db->ex( 117 | "CREATE TABLE {$db->prefix}todolist ( 118 | `id` INT UNSIGNED NOT NULL auto_increment, 119 | `uuid` CHAR(36) NOT NULL default '', 120 | `list_id` INT UNSIGNED NOT NULL default 0, 121 | `d_created` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ 122 | `d_completed` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ 123 | `d_edited` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ 124 | `compl` TINYINT UNSIGNED NOT NULL default 0, 125 | `title` VARCHAR(250) NOT NULL, 126 | `note` TEXT, 127 | `prio` TINYINT NOT NULL default 0, /* priority -,0,+ */ 128 | `ow` INT NOT NULL default 0, /* order weight */ 129 | `tags` VARCHAR(600) NOT NULL default '', /* for fast access to task tags */ 130 | `tags_ids` VARCHAR(250) NOT NULL default '', /* no more than 22 tags (x11 chars) */ 131 | `duedate` DATE default NULL, 132 | PRIMARY KEY(`id`), 133 | KEY(`list_id`), 134 | UNIQUE KEY(`uuid`) 135 | ) CHARSET=utf8 "); 136 | 137 | 138 | $db->ex( 139 | "CREATE TABLE {$db->prefix}tags ( 140 | `id` INT UNSIGNED NOT NULL auto_increment, 141 | `name` VARCHAR(50) NOT NULL, 142 | PRIMARY KEY(`id`), 143 | UNIQUE KEY `name` (`name`) 144 | ) CHARSET=utf8 "); 145 | 146 | 147 | $db->ex( 148 | "CREATE TABLE {$db->prefix}tag2task ( 149 | `tag_id` INT UNSIGNED NOT NULL, 150 | `task_id` INT UNSIGNED NOT NULL, 151 | `list_id` INT UNSIGNED NOT NULL, 152 | KEY(`tag_id`), 153 | KEY(`task_id`), 154 | KEY(`list_id`) /* for tagcloud */ 155 | ) CHARSET=utf8 "); 156 | 157 | 158 | } catch (Exception $e) { 159 | exitMessage("Error: ". htmlarray($e->getMessage())); 160 | } 161 | } 162 | else #sqlite 163 | { 164 | try 165 | { 166 | 167 | $db->ex( 168 | "CREATE TABLE {$db->prefix}lists ( 169 | id INTEGER PRIMARY KEY, 170 | uuid CHAR(36) NOT NULL, 171 | ow INTEGER NOT NULL default 0, 172 | name VARCHAR(50) NOT NULL, 173 | d_created INTEGER UNSIGNED NOT NULL default 0, 174 | d_edited INTEGER UNSIGNED NOT NULL default 0, 175 | sorting TINYINT UNSIGNED NOT NULL default 0, 176 | published TINYINT UNSIGNED NOT NULL default 0, 177 | taskview INTEGER UNSIGNED NOT NULL default 0 178 | ) "); 179 | 180 | $db->ex("CREATE UNIQUE INDEX lists_uuid ON {$db->prefix}lists (uuid)"); 181 | 182 | $db->ex( 183 | "CREATE TABLE {$db->prefix}todolist ( 184 | id INTEGER PRIMARY KEY, 185 | uuid CHAR(36) NOT NULL, 186 | list_id INTEGER UNSIGNED NOT NULL default 0, 187 | d_created INTEGER UNSIGNED NOT NULL default 0, 188 | d_completed INTEGER UNSIGNED NOT NULL default 0, 189 | d_edited INTEGER UNSIGNED NOT NULL default 0, 190 | compl TINYINT UNSIGNED NOT NULL default 0, 191 | title VARCHAR(250) NOT NULL, 192 | note TEXT, 193 | prio TINYINT NOT NULL default 0, 194 | ow INTEGER NOT NULL default 0, 195 | tags VARCHAR(600) NOT NULL default '', 196 | tags_ids VARCHAR(250) NOT NULL default '', 197 | duedate DATE default NULL 198 | ) "); 199 | $db->ex("CREATE INDEX todo_list_id ON {$db->prefix}todolist (list_id)"); 200 | $db->ex("CREATE UNIQUE INDEX todo_uuid ON {$db->prefix}todolist (uuid)"); 201 | 202 | 203 | $db->ex( 204 | "CREATE TABLE {$db->prefix}tags ( 205 | id INTEGER PRIMARY KEY AUTOINCREMENT, 206 | name VARCHAR(50) NOT NULL COLLATE NOCASE 207 | ) "); 208 | $db->ex("CREATE UNIQUE INDEX tags_name ON {$db->prefix}tags (name COLLATE NOCASE)"); 209 | 210 | 211 | $db->ex( 212 | "CREATE TABLE {$db->prefix}tag2task ( 213 | tag_id INTEGER NOT NULL, 214 | task_id INTEGER NOT NULL, 215 | list_id INTEGER NOT NULL 216 | ) "); 217 | $db->ex("CREATE INDEX tag2task_tag_id ON {$db->prefix}tag2task (tag_id)"); 218 | $db->ex("CREATE INDEX tag2task_task_id ON {$db->prefix}tag2task (task_id)"); 219 | $db->ex("CREATE INDEX tag2task_list_id ON {$db->prefix}tag2task (list_id)"); /* for tagcloud */ 220 | 221 | } catch (Exception $e) { 222 | exitMessage("Error: ". htmlarray($e->getMessage())); 223 | } 224 | } 225 | 226 | # create default list 227 | $db->ex("INSERT INTO {$db->prefix}lists (uuid,name,d_created) VALUES (?,?,?)", array(generateUUID(), 'Todo', time())); 228 | 229 | } 230 | elseif($this_db_ver == $lastDatabaseVersion) 231 | { 232 | exitMessage("Installed version does not require database update.". 233 | '

      Go to the index.'); 234 | } 235 | else 236 | { 237 | if(!in_array($this_db_ver, array('1.1','1.2','1.3.0','1.3.1'))) { 238 | exitMessage("Can not update. Unsupported database version ($this_db_ver)."); 239 | } 240 | if(!isset($_POST['update'])) { 241 | exitMessage("Update database v$this_db_ver 242 |
      243 | 244 | "); 245 | } 246 | 247 | # update process 248 | if($this_db_ver == '1.3.1') 249 | { 250 | update_131_14($db, $dbtype); 251 | } 252 | if($this_db_ver == '1.3.0') 253 | { 254 | update_130_131($db, $dbtype); 255 | update_131_14($db, $dbtype); 256 | } 257 | if($this_db_ver == '1.2') 258 | { 259 | update_12_13($db, $dbtype); 260 | update_130_131($db, $dbtype); 261 | update_131_14($db, $dbtype); 262 | } 263 | elseif($this_db_ver == '1.1') 264 | { 265 | update_11_12($db, $dbtype); 266 | update_12_13($db, $dbtype); 267 | update_130_131($db, $dbtype); 268 | update_131_14($db, $dbtype); 269 | } 270 | } 271 | echo "Done

      Attention! Delete this file for security reasons."; 272 | echo '

      Go to the index.'; 273 | printFooter(); 274 | 275 | 276 | function get_db_ver($db, $dbtype) 277 | { 278 | if(!$db || $dbtype == '') return ''; 279 | if(!$db->table_exists($db->prefix.'todolist')) return ''; 280 | $v = '1.0'; 281 | if(!$db->table_exists($db->prefix.'tags')) return $v; 282 | $v = '1.1'; 283 | if($dbtype == 'mysql') { 284 | if(!has_field_mysql($db, $db->prefix.'todolist', 'duedate')) return $v; 285 | } else { 286 | if(!has_field_sqlite($db, $db->prefix.'todolist', 'duedate')) return $v; 287 | } 288 | $v = '1.2'; 289 | if(!$db->table_exists($db->prefix.'lists')) return $v; 290 | $v = '1.3.0'; 291 | if($dbtype == 'mysql') { 292 | if(!has_field_mysql($db, $db->prefix.'todolist', 'd_completed')) return $v; 293 | } else { 294 | if(!has_field_sqlite($db, $db->prefix.'todolist', 'd_completed')) return $v; 295 | } 296 | $v = '1.3.1'; 297 | if($dbtype == 'mysql') { 298 | if(!has_field_mysql($db, $db->prefix.'todolist', 'd_edited')) return $v; 299 | } else { 300 | if(!has_field_sqlite($db, $db->prefix.'todolist', 'd_edited')) return $v; 301 | } 302 | $v = '1.4'; 303 | return $v; 304 | } 305 | 306 | function exitMessage($s) 307 | { 308 | echo $s; 309 | printFooter(); 310 | exit; 311 | } 312 | 313 | function printFooter() 314 | { 315 | echo ""; 316 | } 317 | 318 | 319 | function has_field_sqlite($db, $table, $field) 320 | { 321 | $q = $db->dq("PRAGMA table_info(". $db->quote($table). ")"); 322 | while($r = $q->fetch_row()) { 323 | if($r[1] == $field) return true; 324 | } 325 | return false; 326 | } 327 | 328 | function has_field_mysql($db, $table, $field) 329 | { 330 | $q = $db->dq("DESCRIBE `$table`"); 331 | while($r = $q->fetch_row()) { 332 | if($r[0] == $field) return true; 333 | } 334 | return false; 335 | } 336 | 337 | function testConnect(&$error) 338 | { 339 | try 340 | { 341 | if(Config::get('db') == 'mysql') 342 | { 343 | require_once(MTTPATH. 'class.db.mysql.php'); 344 | $db = new Database_Mysql; 345 | $db->connect(Config::get('mysql.host'), Config::get('mysql.user'), Config::get('mysql.password'), Config::get('mysql.db')); 346 | } else 347 | { 348 | if(false === $f = @fopen(MTTPATH. 'db/todolist.db', 'a+')) throw new Exception("database file is not readable/writable"); 349 | else fclose($f); 350 | 351 | if(!is_writable(MTTPATH. 'db/')) throw new Exception("database directory ('db') is not writable"); 352 | 353 | require_once(MTTPATH. 'class.db.sqlite3.php'); 354 | $db = new Database_Sqlite3; 355 | $db->connect(MTTPATH. 'db/todolist.db'); 356 | } 357 | } catch(Exception $e) { 358 | $error = $e->getMessage(); 359 | return 0; 360 | } 361 | return 1; 362 | } 363 | 364 | function myExceptionHandler($e) 365 | { 366 | echo '
      Fatal Error: \''. $e->getMessage() .'\' in '. $e->getFile() .':'. $e->getLine() . ''. 367 | "\n
      ". $e->getTraceAsString() . "
      \n"; 368 | exit; 369 | } 370 | 371 | 372 | ### 1.1-1.2 ########## 373 | function update_11_12($db, $dbtype) 374 | { 375 | if($dbtype == 'mysql') $db->ex("ALTER TABLE todolist ADD `duedate` DATE default NULL"); 376 | else $db->ex("ALTER TABLE todolist ADD duedate DATE default NULL"); 377 | 378 | # Fixing broken tags 379 | $db->ex("BEGIN"); 380 | $db->ex("DELETE FROM tags"); 381 | $db->ex("DELETE FROM tag2task"); 382 | $q = $db->dq("SELECT id,tags FROM todolist"); 383 | while($r = $q->fetch_assoc()) 384 | { 385 | if($r['tags'] == '') continue; 386 | $tag_ids = prepare_tags($r['tags']); 387 | if($tag_ids) update_task_tags($r['id'], $tag_ids); 388 | } 389 | $db->ex("COMMIT"); 390 | } 391 | 392 | function prepare_tags(&$tags_str) 393 | { 394 | $tag_ids = array(); 395 | $tag_names = array(); 396 | $tags = explode(',', $tags_str); 397 | foreach($tags as $v) 398 | { 399 | # remove duplicate tags? 400 | $tag = str_replace(array('"',"'"),array('',''),trim($v)); 401 | if($tag == '') continue; 402 | list($tag_id,$tag_name) = get_or_create_tag($tag); 403 | if($tag_id && !in_array($tag_id, $tag_ids)) { 404 | $tag_ids[] = $tag_id; 405 | $tag_names[] = $tag_name; 406 | } 407 | } 408 | $tags_str = implode(',', $tag_names); 409 | return $tag_ids; 410 | } 411 | 412 | function get_or_create_tag($name) 413 | { 414 | global $db; 415 | $tag = $db->sq("SELECT id,name FROM tags WHERE name=?", $name); 416 | if($tag) return $tag; 417 | 418 | # need to create tag 419 | $db->ex("INSERT INTO tags (name) VALUES (?)", $name); 420 | return array($db->last_insert_id(), $name); 421 | } 422 | 423 | function update_task_tags($id, $tag_ids) 424 | { 425 | global $db; 426 | foreach($tag_ids as $v) { 427 | $db->ex("INSERT INTO tag2task (task_id,tag_id) VALUES ($id,$v)"); 428 | } 429 | $db->ex("UPDATE tags SET tags_count=tags_count+1 WHERE id IN (". implode(',', $tag_ids). ")"); 430 | } 431 | 432 | ### end 1.1-1.2 ##### 433 | 434 | ### 1.2-1.3 ########## 435 | function update_12_13($db, $dbtype) 436 | { 437 | # update config 438 | Config::save(); 439 | 440 | # and then db 441 | $db->ex("BEGIN"); 442 | if($dbtype=='mysql') 443 | { 444 | $db->ex( 445 | "CREATE TABLE lists ( 446 | `id` INT UNSIGNED NOT NULL auto_increment, 447 | `name` VARCHAR(50) NOT NULL default '', 448 | PRIMARY KEY(`id`) 449 | ) CHARSET=utf8 "); 450 | $db->ex("ALTER TABLE todolist ADD `list_id` INT UNSIGNED NOT NULL default 0"); 451 | $db->ex("ALTER TABLE tags ADD `list_id` INT UNSIGNED NOT NULL default 0"); 452 | 453 | $db->ex("ALTER TABLE todolist ADD KEY(`list_id`)"); 454 | $db->ex("DROP INDEX `name` ON tags"); 455 | $db->ex("ALTER TABLE tags ADD UNIQUE KEY `listid_name` (`list_id`,`name`)"); 456 | } 457 | else 458 | { 459 | $db->ex( 460 | "CREATE TABLE lists ( 461 | id INTEGER PRIMARY KEY, 462 | name VARCHAR(50) NOT NULL 463 | ) "); 464 | $db->ex("ALTER TABLE todolist ADD list_id INTEGER UNSIGNED NOT NULL default 0"); 465 | $db->ex("CREATE INDEX todolist_list_id ON todolist (list_id)"); 466 | 467 | $db->ex( 468 | "CREATE TEMPORARY TABLE tags_backup ( 469 | id INTEGER, 470 | name VARCHAR(50) NOT NULL, 471 | tags_count INT default 0 472 | ) "); 473 | $db->ex("INSERT INTO tags_backup SELECT id,name,tags_count FROM tags"); 474 | $db->ex("DROP TABLE tags"); 475 | $db->ex( 476 | "CREATE TABLE tags ( 477 | id INTEGER PRIMARY KEY, 478 | name VARCHAR(50) NOT NULL, 479 | tags_count INT default 0, 480 | list_id INTEGER UNSIGNED NOT NULL default 0 481 | ) "); 482 | $db->ex("INSERT INTO tags (id,name,tags_count) SELECT id,name,tags_count FROM tags_backup"); 483 | $db->ex("CREATE UNIQUE INDEX tags_listid_name ON tags (list_id,name COLLATE NOCASE) "); 484 | $db->ex("DROP TABLE tags_backup"); 485 | } 486 | $db->ex("COMMIT"); 487 | 488 | $db->ex("INSERT INTO lists (name,d_created) VALUES (?,?)", array('Todo', time())); 489 | $db->ex("UPDATE todolist SET list_id=1"); 490 | 491 | } 492 | 493 | ### end 1.2-1.3 ##### 494 | 495 | 496 | ### 1.3.0 to 1.3.1 ########## 497 | function update_130_131($db, $dbtype) 498 | { 499 | $tz = null; 500 | if(isset($_POST['tz'])) { 501 | $tz = (int)$_POST['tz']; 502 | if($tz<-720 || $tz>720 || $tz%30!=0) $tz = null; 503 | else $tz = $tz*60; 504 | } 505 | if(is_null($tz)) $tz = (int)date('Z'); 506 | 507 | # if($dbtype=='sqlite') { 508 | # $temp_store_pragma = $db->sq("PRAGMA temp_store"); 509 | # $db->ex("PRAGMA temp_store = MEMORY"); 510 | # } 511 | 512 | $db->ex("BEGIN"); 513 | if($dbtype=='mysql') 514 | { 515 | $db->ex("ALTER TABLE lists ADD `ow` INT NOT NULL default 0"); 516 | $db->ex("ALTER TABLE lists ADD `d_created` INT UNSIGNED NOT NULL default 0"); 517 | $db->ex("ALTER TABLE lists ADD `sorting` TINYINT UNSIGNED NOT NULL default 0"); 518 | $db->ex("ALTER TABLE lists ADD `published` TINYINT UNSIGNED NOT NULL default 0"); 519 | $db->ex("ALTER TABLE lists ADD `taskview` INT UNSIGNED NOT NULL default 0"); 520 | 521 | $db->ex("ALTER TABLE todolist ADD `d_created` INT UNSIGNED NOT NULL default 0"); 522 | $db->ex("ALTER TABLE todolist ADD `d_completed` INT UNSIGNED NOT NULL default 0"); 523 | 524 | # convert task date... 525 | $db_session_timezone = $db->sq("SELECT @@session.time_zone");; 526 | $db->ex("SET time_zone='+0:00'"); 527 | $tz = -1*$tz; 528 | $db->ex("UPDATE todolist SET d_created = UNIX_TIMESTAMP(d) + TIME_TO_SEC(TIMEDIFF(NOW(),UTC_TIMESTAMP())) ".($tz<0?'-':'+').abs($tz)); 529 | $db->ex("SET time_zone=?", array($db_session_timezone)); 530 | 531 | $db->ex("ALTER TABLE todolist DROP `d`"); 532 | } 533 | else 534 | { 535 | $db->ex("ALTER TABLE lists ADD ow INTEGER NOT NULL default 0"); 536 | $db->ex("ALTER TABLE lists ADD d_created INTEGER UNSIGNED NOT NULL default 0"); 537 | $db->ex("ALTER TABLE lists ADD sorting TINYINT UNSIGNED NOT NULL default 0"); 538 | $db->ex("ALTER TABLE lists ADD published TINYINT UNSIGNED NOT NULL default 0"); 539 | $db->ex("ALTER TABLE lists ADD taskview INTEGER UNSIGNED NOT NULL default 0"); 540 | 541 | $db->ex("ALTER TABLE todolist ADD d_created INTEGER UNSIGNED NOT NULL default 0"); 542 | $db->ex("ALTER TABLE todolist ADD d_completed INTEGER UNSIGNED NOT NULL default 0"); 543 | 544 | # convert task date to timestamp 545 | $tz = -1*$tz; 546 | $db->ex("UPDATE todolist SET d_created=strftime('%s',d) ".($tz<0?'-':'+').abs($tz)); 547 | 548 | # drop unnecessary field 'd' 549 | $db->ex( 550 | "CREATE TEMPORARY TABLE todolist_backup ( 551 | id INTEGER, 552 | list_id INTEGER UNSIGNED NOT NULL default 0, 553 | d_created INTEGER UNSIGNED NOT NULL default 0, 554 | d_completed INTEGER UNSIGNED NOT NULL default 0, 555 | compl TINYINT UNSIGNED NOT NULL default 0, 556 | title VARCHAR(250) NOT NULL, 557 | note TEXT, 558 | prio TINYINT NOT NULL default 0, 559 | ow INT NOT NULL default 0, 560 | tags VARCHAR(250) NOT NULL default '', 561 | duedate DATE default NULL 562 | ) "); 563 | $db->ex("INSERT INTO todolist_backup (id,list_id,d_created,d_completed,compl,title,note,prio,ow,tags,duedate) ". 564 | " SELECT id,list_id,d_created,d_completed,compl,title,note,prio,ow,tags,duedate FROM todolist"); 565 | $db->ex("DROP TABLE todolist"); 566 | 567 | $db->ex( 568 | "CREATE TABLE todolist ( 569 | id INTEGER PRIMARY KEY, 570 | list_id INTEGER UNSIGNED NOT NULL default 0, 571 | d_created INTEGER UNSIGNED NOT NULL default 0, 572 | d_completed INTEGER UNSIGNED NOT NULL default 0, 573 | compl TINYINT UNSIGNED NOT NULL default 0, 574 | title VARCHAR(250) NOT NULL, 575 | note TEXT, 576 | prio TINYINT NOT NULL default 0, 577 | ow INT NOT NULL default 0, 578 | tags VARCHAR(250) NOT NULL default '', 579 | duedate DATE default NULL 580 | ) "); 581 | $db->ex("CREATE INDEX list_id ON todolist (list_id)"); 582 | 583 | $db->ex("INSERT INTO todolist (id,list_id,d_created,d_completed,compl,title,note,prio,ow,tags,duedate) ". 584 | " SELECT id,list_id,d_created,d_completed,compl,title,note,prio,ow,tags,duedate FROM todolist_backup"); 585 | $db->ex("DROP TABLE todolist_backup"); 586 | } 587 | 588 | $sort = 0; 589 | if(isset($_COOKIE['sort']) && $_COOKIE['sort'] != ''){ 590 | $sort = (int)$_COOKIE['sort']; 591 | if($sort < 0 || $sort > 2) $sort = 0; 592 | } 593 | 594 | if(Config::get('password') != '' && Config::get('allowread')) $published = 1; 595 | else $published = 0; 596 | 597 | $db->ex("UPDATE lists SET d_created=?, sorting=?, published=?", array(time(), $sort, $published)); 598 | $db->ex("UPDATE todolist SET d_completed=d_created WHERE compl=1"); 599 | 600 | $db->ex("COMMIT"); 601 | 602 | # if($dbtype=='sqlite') { 603 | # $db->ex("PRAGMA temp_store = $temp_store_pragma"); 604 | # } 605 | } 606 | 607 | ### end of 1.3.0 to 1.3.1 ########## 608 | 609 | ### update v1.3.1 to v1.4 ########## 610 | function update_131_14($db, $dbtype) 611 | { 612 | $db->ex("BEGIN"); 613 | if($dbtype=='mysql') 614 | { 615 | $db->ex("DROP TABLE {$db->prefix}tags"); 616 | $db->ex( 617 | "CREATE TABLE {$db->prefix}tags ( 618 | `id` INT UNSIGNED NOT NULL auto_increment, 619 | `name` VARCHAR(50) NOT NULL, 620 | PRIMARY KEY(`id`), 621 | UNIQUE KEY `name` (`name`) 622 | ) CHARSET=utf8 "); 623 | 624 | $db->ex("ALTER TABLE {$db->prefix}todolist CHANGE `tags` `tags` VARCHAR(600) NOT NULL default ''"); 625 | $db->ex("ALTER TABLE {$db->prefix}todolist ADD `tags_ids` VARCHAR(250) NOT NULL default ''"); 626 | $db->ex("ALTER TABLE {$db->prefix}todolist ADD `uuid` CHAR(36) NOT NULL default ''"); 627 | $db->ex("ALTER TABLE {$db->prefix}todolist ADD `d_edited` INT UNSIGNED NOT NULL default 0"); 628 | 629 | $db->ex("ALTER TABLE {$db->prefix}tag2task ADD `list_id` INT UNSIGNED NOT NULL"); 630 | $db->ex("ALTER TABLE {$db->prefix}tag2task ADD KEY(`list_id`)"); 631 | 632 | $db->ex("ALTER TABLE {$db->prefix}lists ADD `uuid` CHAR(36) NOT NULL default ''"); 633 | $db->ex("ALTER TABLE {$db->prefix}lists ADD `d_edited` INT UNSIGNED NOT NULL default 0"); 634 | 635 | } 636 | else #sqlite 637 | { 638 | # changes in tags table: fully new 639 | $db->ex("DROP TABLE {$db->prefix}tags"); //index will be deleted too 640 | $db->ex( 641 | "CREATE TABLE {$db->prefix}tags ( 642 | id INTEGER PRIMARY KEY AUTOINCREMENT, 643 | name VARCHAR(50) NOT NULL COLLATE NOCASE 644 | ) "); 645 | $db->ex("CREATE UNIQUE INDEX tags_name ON {$db->prefix}tags (name COLLATE NOCASE)"); 646 | 647 | # changes in todolist table: uuid, d_edited, tags, tags_ids 648 | $db->ex( 649 | "CREATE TABLE todolist_new ( 650 | id INTEGER PRIMARY KEY, 651 | uuid CHAR(36) NOT NULL default '', 652 | list_id INTEGER UNSIGNED NOT NULL default 0, 653 | d_created INTEGER UNSIGNED NOT NULL default 0, 654 | d_completed INTEGER UNSIGNED NOT NULL default 0, 655 | d_edited INTEGER UNSIGNED NOT NULL default 0, 656 | compl TINYINT UNSIGNED NOT NULL default 0, 657 | title VARCHAR(250) NOT NULL, 658 | note TEXT, 659 | prio TINYINT NOT NULL default 0, 660 | ow INTEGER NOT NULL default 0, 661 | tags VARCHAR(600) NOT NULL default '', 662 | tags_ids VARCHAR(250) NOT NULL default '', 663 | duedate DATE default NULL 664 | ) "); 665 | $db->ex("INSERT INTO todolist_new (id,list_id,d_created,d_completed,compl,title,note,prio,ow,tags,duedate)". 666 | " SELECT id,list_id,d_created,d_completed,compl,title,note,prio,ow,tags,duedate FROM {$db->prefix}todolist"); 667 | $db->ex("DROP TABLE {$db->prefix}todolist"); 668 | $db->ex("ALTER TABLE todolist_new RENAME TO {$db->prefix}todolist"); 669 | $db->ex("CREATE INDEX todo_list_id ON {$db->prefix}todolist (list_id)"); #1st index of 2 670 | 671 | # changes in tag2task table: new column and index, new names of indexes 672 | $db->ex("ALTER TABLE {$db->prefix}tag2task ADD list_id INTEGER NOT NULL default 0"); 673 | $db->ex("DROP INDEX tag_id"); 674 | $db->ex("DROP INDEX task_id "); 675 | $db->ex("CREATE INDEX tag2task_tag_id ON {$db->prefix}tag2task (tag_id)"); 676 | $db->ex("CREATE INDEX tag2task_task_id ON {$db->prefix}tag2task (task_id)"); 677 | $db->ex("CREATE INDEX tag2task_list_id ON {$db->prefix}tag2task (list_id)"); 678 | 679 | # changes in lists table: uuid, d_edited 680 | $db->ex("ALTER TABLE {$db->prefix}lists ADD uuid CHAR(36) NOT NULL default ''"); 681 | $db->ex("ALTER TABLE {$db->prefix}lists ADD d_edited INTEGER UNSIGNED NOT NULL default 0"); 682 | 683 | } 684 | 685 | # recreate tags 686 | $db->ex("DELETE FROM {$db->prefix}tag2task"); 687 | 688 | $q = $db->dq("SELECT id,list_id,tags FROM {$db->prefix}todolist WHERE tags != ''"); 689 | $ar = array(); 690 | while($r = $q->fetch_assoc()) $ar[] = $r; 691 | foreach($ar as $r) 692 | { 693 | $aTags = v14_prepareTags($r['tags']); 694 | if($aTags) 695 | { 696 | v14_addTaskTags($r['id'], $aTags['ids'], $r['list_id']); 697 | $db->ex("UPDATE {$db->prefix}todolist SET tags=?,tags_ids=? WHERE id=".$r['id'], 698 | array(implode(',',$aTags['tags']), implode(',',$aTags['ids'])) ); 699 | } 700 | } 701 | 702 | # fix bug with empty lists.d_created 703 | $db->ex("UPDATE {$db->prefix}lists SET d_created=?", time()); 704 | 705 | # init d_edited 706 | $db->ex("UPDATE {$db->prefix}todolist SET d_edited=d_created"); 707 | $db->ex("UPDATE {$db->prefix}todolist SET d_edited=d_completed WHERE d_completed > d_edited"); 708 | $db->ex("UPDATE {$db->prefix}lists SET d_edited=d_created"); 709 | 710 | # add UUID 711 | $q = $db->dq("SELECT id FROM {$db->prefix}todolist"); 712 | $ar = array(); 713 | while($r = $q->fetch_assoc()) $ar[] = $r; 714 | foreach($ar as $r) { 715 | $db->ex("UPDATE {$db->prefix}todolist SET uuid=? WHERE id=".$r['id'], array(generateUUID()) ); 716 | } 717 | 718 | $q = $db->dq("SELECT id FROM {$db->prefix}lists"); 719 | $ar = array(); 720 | while($r = $q->fetch_assoc()) $ar[] = $r; 721 | foreach($ar as $r) { 722 | $db->ex("UPDATE {$db->prefix}lists SET uuid=? WHERE id=".$r['id'], array(generateUUID()) ); 723 | } 724 | 725 | # create unique indexes for UUID 726 | if($dbtype=='mysql') 727 | { 728 | $db->ex("ALTER TABLE {$db->prefix}lists ADD UNIQUE KEY (`uuid`)"); 729 | $db->ex("ALTER TABLE {$db->prefix}todolist ADD UNIQUE KEY (`uuid`)"); 730 | } 731 | else 732 | { 733 | $db->ex("CREATE UNIQUE INDEX lists_uuid ON {$db->prefix}lists (uuid)"); 734 | $db->ex("CREATE UNIQUE INDEX todo_uuid ON {$db->prefix}todolist (uuid)"); 735 | } 736 | 737 | $db->ex("COMMIT"); 738 | } 739 | 740 | function v14_prepareTags($tagsStr) 741 | { 742 | $tags = explode(',', $tagsStr); 743 | if(!$tags) return 0; 744 | 745 | $aTags = array('tags'=>array(), 'ids'=>array()); 746 | foreach($tags as $tag) 747 | { 748 | $tag = str_replace(array('"',"'",'<','>','&','/','\\','^'),'',trim($tag)); 749 | if($tag == '') continue; 750 | 751 | $aTag = v14_getOrCreateTag($tag); 752 | if($aTag && !in_array($aTag['id'], $aTags['ids'])) { 753 | $aTags['tags'][] = $aTag['name']; 754 | $aTags['ids'][] = $aTag['id']; 755 | } 756 | } 757 | return $aTags; 758 | } 759 | 760 | function v14_getOrCreateTag($name) 761 | { 762 | global $db; 763 | $tagId = $db->sq("SELECT id FROM {$db->prefix}tags WHERE name=?", array($name)); 764 | if($tagId) return array('id'=>$tagId, 'name'=>$name); 765 | 766 | $db->ex("INSERT INTO {$db->prefix}tags (name) VALUES (?)", array($name)); 767 | return array('id'=>$db->last_insert_id(), 'name'=>$name); 768 | } 769 | 770 | function v14_addTaskTags($taskId, $tagIds, $listId) 771 | { 772 | global $db; 773 | if(!$tagIds) return; 774 | foreach($tagIds as $tagId) 775 | { 776 | $db->ex("INSERT INTO {$db->prefix}tag2task (task_id,tag_id,list_id) VALUES (?,?,?)", array($taskId,$tagId,$listId)); 777 | } 778 | } 779 | ### end of 1.4 ##### 780 | 781 | 782 | ?> -------------------------------------------------------------------------------- /themes/default/images/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Image files page_white_text.png and calendar.png and some icons in buttons.png 2 | are (or based on) icons from Silk Icons set by Mark James (http://www.famfamfam.com/lab/icons/silk/), 3 | licensed under the Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/). 4 | 5 | Some icons in buttons.png are based on "Silk Companion 1" icons set by Damien Guard 6 | (http://damieng.com/creative/icons/silk-companion-1-icons), licensed under the 7 | Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/). 8 | 9 | Icons in mzl.png are (or based on) icons from Mozilla Source Code, 10 | (http://www.mozilla.org/MPL/#source-code), licensed under the terms 11 | of tri-license (MPL 1.1/GPL 2.0/LGPL 2.1). 12 | 13 | Other images in this directory were made by Max Pozdeev and Jeremie Francois and licensed under the terms of GNU GPL v2+. -------------------------------------------------------------------------------- /themes/default/images/arrdown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/arrdown.gif -------------------------------------------------------------------------------- /themes/default/images/arrdown2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/arrdown2.gif -------------------------------------------------------------------------------- /themes/default/images/buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/buttons.png -------------------------------------------------------------------------------- /themes/default/images/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/calendar.png -------------------------------------------------------------------------------- /themes/default/images/closetag.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/closetag.gif -------------------------------------------------------------------------------- /themes/default/images/corner_left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/corner_left.gif -------------------------------------------------------------------------------- /themes/default/images/corner_right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/corner_right.gif -------------------------------------------------------------------------------- /themes/default/images/fixme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/fixme.png -------------------------------------------------------------------------------- /themes/default/images/icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/icons.gif -------------------------------------------------------------------------------- /themes/default/images/index.html: -------------------------------------------------------------------------------- 1 | Place for Images -------------------------------------------------------------------------------- /themes/default/images/loading1_24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/loading1_24.gif -------------------------------------------------------------------------------- /themes/default/images/mzl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/mzl.png -------------------------------------------------------------------------------- /themes/default/images/page_white_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/page_white_text.png -------------------------------------------------------------------------------- /themes/default/images/tab_hover.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/tab_hover.gif -------------------------------------------------------------------------------- /themes/default/images/time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/time.png -------------------------------------------------------------------------------- /themes/default/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoonCactus/myTDX/018383d4428d510fcb2e15e5625f0bffb71e7b25/themes/default/images/user.png -------------------------------------------------------------------------------- /themes/default/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <?php mttinfo('title'); ?> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 68 | 69 |
      70 |
      71 |
      72 | 73 |

      74 | 75 |
      76 | 77 |
      78 |
      79 |
      80 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |
      90 |
      91 | 92 |
      93 | 94 | 154 | 155 | 156 | 200 | 201 | 202 | 209 | 210 | 216 | 217 | 225 | 226 | 231 | 232 | 233 | 253 | 254 | 260 | 261 | 270 | 271 | 279 | 280 | 284 | 285 | 291 | 292 | 293 | 294 |
      295 |
      296 |
      297 | 298 | 299 | 300 |
      301 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /themes/default/markdown.css: -------------------------------------------------------------------------------- 1 | /* 2 | Almost copy/pasted from drawdown/index.html 3 | FIXME: there are most probably conflicts with existinf CSS 4 | */ 5 | 6 | * { 7 | box-sizing: border-box; 8 | } 9 | html, body { 10 | width: 100%; 11 | height: 100%; 12 | padding: 0; 13 | margin: 0; 14 | overflow: hidden; 15 | } 16 | hr { 17 | margin: 1em 0; 18 | border: 0; 19 | border-bottom: 1px solid #ccc; 20 | } 21 | blockquote { 22 | margin-left: 0; 23 | padding: 0.5em 0 0.5em 2em; 24 | border-left: 3px solid rgb(211, 218, 234); 25 | } 26 | code { 27 | background: rgba(211, 218, 234, 0.25); 28 | } 29 | pre > code { 30 | display: block; 31 | padding: 0.5em 4em; 32 | } 33 | table { 34 | border-spacing: 0; 35 | border-collapse: collapse; 36 | } 37 | td { 38 | padding: 4px 8px; 39 | } 40 | tr:nth-child(2n) { 41 | background: #f3f3f3; 42 | } 43 | th { 44 | border-bottom: 1px solid #aaa; 45 | } 46 | img { 47 | max-width: 96px; 48 | } 49 | .plain, .markdown { 50 | width: 100%; 51 | margin: 0; 52 | overflow: scroll; 53 | } 54 | .plain { 55 | border: 0; 56 | border-right: 1px solid #000; 57 | padding: 12px; 58 | font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; 59 | font-size: 12px; 60 | resize: none; 61 | } 62 | .markdown { 63 | left: 50%; 64 | padding: 0 0 0 12px; 65 | overflow: auto; 66 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif; 67 | font-size: 14px; 68 | line-height: 1.3; 69 | } -------------------------------------------------------------------------------- /themes/default/pda.css: -------------------------------------------------------------------------------- 1 | body { margin:0px; font-size:100%; } 2 | h2 { margin:0; margin-bottom:2px; font-size:1em; } 3 | h3 { margin-bottom:4px; padding:4px 0; } 4 | #body { margin-left:1px; margin-right:1px; padding:1px; padding-bottom:12px;} 5 | #bar_login, #bar_logout { padding-right:1px; } 6 | 7 | #tabs ul { margin-top:0px; } 8 | #tabs ul li { width:70px; margin-right: 1px; } 9 | .tab-content { padding:4px; } 10 | 11 | #htab_search { width:40%; max-width:190px; } 12 | .mtt-searchbox { float:right; } 13 | .mtt-searchbox td { width:40%; } 14 | #toolbar.mtt-intask #htab_search { display:none; } 15 | #toolbar.mtt-insearch #htab_newtask { display:none; } 16 | #toolbar.mtt-insearch #htab_search { width:100%; } 17 | #toolbar.mtt-insearch .mtt-searchbox td { width:40%; } 18 | 19 | #tasklist li { padding:0.5em 3px; overflow:hidden; } 20 | .task-actions { display:none; } 21 | .task-date { display:none; } 22 | .task-note-actions { display:block; padding-top:8px; } 23 | .task-note-block { margin-left:0px; border-left:1px solid #777777; background:none; padding-left:4px; margin-top:1px; padding-top:0px; display:none; } 24 | .task-note-area textarea { width:95%; } 25 | .task-middle { margin-right:0px; } 26 | 27 | #tasklist li .task-through { white-space:nowrap; overflow:hidden; } 28 | #tasklist li:hover { background-color:#ffffff; } 29 | #tasklist li.task-expanded .task-note-block { display:none; } 30 | /*#tasklist li.task-expanded .task-toggle { background-position:-32px 0; }*/ 31 | #tasklist li.clicked { background-color:#f6f6f6; } 32 | #tasklist li.clicked .task-actions { display:block; } 33 | #tasklist li.clicked .task-through { white-space:normal; display:inline; } 34 | #tasklist li.clicked.task-has-note .task-note-block { display:block; } 35 | /*#tasklist li.clicked.task-has-note .task-toggle { background-position:-48px 0; } */ 36 | #tasklist li.clicked.doubleclicked.task-has-note .task-note-block { display:none; } 37 | .task-toggle { display:none; } 38 | .task-middle { margin-left:25px; } 39 | 40 | #page_taskedit { max-width:99.5%; border:none; position:static; padding:0; } 41 | #page_taskedit .form-table { width:100%; } 42 | #page_taskedit .form-row .in500 { color:#444444; } 43 | #page_taskedit .form-row textarea { height: 70px; } 44 | 45 | #loading { padding:0px; padding-top:1px; padding-right:1px; height:16px; overflow:hidden; } 46 | #loading img { /*width:8px; height:8px;*/ } 47 | 48 | #tagcloud { max-width:100%; } 49 | .mtt-settings-table .in350 { min-width:50px; } 50 | .mtt-notes-showhide { display:none; } 51 | -------------------------------------------------------------------------------- /themes/default/print.css: -------------------------------------------------------------------------------- 1 | html { height:0; } 2 | body { height:0; min-height:0; margin:0; } 3 | h2{ display: none; } 4 | h3 { border-bottom:2px solid #777777; } 5 | #lists { display: none; } 6 | #toolbar { display: none; } 7 | .small-bar { display:none; } 8 | .task-actions { display:none; } 9 | 10 | #bar { display:none; } 11 | #taskviewcontainer { border:none; } 12 | #taskviewcontainer img { display:none; } 13 | 14 | #tasklist { list-style-type: decimal; list-style-position: outside; } 15 | #tasklist li { padding-left:0px; margin-left:30px; border-bottom:none; padding-bottom: 8px; } 16 | div.task-note-block { border-left:1px solid #777777; background:none; padding-left:5px; margin-top:5px; padding-top:0px; font-size:9pt; color:#333333; } 17 | .task-middle { margin-left:0px; margin-right:3px; } 18 | .task-left { display:none; } 19 | 20 | .task-date { white-space:nowrap; margin-left:10px; } 21 | #tasklist li.today, #tasklist li.past { background-color:#ffffff; border-color:#dedede; } 22 | .task-prio { font-weight:bold; } 23 | 24 | li.task-completed { opacity:1; } 25 | 26 | #footer_content { border-top:1px solid #777777; background:none; } 27 | #footer_content a { text-decoration:none; color:#000000; } 28 | 29 | #tagcloudbtn { display:none; } 30 | .mtt-notes-showhide { display:none; } 31 | #taskview img { display:none; } -------------------------------------------------------------------------------- /themes/default/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | (C) Copyright 2009-2010 myTinyTodo by Max Pozdeev 3 | (C) Copyright 2017 fork myTDX by Jérémie FRANCOIS 4 | Licensed under the GNU GPL v2 license. See file COPYRIGHT for details. 5 | */ 6 | 7 | /* default style */ 8 | 9 | html { height:100%; overflow-y:scroll; } 10 | body { margin:0px; padding:0px; height:100%; min-height:100%; background-color:#fff; font-family:arial; } 11 | #wrapper { margin:0px auto; max-width:1200px; height:100%; } 12 | #container { height:auto !important;height:100%;min-height:100%; } 13 | #mtt_body { padding:8px; padding-bottom:16px; } 14 | 15 | 16 | td, th, input, textarea, select { font-family:arial; font-size:1em; } 17 | form { display: inline; } 18 | h2,h3 { margin:0; } 19 | h2 { font-size:1.5em; float:left; padding-right:10px; background-color:#ffffff; } 20 | h3 { border-bottom:2px solid #B5D5FF; margin-bottom:10px; padding:6px 0; font-size:1.1em; } 21 | #page_tasks h3 { padding-left:4px; padding-right:4px; } 22 | a { color:#0000ff; cursor:pointer; text-decoration:underline; } 23 | 24 | #space { height:30px; } 25 | #footer { height:30px; margin-top:-30px; } 26 | #footer_content { background-color:#b5d5ff; padding:5px; font-size:0.8em; } 27 | #footer_content a { color:#000000; } 28 | 29 | #bar { border-bottom:1px solid #b5d5ff; padding-bottom:5px; height:15px; } 30 | .bar-menu { float:right; } 31 | .nodecor { text-decoration:none; } 32 | #bar_logout { display:none; } 33 | #bar_auth { display:none; } 34 | #authform { overflow: hidden; z-index:100; background-color:#f9f9f9; border:1px solid #cccccc; padding:5px; width:160px; } 35 | #authform div { padding:2px 0px; } 36 | #authform div.h { font-weight:bold; } 37 | #loading progress { float:left; width: 4em; height: 6px; margin-top:10px; } 38 | 39 | #msg { float:left; } 40 | #msg .msg-text { padding:1px 4px; font-weight:bold; cursor:pointer; } 41 | #msg .msg-details { padding:1px 4px; background-color:#fff; display:none; max-width:700px; } 42 | #msg.mtt-error .msg-text { background-color:#ff3333; } 43 | #msg.mtt-error .msg-details { border:1px solid #ff3333; } 44 | #msg.mtt-info .msg-text { background-color:#EFC300; } 45 | #msg.mtt-info .msg-details { border:1px solid #EFC300;} 46 | 47 | .mtt-tabs { list-style:none; padding:0; margin:0; } 48 | .mtt-tabs li { margin:1px 3px 0 0; float:left; border-left:1px solid #ededed; background:#fbfbfb url(images/tab_hover.gif) no-repeat top right; } 49 | .mtt-tab a { position:relative; margin:0; font-size:0.9em; font-weight:bold; text-decoration:none; text-align:center; white-space:nowrap; color:#444444; display:block; height:21px; padding:6px 6px 0px 2px; outline:none; vertical-align:top; } 50 | .mtt-tab a span { display:inline-block; min-width:75px; max-width:195px; cursor:pointer; padding:0; overflow:hidden; } 51 | .mtt-tab .list-action { display:none; float:left; position:absolute; top:6px; right:5px; width:15px; height:15px; background:transparent url(images/icons.gif) 0 0 no-repeat; cursor:pointer; } 52 | .mtt-tab .list-action:hover, .mtt-tab .list-action.mtt-menu-button-active { background-position:-16px 0; } 53 | .mtt-tab.mtt-tabs-selected span { margin-right:16px; } 54 | .mtt-tab.mtt-tabs-selected .list-action { display:block; } 55 | .mtt-tab.mtt-tabs-selected { background:#ededed url(images/corner_right.gif) no-repeat top right; border-left:1px solid #ededed; } 56 | .mtt-tabs li:hover a { color: #888888; } 57 | .mtt-tabs.mtt-tabs-only-one li { display:none; } 58 | .mtt-tabs.mtt-tabs-only-one li.mtt-tabs-selected { display:block; } 59 | #mtt_body.readonly li.mtt-tabs-selected span { margin-right:0; } 60 | .mtt-tabs-hidden { display:none; } 61 | 62 | .mtt-tabs-alltasks { margin:1px 3px 0 3px; float:right; border-right:1px solid #ededed; background:#fbfbfb url(images/tab_hover.gif) no-repeat top left; } 63 | .mtt-tabs-alltasks.mtt-tab a { padding:6px 2px 0px 6px; } 64 | .mtt-tabs-alltasks.mtt-tab.mtt-tabs-selected { border-left:none; border-right:1px solid #ededed; background:#ededed url(images/corner_left.gif) no-repeat top left; } 65 | 66 | 67 | #tabs_buttons { 68 | float:right; 69 | padding-top:4px; 70 | padding-bottom:2px; 71 | padding-left:2px; 72 | padding-right:2px; 73 | border:1px solid #ededed; 74 | border-bottom:none; 75 | margin-top:1px; 76 | -moz-border-radius-topright:5px; -webkit-border-top-right-radius:5px; border-top-right-radius:5px; 77 | } 78 | 79 | .mtt-tabs-button { 80 | float:left; 81 | font-size:0.9em; 82 | padding:1px; /* makes button bigger */ 83 | border:1px solid transparent; /* preallocate space for :hover border */ 84 | } 85 | 86 | .mtt-tabs-button span { 87 | display:block; 88 | width:16px; 89 | height:16px; 90 | } 91 | 92 | .mtt-tabs-button:hover, .mtt-tabs-button.mtt-menu-button-active { 93 | border:1px solid #ccc; -moz-border-radius:3px; -webkit-border-radius:3px; border-radius:3px; 94 | } 95 | 96 | .mtt-tabs-add-button { float:left; margin-top:1px; padding:6px 2px 0px 2px; font-size:0.9em; height:21px; border-left:1px solid #ededed; background:#fbfbfb url(images/tab_hover.gif) no-repeat top right; } 97 | .mtt-tabs-add-button:hover { cursor:pointer; } 98 | .mtt-tabs-add-button>span { display:block; width:16px; height:16px; background:url(images/buttons.png) 0 0 no-repeat; } 99 | .mtt-tabs-add-button:hover>span { background-position:-16px 0; } 100 | #mtt_body.readonly .mtt-tabs-add-button { display:none; } 101 | 102 | .mtt-tabs-select-button>span { background:url(images/icons.gif) -64px 0 no-repeat; } 103 | .mtt-tabs-select-button:hover>span, .mtt-tabs-select-button.mtt-menu-button-active>span { background-position:-80px 0; } 104 | 105 | 106 | #mtt_body.no-lists #toolbar > * { visibility:hidden; } 107 | .mtt-htabs { clear:both; padding:8px; border-bottom:2px solid #DEDEDE; background:#ededed; } 108 | 109 | .mtt-img-button { width:16px; height:16px; padding:2px; border:1px solid transparent; display:inline-block; } 110 | .mtt-img-button:hover { border:1px solid #ccc; -moz-border-radius:3px; -webkit-border-radius:3px; border-radius:3px; } 111 | .mtt-img-button span { display:inline-block; width:16px; height:16px; } 112 | 113 | .arrdown { display:inline-block; height:7px; width:9px; background:url(images/arrdown.gif); } 114 | .arrdown2 { display:inline-block; height:7px; width:7px; background:url(images/arrdown2.gif); } 115 | 116 | 117 | /* Quick Task Add */ 118 | 119 | .mtt-taskbox td.mtt-tb-cell { padding:0px; width:450px; } 120 | .mtt-tb-c { position:relative; padding-left:22px; /*input padding+border*/ } 121 | #task { color:#444444; background:#fff; height:1.35em; padding:2px; padding-right:18px; border:1px inset #F0F0F0; width:100%; margin-left:-22px; } 122 | #task_placeholder span { display:none; color:#ccc; position:absolute; left:0; top:0; height:1.35em; line-height:1.35em; padding:3px; /*input top and left padding+border*/ } 123 | #task_placeholder.placeholding span { display:inline-block; } 124 | 125 | .mtt-taskbox-icon { width:16px; height:16px; position:absolute; top:50%; margin-top:-8px; } 126 | .mtt-taskbox-icon.mtt-icon-submittask { background:url(images/mzl.png) 0px -32px no-repeat; right:4px; } 127 | 128 | #newtask_adv span { background:url(images/buttons.png) 0 -48px no-repeat; } 129 | #newtask_adv:hover span { background-position:-16px -48px; } 130 | #mtt_body.show-all-tasks #htab_newtask, #mtt_body.readonly #htab_newtask { display:none; } 131 | 132 | 133 | /* Live Search */ 134 | #htab_search { float:right; } 135 | #search { 136 | color:#444444; background:#fff; height:1.35em; padding:2px 18px; width:100%; margin-left:-38px; /*padding+border*/ 137 | border:1px inset #F0F0F0; -moz-border-radius:10px; -webkit-border-radius:10px; border-radius:10px; 138 | } 139 | #search_close { display:none; } 140 | 141 | .mtt-searchbox td { padding:0px; width:180px; } 142 | .mtt-searchbox-c { position:relative; padding-left:38px; /*input padding+border*/ } 143 | .mtt-searchbox-icon { width:16px; height:16px; position:absolute; top:50%; margin-top:-8px; } 144 | 145 | .mtt-searchbox-icon.mtt-icon-search { background:url(images/mzl.png) 0px -16px no-repeat; left:4px; } 146 | .mtt-searchbox-icon.mtt-icon-cancelsearch { background:url(images/mzl.png) 0px 0px no-repeat; right:4px; } 147 | 148 | #searchbar { font-size:1em; font-weight:normal; display:none; margin-top:5px; } 149 | #searchbarkeyword { font-weight:bold; } 150 | 151 | 152 | /* */ 153 | #mtt_body.no-lists #page_tasks h3 > * { visibility:hidden; } 154 | .mtt-notes-showhide { font-size:0.8em; font-weight:normal; margin-left:2px; margin-right:2px; } 155 | .mtt-notes-showhide a { text-decoration:none; border-bottom:1px dotted; } 156 | 157 | #mtt_filters { font-size:0.8em; font-weight:normal; } 158 | .tag-filter { margin-left:3px; margin-right:3px; } 159 | .tag-filter-exclude { text-decoration:line-through; } 160 | .mtt-filter-header { font-weight:bold; margin-right:.33em; } 161 | .mtt-filter-close { cursor:pointer; position:relative; top:2px; margin-left:3px; display:inline-block; width:10px; height:10px; background:url(images/closetag.gif) 0 0 no-repeat; } 162 | 163 | .task-left { float:left; } 164 | .task-toggle { visibility:hidden; margin-top:2px; cursor:pointer; width:15px; height:15px; float:left; background:url(images/icons.gif) -64px -16px no-repeat; } 165 | li.task-has-note .task-toggle { visibility:visible; } 166 | li.task-expanded .task-toggle { background-position:-80px -16px; } 167 | .task-middle { margin-left:40px; margin-right:20px; } 168 | #tasklist { list-style-type: none; margin: 0; padding: 0;} 169 | #tasklist>li { padding:6px 2px 6px 6px; border-bottom:1px solid #DEDEDE; min-height:18px; margin-bottom:1px; background-color:#fff; } 170 | #tasklist>li:hover { background-color:#f6f6f6; } 171 | .task-actions { float:right; width:20px; text-align:right; } 172 | .task-date { color:#999999; font-size:0.8em; margin-left:4px; display:none; } 173 | .task-date-completed { color:#999999; display:none; margin-left:5px; } 174 | .show-inline-date .task-date { display:inline; } 175 | .show-inline-date li.task-completed .task-date-completed { display:inline; } 176 | .show-inline-date li.task-completed .task-date { display:none; } 177 | .hide-task-xref .task-through .task-xref { display:none; } 178 | .task-through { overflow:hidden; } 179 | .task-through-right { float:right; } 180 | .task-title a { color:#000000; } 181 | .task-title a:hover { color:#af0000; } 182 | #mtt_body.readonly #tasklist li .task-actions { display:none; } 183 | .task-listname { background-color:#eee; color:#555; padding:0px 3px; } 184 | .task-tags { padding:0px 2px; } 185 | .task-tags .tag { font-size:0.8em; color:#333333; text-decoration:underline; } 186 | .task-tags .tag:hover { } 187 | .duedate { color:#333333; padding:0px; padding-left:1px; margin-left:5px; } 188 | li.task-completed .duedate { /*font-size:0.8em;*/ display:none; } 189 | #tasklist>li.soon .duedate { color:#008000; } 190 | #tasklist>li.today .duedate { color:#FF0000; } 191 | #tasklist>li.past .duedate { color:#A52A2A; } 192 | li.task-completed .task-middle { color:#777777;} 193 | li.task-completed .task-through { text-decoration:line-through; } 194 | li.task-completed .task-title a { color:#777777; } 195 | #tasklist>li.task-completed { opacity:0.6; filter:alpha(opacity=60); } 196 | #tasklist>li.task-completed:hover { opacity:1.0; filter:alpha(opacity=100); } 197 | #tasklist>li.not-in-tagpreview { opacity:0.1; filter: alpha(opacity=10); } 198 | #tasklist>li.mtt-task-placeholder { 199 | min-height:0px; padding:0px; height:18px; line-height:18px; 200 | background-color:#ddd; border:1px solid #aaa; 201 | -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; 202 | } 203 | 204 | a.taskactionbtn { display:block; float:right; height:15px; width:15px; text-decoration:none; background:url(images/icons.gif) 0 0 no-repeat; display:none; } 205 | li:hover a.taskactionbtn, a.taskactionbtn.mtt-menu-button-active { background-position:-16px 0; display:block;} 206 | 207 | #tasklist.filter-past>li, #tasklist.filter-today li, #tasklist.filter-soon li { display:none; } 208 | #tasklist.filter-past>li.past, #tasklist.filter-today li.today, #tasklist.filter-soon li.soon { display:block; } 209 | #tasklist.filter-past>li.task-completed, #tasklist.filter-today li.task-completed, #tasklist.filter-soon li.task-completed { display:none; } 210 | 211 | .task-note-block { margin-left:2px; color:#777777; background:url(images/page_white_text.png) left 2px no-repeat; padding-left:19px; padding-top:2px; min-height:16px; margin-top:2px; display:none; } 212 | li.task-expanded .task-note-block { display:block; } 213 | li.task-completed .task-note-block .task-note { text-decoration:line-through; } 214 | .task-note-area { display:none; margin-bottom:5px; } 215 | .task-note-area textarea { color:#999999; width:100%; display:block; height:65px; } 216 | .task-note-actions { font-size:0.8em; } 217 | .hidden { display:none; } 218 | .invisible { visibility:hidden; } 219 | .in500 { width:500px; color:#444444; } 220 | .in100 { width:100px; color:#444444; } 221 | .task-note span a { text-decoration: none; color:#A0A0FF; } 222 | .task-note span a:hover { color:#af0000; } 223 | 224 | .task-prio { padding-left:2px; padding-right:2px; margin-left:0px; margin-right:5px; cursor:default; } 225 | .prio-neg { background-color:#3377ff; color:#ffffff; } 226 | .prio-pos { background-color:#ff3333; color:#ffffff; } 227 | .prio-pos-1 { background-color:#ff7700; color:#ffffff; } 228 | .prio-zero { /*background-color:#dedede;*/ } 229 | .task-prio.prio-zero { display:none; } 230 | .task-xref { font-size:80%; padding-right:4px; text-decoration: none; color:#A0A0FF; } 231 | .task-note .task-xref a { text-decoration: underline; } 232 | 233 | .form-row { margin-top:8px; } 234 | .form-row .h { font-weight:bold; color:#333333; } 235 | .form-row .descr { padding-left:1em; font-weight:100; font-size:0.8em; font-weight:normal; color:#222; } 236 | .form-row-short-end { clear:both; } 237 | #page_taskedit .form-row .in500 { width:99%; } 238 | #page_taskedit .form-row textarea.in500 { height:200px; /*resize:none;*/ } 239 | #page_taskedit .form-row-short { float:left; margin-right:12px; } 240 | #page_taskedit .form-bottom-buttons { text-align:center; } 241 | #alltags .tag { font-weight:bold; color:#333333; } 242 | #alltags .tag:hover { background-color:#999988; color:white; } 243 | #alltags .tag_alien { color: #AAAAAA; } 244 | .alltags-cell { width:1%; white-space:nowrap; padding-left:5px; } 245 | #page_taskedit.mtt-inadd .mtt-inedit { display:none; } 246 | #page_taskedit.mtt-inedit .mtt-inadd { display:none; } 247 | #taskedit-date { font-size:1em; font-weight:normal; display:inline; color:#777; margin-left:8px; } 248 | 249 | a.mtt-back-button { font-size:0.8em; } 250 | 251 | /* autocomplete */ 252 | .ac_results { padding:0px; border:1px solid #cccccc; background-color:#f9f9f9; overflow:hidden; z-index:99999; -moz-box-shadow:1px 2px 5px rgba(0,0,0,0.5); -webkit-box-shadow:1px 2px 5px rgba(0,0,0,0.5); } 253 | .ac_results ul { width: 100%; list-style-position: outside; list-style: none; padding: 0; margin: 0; } 254 | .ac_results li { margin: 0px; padding: 2px 5px; cursor: default; display: block; line-height: 16px; overflow: hidden; } 255 | .ac_over { background-color:#316AC5; color:white; } 256 | 257 | #priopopup { overflow: hidden; z-index:100; background-color:#f9f9f9; border:1px solid #cccccc; padding:5px; } 258 | #priopopup span { cursor:pointer; border:1px solid #f9f9f9; } 259 | #priopopup .prio-zero:hover { border-color:#dedede; } 260 | #priopopup .prio-neg:hover { border-color:#3377ff; } 261 | #priopopup .prio-pos:hover { border-color:#ff3333; } 262 | #priopopup .prio-pos-1:hover { border-color:#ff7700; } 263 | 264 | #tagcloudbtn { margin-right:2px; font-size:0.8em; font-weight:normal; padding:2px; float:right; } 265 | #mtt_body.show-all-tasks #tagcloudbtn { display:none; } 266 | #tagcloudload { display:none; height:24px; background:url(images/loading1_24.gif) center no-repeat; } 267 | #tagcloud { 268 | overflow: hidden; z-index:100; background-color:#f9f9f9; border:1px solid #cccccc; padding:5px; 269 | width:100%; max-width:450px; margin:0px 7px 7px 7px; text-align:center; 270 | -moz-box-shadow:1px 2px 5px rgba(0,0,0,0.5); -webkit-box-shadow:1px 2px 5px rgba(0,0,0,0.5); 271 | } 272 | #tagcloud .tag { margin:1px 0px; padding:2px; line-height:140%; color:black; } 273 | #tagcloud .tag:hover { background-color:#999988; color:white; } 274 | #tagcloud .w0 { font-size:80%; } 275 | #tagcloud .w1 { font-size:90%; } 276 | #tagcloud .w2 { font-size:100%; } 277 | #tagcloud .w3 { font-size:110%; } 278 | #tagcloud .w4 { font-size:120%; } 279 | #tagcloud .w5 { font-size:130%; } 280 | #tagcloud .w6 { font-size:140%; } 281 | #tagcloud .w7 { font-size:150%; } 282 | #tagcloud .w8 { font-size:160%; } 283 | #tagcloud .w9 { font-size:170%; } 284 | 285 | #tagcloudcancel { float:right; } 286 | #tagcloudcancel span { background:url(images/buttons.png) 0 -32px no-repeat; } 287 | #tagcloudcancel span:hover { background-position:-16px -32px; } 288 | 289 | #taskview { padding:2px; } 290 | 291 | .ui-datepicker { width:190px; z-index:202; border: 1px solid #cccccc; background: #ffffff; display:none; padding:2px; 292 | -moz-box-shadow:1px 2px 5px rgba(0,0,0,0.5); -webkit-box-shadow:1px 2px 5px rgba(0,0,0,0.5); box-shadow:1px 2px 5px rgba(0,0,0,0.5); 293 | -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; 294 | } 295 | .ui-datepicker-trigger { cursor:pointer; vertical-align:text-bottom; margin-left:1px; } 296 | .ui-datepicker-calendar { width:100%; border-collapse:collapse; } 297 | .ui-datepicker-calendar thead th { text-align:center; padding:0px; font-size:0.9em; } 298 | .ui-datepicker-calendar tbody td { text-align:right; padding:1px; } 299 | .ui-datepicker-calendar td a { display:block; text-decoration:none; color:#444444; border:1px solid #cccccc; background-color:#f9f9f9; color:#111; padding:1px; } 300 | .ui-datepicker-calendar td.ui-datepicker-current-day a { background-color:#EAF5FF; color:#222222; border-color:#5980FF; } 301 | .ui-datepicker-calendar td.ui-datepicker-today a { color:#fff; background-color:#ccc; } 302 | .ui-datepicker-calendar td a:hover { border-color:#5980FF; } 303 | .ui-datepicker-header { padding:3px 0px; } 304 | .ui-datepicker-prev { position:absolute; left:2px; height:20px; text-decoration:none; } 305 | .ui-datepicker-next { position:absolute; right:2px; height:20px; text-decoration:none; } 306 | .ui-datepicker-title { text-align:center; line-height:20px; } 307 | .ui-icon { width:16px; height:16px; text-indent:-99999px; overflow:hidden; } 308 | .ui-datepicker .ui-icon-circle-triangle-w { display:block; position:absolute; top:50%; margin-top:-8px; left:50%; background:url(images/icons.gif) -48px -16px no-repeat; } 309 | .ui-datepicker .ui-icon-circle-triangle-e { display:block; position:absolute; top:50%; margin-top:-8px; right:50%; background:url(images/icons.gif) -32px -16px no-repeat; } 310 | 311 | .mtt-menu-button { 312 | -moz-user-select:none; -webkit-user-select: none; 313 | cursor:pointer; 314 | border:1px solid transparent; 315 | } 316 | .mtt-menu-button:hover, .mtt-menu-button.mtt-menu-button-active { 317 | border:1px solid #ccc; -moz-border-radius:3px; -webkit-border-radius:3px; border-radius:3px; 318 | } 319 | 320 | .mtt-menu-container { 321 | overflow:hidden; z-index:100; 322 | background-color:#f9f9f9; border:1px solid #cccccc; padding:2px 0px; 323 | -moz-box-shadow:1px 2px 5px rgba(0,0,0,0.5); -webkit-box-shadow:1px 2px 5px rgba(0,0,0,0.5); box-shadow:1px 2px 5px rgba(0,0,0,0.5); 324 | -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; 325 | margin:0px 7px 7px 0px; /* for shadows */ 326 | } 327 | .mtt-menu-container ul { list-style: none; padding:0; margin:0; } 328 | .mtt-menu-container li { margin:1px 0px; cursor:default; color:#000; white-space:nowrap; padding:0.15em 0px; padding-left:24px; padding-right:18px; position:relative; } 329 | .mtt-menu-container li:hover, .mtt-menu-container li.mtt-menu-item-active { 330 | background-color:#316AC5; 331 | color:white; 332 | background:-moz-linear-gradient(#5373fc, #3157f4); 333 | background:-webkit-gradient(linear, left top, left bottom, from(#5373fc), to(#3157f4)); 334 | } 335 | .mtt-menu-container li.mtt-item-disabled, .mtt-menu-container li.mtt-item-disabled a { color:#ACA899; } 336 | .mtt-menu-container a { display:block; cursor:default; text-decoration:none; outline:none; color:#000; } 337 | .mtt-menu-container li:hover a { color:white; } 338 | .mtt-menu-container li.mtt-menu-delimiter { height:0px; line-height:0; border-bottom:1px solid #cccccc; margin:2px -1px; padding:0px; font-size:0px; } 339 | .mtt-menu-container .menu-icon { width:16px; height:16px; position:absolute; left:4px; top:50%; margin-top:-8px; } 340 | li.mtt-item-checked .menu-icon { background:url(images/icons.gif) -16px -16px; } 341 | li.mtt-menu-indicator .submenu-icon { 342 | position:absolute; right:2px; top:50%; margin-top:-8px; 343 | width:16px; height:16px; background:url(images/icons.gif) -32px -16px no-repeat; 344 | } 345 | li.mtt-item-hidden { display:none; } 346 | 347 | #slmenucontainer li.mtt-list-hidden a { color:#777777; text-decoration:line-through; } 348 | #cmenulistscontainer li.mtt-list-hidden { color:#777777; text-decoration:line-through; } 349 | 350 | #btnRssFeed .menu-icon { background:url(images/buttons.png) -16px -64px no-repeat; } 351 | #btnRssFeed.mtt-item-disabled .menu-icon { background:url(images/buttons.png) 0px -64px no-repeat; } 352 | 353 | .mtt-settings-table { width:100%; border-collapse:collapse; } 354 | .mtt-settings-table th, .mtt-settings-table td { border-bottom:1px solid #dedede; padding:8px; vertical-align:top; } 355 | .mtt-settings-table .form-buttons { border-bottom:none; text-align:center; } 356 | .mtt-settings-table th { text-align:left; width:210px; padding-left:8px; } 357 | .mtt-settings-table .descr { font-size:0.8em; font-weight:normal; color:#222; } 358 | .mtt-settings-table .in350 { min-width:350px; } 359 | -------------------------------------------------------------------------------- /themes/default/style_rtl.css: -------------------------------------------------------------------------------- 1 | body { direction:rtl; } 2 | h2 { float:right; padding-left:10px; padding-right:0px; } 3 | .bar-menu { float:left; } 4 | #loading { float:right; } 5 | #lists .mtt-tabs { float:right; } 6 | .mtt-tabs li { float:right; margin:1px 0 0 3px; border-left:none; border-right:1px solid #ededed; background:#fbfbfb url(images/tab_hover.gif) no-repeat top left; } 7 | .mtt-tabs li.mtt-tabs-selected { border-left:none; border-right:1px solid #ededed; background:#ededed url(images/corner_left.gif) no-repeat top left; } 8 | 9 | #tabs_buttons { float:left; } 10 | .mtt-tabs-button { float:right; } 11 | #tagcloudbtn { float:left; } 12 | 13 | #htab_search { float:left; } 14 | #task { padding: 2px 2px 2px 18px; } 15 | #task_placeholder span { right:0px; left:auto; } 16 | .mtt-taskbox-icon.mtt-icon-submittask { left:4px; right:auto; } 17 | .mtt-searchbox-icon.mtt-icon-search { right:4px; left:auto; } 18 | .mtt-searchbox-icon.mtt-icon-cancelsearch { left:4px; right:auto; } 19 | 20 | .task-actions { float:left; text-align:left; width:15px; } 21 | .task-left { float:right; } 22 | .task-toggle { float:right; } 23 | .task-middle { margin-right:40px; margin-left:25px; } 24 | .task-date { margin-right:4px; } 25 | .duedate { float:left; margin-left:0; margin-right:5px; } 26 | .duedate-arrow { display:none; } 27 | .duedate:before { content:'\2190'; } 28 | .task-through-right { float:left; } 29 | 30 | #taskedit-date { float:left; } 31 | #page_taskedit .form-row-short { float:right; margin-left:12px; margin-right:0; } 32 | .task-prio { float:right; margin-left:4px; margin-right:0; } 33 | .alltags-cell { padding-left:0; padding-right:5px; } -------------------------------------------------------------------------------- /themes/default/tags.css: -------------------------------------------------------------------------------- 1 | /* CSS for tag kinds @username or #status */ 2 | 3 | .task-tags { 4 | float: right; 5 | } 6 | 7 | .task-tags IMG { 8 | height: 12px; 9 | padding-right:4px; 10 | vertical-align: bottom; 11 | padding-bottom: 2px; 12 | } 13 | 14 | .task-tags .tag_user { 15 | border: 1px solid gray; 16 | padding-left: 4px; 17 | padding-right: 4px; 18 | background-color: #44FF88; 19 | border-radius: 3px; 20 | text-decoration: none; 21 | } 22 | 23 | .task-tags .tag_time { 24 | border: 1px solid red; 25 | background-color: yellow; 26 | color: black; 27 | padding-left: 4px; 28 | padding-right: 4px; 29 | border-radius: 3px; 30 | text-decoration: none; 31 | } 32 | 33 | .task-tags .tag_fixme { 34 | border: 1px solid red; 35 | background-color: #FFAAAA; 36 | color: black; 37 | padding-left: 4px; 38 | padding-right: 4px; 39 | border-radius: 3px; 40 | text-decoration: none; 41 | } 42 | 43 | .task-tags .tag_highlight { 44 | width: 1em; 45 | height: 100%; 46 | border: 1px solid gray; 47 | border-radius: 1em; 48 | padding: 0px 4px 0px 4px; 49 | text-decoration: none; 50 | background-color: #DDDDFF; 51 | } 52 | 53 | /* Specific CSS for specific tags: use "tag_yourname" */ 54 | -------------------------------------------------------------------------------- /themes/index.html: -------------------------------------------------------------------------------- 1 | somewhere -------------------------------------------------------------------------------- /tmp/.htaccess: -------------------------------------------------------------------------------- 1 | deny from all -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 1.5.3 2 | --------------------------------------------------------------------------------