php-tool-box:todo
文書の過去の版を表示しています。
PHP 1ファイルのみでDB不要のTODO管理ツール - Todo.php
コメントと更新履歴はゼロと無限の間のログ » Todo.phpへどうぞ。
(MOONGIFT風に)みなさんはタスク管理にどんなものを使っているだろうか。Webのサービスでもインストール型のツールでも、TODO管理の方法は色々あるが、いざ探してみると意外に帯に短し襷に長しである。
Remember The Milkは高機能だが重いし、Tracは共有するには良いが個人で使うには冗長、iGoogleのTODOガジェットはシンプルでよいが痒いところに手が届かない。
それならいっそ、自分のサーバで手軽に管理できるTODOツールはどうだろうか。今日紹介するのはPHPが1ファイルのみ、しかもDBも不要なTODO管理ツール、「Todo.php」だ。
使い方
下記のソースコードをコピーして適当な名前でPHPファイルとして保存し、Todoクラスの先頭で定義してある定数(const)をお好みで変更してください。また、定数に合わせてデータを保存するディレクトリを作ってください。
なお、このソースコードではJavaScirptとCSSとして、操作性を高めるためにPryn.js/cssを、カレンダーを使うためにYahho CalendarとGCalHolidaysを使っていますが、これら無しでもTodo.phpは動きます。使わない場合はこれらを削除してください。
サンプル
さらに進んだ使い方
メールサーバが使えるなら、mail2todoを組み合わせて使うと便利です。
ライセンス
MITライセンスで。
ソースコード
<?php /** * Todo.php - Simple TODO Manager - * @version 0.2.9 * @see http://0-oo.net/sbox/php-tool-box/todo * @copyright 2008-2009 dgbadmin@gmail.com * @license http://0-oo.net/pryn/MIT_license.txt (The MIT license) */ class Todo { /** 文字コード */ const ENCODING = 'UTF-8'; /** サーバに保存するファイル名の文字コード */ //const FILE_NAME_ENCODING = 'UTF-8'; //Linuxその1 const FILE_NAME_ENCODING = 'EUC-JP'; //Linuxその2 //const FILE_NAME_ENCODING = 'Shift_JIS'; //Windows /** TODOデータを保存するディレクトリ */ const DATA_DIR = 'data'; /** データファイルの拡張子 */ const DATA_EXT = 'txt'; /** カテゴリ名として許可する正規表現 */ const CAT_REGEX = '^[^\\\\./:*?"<>|]{1,20}$'; /** ページタイトル */ const TITLE = 'TODO'; /** バックアップの保存期限 */ const BACKUP_TIME = '-7 day'; /** 優先度の最大値 */ const PRI_MAX = 5; public $cat; public $cats; public $list; /** * コンストラクタ * @param string $cat カテゴリ */ public function __construct($cat) { $this->cat = $cat; mb_internal_encoding(Todo::ENCODING); mb_regex_encoding(Todo::ENCODING); } /** * 表示の準備 */ public function setUp() { if ($this->isValidCat()) { if ($_REQUEST['delete'] && $this->_deleteCat()) { } else { if ($_POST['update']) { $this->_updateList(); } $this->list = $this->_getList(); } } $this->cats = $this->_getCategories(); } /** * カテゴリチェック * @return boolean 許可されるカテゴリかどうか */ public function isValidCat() { return mb_eregi(Todo::CAT_REGEX, $this->cat); } /** * TODOリストのファイルパスを取得する * @return string パス */ public function getPath() { $cat = mb_convert_encoding($this->cat, Todo::FILE_NAME_ENCODING); return Todo::DATA_DIR . '/' . $cat . '.' . Todo::DATA_EXT; } /** * カテゴリを全て取得する * @return array 全てのカテゴリのカテゴリ名とファイルサイズ */ private function _getCategories() { $h = opendir(Todo::DATA_DIR); if (!$h) { exit('data directory is not found.'); } $cats = array(); $limit = date('YmdHis', strToTime(Todo::BACKUP_TIME)); while (false !== ($file = readdir($h))) { if (is_dir($file)) { continue; } $arr = explode('.', $file); $path = Todo::DATA_DIR . '/' . $file; if (count($arr) == 3) { //バックアップの場合 if ($arr[2] < $limit) { //期限切れは削除 unlink($path); } } else { //最新版の場合 $cat = mb_convert_encoding($arr[0], Todo::ENCODING, Todo::FILE_NAME_ENCODING); $cats[$cat] = filesize($path); } } closedir($h); ksort($cats); return $cats; } /** * TODOリストを更新して保存する */ private function _updateList() { $oldCat = new Todo($_POST['oldcat']); //変更前のカテゴリ $newPath = $this->getPath(); if (is_file(strtoupper(__FILE__))) { //ファイルパスで大文字小文字を区別しない場合 $change = strcasecmp($oldCat->cat, $this->cat); } else { $change = ($oldCat->cat != $this->cat); } if ($change && is_file($newPath)) { return; //変更後のカテゴリが既に存在する場合は更新しない } $oldPath = $oldCat->getPath(); if ($oldCat->isValidCat() && is_file($oldPath)) { rename($oldPath, $oldPath . '.' . date('YmdHis')); //バックアップ } foreach ($_POST['todo'] as $post) { if ($post[1] != '') { //TODO未入力は削除 $data .= implode("\t", $post) . "\n"; } } file_put_contents($newPath, $data); } /** * TODOリストを取得する * @return array TODOリスト */ private function _getList() { $path = $this->getPath(); if (!is_file($path)) { //新規の場合 return array(''); } $list = explode("\n", file_get_contents($path)); rsort($list); //優先度順でソート return $list; } /** * カテゴリを削除する * @return boolean 削除できたかどうか */ private function _deleteCat() { $path = $this->getPath(); if (!is_file($path) || filesize($path)) { //todoが残っている場合は削除させない return false; } $this->cat = null; return unlink($path); } } //----- Global Functions ----- /** * HTMLエスケープ * @param string $val エスケープしたい文字列 * @return string エスケープした文字列 */ function h($val) { return htmlspecialchars($val, ENT_QUOTES); } /** * option要素を出力する * @param string $val optionの値 * @param string $selected selectedにすべき値 */ function echoOption($val, $selectedVal) { if ($val == $selectedVal) { $selected = 'selected="selected"'; } echo "<option $selected>$val</option>\n"; } //---------------------------- $todo = new Todo($_REQUEST['cat']); $todo->setUp(); ini_set('default_charset', Todo::ENCODING); ?> <<?php ?>?xml version="1.0" encoding="<?php echo Todo::ENCODING; ?>" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html;charset=<?php echo Todo::ENCODING; ?>" /> <meta http-equiv="Content-Style-Type" content="text/css" /> <meta http-equiv="Content-Script-Type" content="text/javascript" /> <title><?php echo h(Todo::TITLE . ' - ' . $todo->cat); ?></title> <link rel="stylesheet" type="text/css" href="/css/Pryn.css" /> <link rel="stylesheet" type="text/css" href="/css/YahhoStickyFooter.css" /> <style type="text/css"> div#hd{ padding-top: 1em; } h1{ margin: 1em 0 0; } h1 a{ text-decoration: none; } div#bd, li#add{ font-size: 100%; } div.yui-g{ width: 43em; } div#cat{ margin-bottom: 1em; font-size: 161.6%; } tr.row:hover{ background-color: #79a; } th, td{ border: solid #79a 1px; } td{ padding: 0; } select{ margin: 1px; width: 4.1em; height: 1.5em; text-align: center; } table#todo input, select{ border-width: 0; } input.todo{ margin: 2px; margin-left: 5px; width: 21.5em; } input.date{ margin: 1px; width: 5.6em; text-align: center; } input#update{ float: right; margin-right: 1em; } li{ padding-top: 0.5em; font-size: 131%; } input#new-cat{ width: 5em; } div#ft{ text-align: center; } </style> </head> <body> <div id="doc" class="yui-t2"> <!-- ヘッダー --> <div id="hd"><h1><a href="?"><?php echo h(Todo::TITLE); ?></a></h1></div> <div id="bd"> <div id="yui-main"><div class="yui-b"><div class="yui-g"> <?php $i = -1; if ($todo->isValidCat()) { ?> <form method="post" action="?"> <div id="cat"> カテゴリ : <input type="text" name="cat" value="<?php echo h($todo->cat); ?>" /> <input type="hidden" name="oldcat" value="<?php echo h($todo->cat); ?>" /> </div> <table id="todo" summary="todo"> <tr><th>優先度</th><th>TODO</th><th>開始日 </th><th>期限</th><th>状態</th></tr> <?php $styleClasses = array('', 'todo', 'date han', 'date han'); $statuses = array('<!-- -->', '保留', '完了'); foreach ($todo->list as $i => $row) { $task = explode("\t", $row); if ($task[4] == $statuses[2]) { //完了は出力しない continue; } ?> <tr class="row"> <!-- 優先度 --> <td> <select name="todo[<?php echo $i; ?>][]"> <?php for ($j = 1; $j < Todo::PRI_MAX + 1; $j++) { echoOption($j, $task[0]); } ?> </select> </td> <?php //TODO、開始日、期限 for ($j = 1; $j < 4; $j++) { ?> <td> <input type="text" name="todo[<?php echo $i; ?>][]" value="<?php echo h($task[$j]); ?>" class="<?php echo $styleClasses[$j]; ?>" /> </td> <?php } ?> <!-- 状態 --> <td> <select name="todo[<?php echo $i; ?>][]"> <?php foreach ($statuses as $status) { echoOption($status, $task[4]); } ?> </select> </td> </tr> <?php } ?> </table> <div> <input type="submit" id="update" name="update" value="更新" /> </div> </form> <div class="clear"></div> <?php } else if ($todo->cat) { ?> <span class="error">残念ですが、このカテゴリ名( <?php echo h($todo->cat); ?> )は使えません</span> <?php } ?> </div></div></div> <!-- カテゴリリスト --> <div id="cats" class="yui-b"> <ul> <?php foreach ($todo->cats as $cat => $fileSize) { $href = '?cat=' . urlencode($cat); ?> <li> <a href="<?php echo $href; ?>"><?php echo h($cat); ?></a> <?php if (!$fileSize) { //todoが無いカテゴリは削除できる ?> <a href="<?php echo $href; ?>&delete=do" title="このカテゴリを削除する">[削除]</a> <?php } ?> </li> <?php } ?> <li id="add"> <form method="post" action="?"> <div> <input type="text" id="new-cat" name="cat" /> <input type="submit" value="追加" /> </div> </form> </li> </ul> </div> </div> <!-- フッター --> <div id="ft"> powered by <a href="http://0-oo.net/sbox/php-tool-box/todo" class="same-window">Todo.php</a> </div> </div> <script type="text/javascript" src="/js/Pryn.js"></script> <script type="text/javascript" src="/js/YahhoCal.js"></script> <script type="text/javascript" src="/js/GCalHolidays.js"></script> <script type="text/javascript"> YahhoCal.loadYUI(); YahhoCal.setMondayAs1st(); Pryn.addEvent(window, "load", function() { (function(input) { var acs = new Pryn.ClassAccessor(input); if (acs.hasClass("date")) { Pryn.addEvent(input, "click", function() { YahhoCal.render(YAHOO.util.Dom.generateId(input)); }); acs.addClass("clickable"); } }).foreach($T("input")); }); </script> </body> </html>
php-tool-box/todo.1259353323.txt.gz · 最終更新: 2009/11/28 05:22 by dgbadmin