Index: trunk/extensions/TodoTasks/SpecialTaskList.php |
— | — | @@ -0,0 +1,430 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/* |
| 5 | +This program is free software; you can redistribute it and/or |
| 6 | +modify it under the terms of the GNU General Public License |
| 7 | +as published by the Free Software Foundation, version 2 |
| 8 | +of the License. |
| 9 | + |
| 10 | +This program is distributed in the hope that it will be useful, |
| 11 | +but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | +GNU General Public License for more details. |
| 14 | + |
| 15 | +You should have received a copy of the GNU General Public License |
| 16 | +along with this program; if not, write to the Free Software |
| 17 | +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 18 | +*/ |
| 19 | + |
| 20 | +$wgUseProjects = true; |
| 21 | + |
| 22 | +global $wgHooks; |
| 23 | +global $wgSpecialPages; |
| 24 | + |
| 25 | +$wgSpecialPages['TaskList'] = 'TaskList'; |
| 26 | +if ($wgUseProjects) |
| 27 | + $wgSpecialPages['TaskListByProject'] = 'TaskListByProject'; |
| 28 | +$wgHooks['PersonalUrls'][] = 'addPersonalUrl'; |
| 29 | +$wgHooks['AlternateEdit'][] = 'todoPreviewAction'; |
| 30 | +$wgHooks['EditPage::attemptSave'][] = 'todoSavePreparser'; |
| 31 | +$wgExtensionFunctions[] = 'wfTodoParserFunction_Setup'; |
| 32 | +$wgHooks['LanguageGetMagic'][] = 'wfTodoParserFunction_Magic'; |
| 33 | +$wgTodoTasksExtensionVersion = '0.8.1'; |
| 34 | +$wgExtensionCredits['parserhook'][] = array( |
| 35 | + 'version' => $wgTodoTasksExtensionVersion, |
| 36 | + 'name' => 'Todo Tasks', |
| 37 | + 'author' => 'Paul Grinberg', |
| 38 | + 'email' => 'gri6507 at yahoo dot com', |
| 39 | + 'url' => 'http://www.mediawiki.org/wiki/Extension:Todo_Tasks', |
| 40 | + 'description' => 'adds <nowiki>{{#todo:}}</nowiki> parser function for assigning tasks'); |
| 41 | +$wgExtensionCredits['specialpage'][] = array( |
| 42 | + 'name' => 'Todo Tasks', |
| 43 | + 'version' => $wgTodoTasksExtensionVersion, |
| 44 | + 'author' => 'Paul Grinberg', |
| 45 | + 'email' => 'gri6507 at yahoo dot com', |
| 46 | + 'url' => 'http://www.mediawiki.org/wiki/Extension:Todo_Tasks', |
| 47 | + 'description' => 'Adds a special page for reviewing tasks assignments' |
| 48 | +); |
| 49 | + |
| 50 | + |
| 51 | +function wfTodoParserFunction_Setup() { |
| 52 | + global $wgParser; |
| 53 | + # Set a function hook associating the "example" magic word with our function |
| 54 | + $wgParser->setFunctionHook( 'todo', 'wfTodoParserFunction_Render' ); |
| 55 | +} |
| 56 | + |
| 57 | +function wfTodoParserFunction_Magic( &$magicWords, $langCode ) { |
| 58 | + # Add the magic word |
| 59 | + # The first array element is case sensitive, in this case it is not case sensitive |
| 60 | + # All remaining elements are synonyms for our parser function |
| 61 | + $magicWords['todo'] = array( 0, 'todo' ); |
| 62 | + # unless we return true, other parser functions extensions won't get loaded. |
| 63 | + return true; |
| 64 | +} |
| 65 | + |
| 66 | +function getUserIDFromUserText($user) { |
| 67 | + $dbr = wfGetDB( DB_SLAVE ); |
| 68 | + $userid = 0; |
| 69 | + |
| 70 | + if (preg_match('/^\s*(.*?)\s*$/', $user, $matches)) |
| 71 | + $user = $matches[1]; |
| 72 | + |
| 73 | + $u = User::newFromName($user); |
| 74 | + if ($u) { |
| 75 | + $userid = $u->idForName(); // valid userName |
| 76 | + } |
| 77 | + if (!$userid) { // if not a valid userName, try as a userRealName |
| 78 | + $userid = $dbr->selectField( 'user', 'user_id', array( 'user_real_name' => $user ), 'renderTodo' ); |
| 79 | + if (!$userid) { // if not valid userRealName, try case insensitive userRealName |
| 80 | + $sql = "SELECT user_id FROM ". $dbr->tableName('user') ." WHERE UPPER(user_real_name) LIKE '%" . strtoupper($user) |
| 81 | +. "%'"; |
| 82 | + $res = $dbr->query( $sql, __METHOD__ ); |
| 83 | + if ($dbr->numRows($res)) { |
| 84 | + $row = $dbr->fetchRow($res); |
| 85 | + $userid = $row[0]; |
| 86 | + } |
| 87 | + $dbr->freeResult($res); |
| 88 | + if (!$userid) { // if not case insensitive userRealName, try case insensitive lastname |
| 89 | + list ($first, $last) = preg_split('/\s+/', $user); |
| 90 | + if ($last != '') { |
| 91 | + $sql = "SELECT user_id FROM ". $dbr->tableName('user') ." WHERE UPPER(user_real_name) LIKE '%" . |
| 92 | +strtoupper($last) . "%'"; |
| 93 | + $res = $dbr->query( $sql, __METHOD__ ); |
| 94 | + if ($dbr->numRows($res)) { |
| 95 | + $row = $dbr->fetchRow($res); |
| 96 | + $userid = $row[0]; |
| 97 | + } |
| 98 | + $dbr->freeResult($res); |
| 99 | + } |
| 100 | + } |
| 101 | + } |
| 102 | + } |
| 103 | + return $userid; |
| 104 | +} |
| 105 | + |
| 106 | +function getValidProjects() { |
| 107 | + $ProjPageTitle = Title::newFromText ('TodoTasksValidProjects', NS_MEDIAWIKI) ; |
| 108 | + return Revision::newFromTitle($ProjPageTitle)->getText(); |
| 109 | +} |
| 110 | + |
| 111 | +function validateProject($projlist, $proj) { |
| 112 | + $validprojects = preg_split('/\s*\*\s*/', $projlist, -1, PREG_SPLIT_NO_EMPTY); |
| 113 | + foreach ($validprojects as $vp) { |
| 114 | + if (preg_match("/^$proj$/i", $vp)) |
| 115 | + return $vp; |
| 116 | + } |
| 117 | + |
| 118 | + return 'Unknown Project'; |
| 119 | +} |
| 120 | + |
| 121 | +$todoPreview; |
| 122 | +function wfTodoParserFunction_Render( &$parser, $input, $users, $project='') { |
| 123 | + global $wgOut, $wgSitename, $wgEmergencyContact, $todoPreview; |
| 124 | + global $wgUseProjects; |
| 125 | + |
| 126 | + $username = ''; |
| 127 | + $fullname = ''; |
| 128 | + $u = 0; |
| 129 | + $userIdList = array(); |
| 130 | + $userIdList2 = array(); |
| 131 | + $dbr = wfGetDB( DB_SLAVE ); |
| 132 | + |
| 133 | + $task_text = ''; |
| 134 | + $task_project = ''; |
| 135 | + |
| 136 | + if ($wgUseProjects) { |
| 137 | + if (isset($project) && ($project != '')) { |
| 138 | + $task_project .= "''"; |
| 139 | + $first = true; |
| 140 | + $validProjects = getValidProjects(); |
| 141 | + $projlist = preg_split('/\s*,\s*/', $project, -1, PREG_SPLIT_NO_EMPTY); |
| 142 | + foreach ( $projlist as $proj ) { |
| 143 | + if (!$first) { |
| 144 | + $task_project .= ', '; |
| 145 | + } else { |
| 146 | + $first = false; |
| 147 | + } |
| 148 | + $task_project .= validateProject($validProjects, $proj); |
| 149 | + } |
| 150 | + $task_project .= "'' - "; |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + $task_text .= "$input (''' "; |
| 155 | + |
| 156 | + if ($users == '') { // if no user specified |
| 157 | + $task_text .= "unspecified user"; |
| 158 | + } else { |
| 159 | + $first = true; |
| 160 | + $userlist = preg_split('/\s*,\s*/', $users, -1, PREG_SPLIT_NO_EMPTY); |
| 161 | + foreach ( $userlist as $user ) { |
| 162 | + if (!$first) { |
| 163 | + $task_text .= ', '; |
| 164 | + } else { |
| 165 | + $first = false; |
| 166 | + } |
| 167 | + $userid = getUserIDFromUserText($user); |
| 168 | + if ($userid != 0) { // successfully found the user |
| 169 | + $u = User::newFromId($userid); |
| 170 | + $username = $u->getName(); |
| 171 | + $fullname = $u->getRealName(); |
| 172 | + $task_text .= "${fullname}"; |
| 173 | + array_push($userIdList, $userid); |
| 174 | + array_push($userIdList2, $userid); |
| 175 | + } else { // fall through to worst case scenario |
| 176 | + $task_text .= "incorrect username"; |
| 177 | + } |
| 178 | + } |
| 179 | + } |
| 180 | + $task_text .= "''' )"; |
| 181 | + $text = $task_project . $task_text; |
| 182 | + |
| 183 | + /* The following assumes the existance of an extra wiki table called $prefix_todo. |
| 184 | + To create this table issue |
| 185 | + CREATE TABLE wiki_todo ( |
| 186 | + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, |
| 187 | + hash TINYBLOB |
| 188 | + ); |
| 189 | + */ |
| 190 | + |
| 191 | + $hash = md5($task_text); |
| 192 | + if (is_object($todoPreview) && !$todoPreview->preview) { |
| 193 | + /* Only when the action is a Submit instead of a Preview, send out an email |
| 194 | + reminder and store in database (if needed). |
| 195 | + */ |
| 196 | + $adminAddress = new MailAddress( $wgEmergencyContact, 'WikiAdmin' ); |
| 197 | + $tasklist = Title::newFromText("Special:TaskList"); |
| 198 | + $body = ",\n\nSomeone has assigned a new Task for you on " . $parser->getTitle()->getFullURL() . |
| 199 | + ".\n\nTo see your complete Task List go to " . $tasklist->getFullURL() . |
| 200 | + ".\n\n\tYour friendly ${wgSitename} notification system\n"; |
| 201 | + $row = $dbr->selectRow( 'todo', 'id', array( 'hash' => $hash ), __METHOD__ ); |
| 202 | + if (!$row) { // this is a new todo item |
| 203 | + while ($userid = array_pop($userIdList)) { |
| 204 | + $u = User::newFromId($userid); |
| 205 | + $fullname = $u->getRealName(); |
| 206 | + $email = "Dear ${fullname}$body"; |
| 207 | + $u->sendMail("[${wgSitename}] Task List Change", $email, $adminAddress->toString()); |
| 208 | + $dbr->insert('todo', array( 'hash' => $hash ), __METHOD__ ); |
| 209 | + } |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + |
| 214 | + return $text; |
| 215 | +} |
| 216 | + |
| 217 | +function addPersonalUrl(&$personal_urls, $wgTitle) |
| 218 | +{ |
| 219 | + global $wgOut; |
| 220 | + |
| 221 | + $personal_urls['mytasks'] = array( |
| 222 | + 'text' => "My tasks", |
| 223 | + 'href' => Skin::makeSpecialUrl( 'TaskList') |
| 224 | + ); |
| 225 | + return true; |
| 226 | +} |
| 227 | + |
| 228 | +function todoPreviewAction(&$q) { |
| 229 | + global $todoPreview; |
| 230 | + |
| 231 | + $todoPreview = $q; |
| 232 | + return true; |
| 233 | +} |
| 234 | + |
| 235 | +function todoSavePreparser(&$q) { |
| 236 | + // update the text of the todo so that it has a propper full name in it. |
| 237 | + // this way, the <dpl> search will work better |
| 238 | + $newpagetext = ''; |
| 239 | + $sections = preg_split('/({{.*?}})/', $q->textbox1, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); |
| 240 | + foreach($sections as $section) { |
| 241 | + if (preg_match("/ |
| 242 | + ^{{ # we found a template |
| 243 | + \s* # that may begin with spaces |
| 244 | + ( # capture the template name in $1 |
| 245 | + (todo) # template may be a TODO |
| 246 | + | # or |
| 247 | + (action item) # template may be an ACTION ITEM |
| 248 | + ) |
| 249 | + \s* # the template name may have trailing spaces |
| 250 | + \| # the pipe to indicate template parameter #1 (task description) |
| 251 | + ( # capture the template parameter in $4; this string is comprized of |
| 252 | + [^|]*? # a possible sequence of non pipe characters |
| 253 | + (\[\[.*\|?.*\]\])? # a possible wiki syntax link |
| 254 | + [^|]* # a possible sequence on non pipe characters |
| 255 | + ) |
| 256 | + \| # the pipe for the template parameter #3 (task assignees) |
| 257 | + ([,.\s\w\d]*) # any combination of 'chars' repeated any number of times, captured in $6 |
| 258 | + ( # following by an optional grouping captured in $7 |
| 259 | + \| # which is the template parameter #4 (project associated with task) |
| 260 | + \s*project\s*=\s* # which begins with project= (with possible spaces) |
| 261 | + [,.\s\w\d]*? # any combination of 'chars' repeated any number of times |
| 262 | + )? |
| 263 | + }}$ # which ends the template |
| 264 | + /ix", $section, $matches)) { |
| 265 | + $newpagetext .= '{{' . "$matches[1]|$matches[4]|"; |
| 266 | + $first = true; |
| 267 | + $userlist = preg_split('/\s*,\s*/', $matches[6], -1, PREG_SPLIT_NO_EMPTY); |
| 268 | + foreach ( $userlist as $user ) { |
| 269 | + if (!$first) { |
| 270 | + $newpagetext .= ', '; |
| 271 | + } else { |
| 272 | + $first = false; |
| 273 | + } |
| 274 | + $userid = getUserIDFromUserText($user); |
| 275 | + if ($userid != 0) { // successfully found the user |
| 276 | + $u = User::newFromId($userid); |
| 277 | + $username = $u->getName(); |
| 278 | + $fullname = $u->getRealName(); |
| 279 | + $newpagetext .= "${fullname}"; |
| 280 | + } else { // fall through to worst case scenario |
| 281 | + $newpagetext .= $user; |
| 282 | + } |
| 283 | + } |
| 284 | + if (isset($matches[7])) |
| 285 | + $newpagetext .= "$matches[7]"; |
| 286 | + $newpagetext .= '}}'; |
| 287 | + } else { |
| 288 | + $newpagetext .= $section; |
| 289 | + } |
| 290 | + } |
| 291 | + $q->textbox1 = $newpagetext; |
| 292 | + return true; |
| 293 | +} |
| 294 | + |
| 295 | + |
| 296 | +require ( dirname( __FILE__ ) . "/../includes/SpecialPage.php" ); |
| 297 | + |
| 298 | +class TaskList extends SpecialPage |
| 299 | +{ |
| 300 | + function TaskList() { |
| 301 | + SpecialPage::SpecialPage("TaskList"); |
| 302 | + self::loadMessages(); |
| 303 | + return true; |
| 304 | + } |
| 305 | + |
| 306 | + function loadMessages() { |
| 307 | + static $messagesLoaded = false; |
| 308 | + global $wgMessageCache; |
| 309 | + if ($messagesLoaded) return; |
| 310 | + $messagesLoaded = true; |
| 311 | + |
| 312 | + $allMessages = array( |
| 313 | + 'en' => array( |
| 314 | + 'tasklist' => 'Task List', |
| 315 | + ) |
| 316 | + ); |
| 317 | + |
| 318 | + foreach ( $allMessages as $lang => $langMessages ) { |
| 319 | + $wgMessageCache->addMessages( $langMessages, $lang ); |
| 320 | + } |
| 321 | + return true; |
| 322 | + } |
| 323 | + |
| 324 | + function execute($user) { |
| 325 | + global $wgRequest, $wgOut, $wgUser; |
| 326 | + |
| 327 | + $this->setHeaders(); |
| 328 | + $wgOut->setPagetitle(wfMsg('tasklist')); |
| 329 | + |
| 330 | + $u = array(); |
| 331 | + if (is_null($user)) { |
| 332 | + $u[] = $wgUser; |
| 333 | + } else { |
| 334 | + foreach (explode(',', $user) as $usr) { |
| 335 | + $u[] = User::newFromName($usr); |
| 336 | + } |
| 337 | + } |
| 338 | + |
| 339 | + foreach ($u as $user) { |
| 340 | + if (!$user) { |
| 341 | + $user = $wgUser; |
| 342 | + } |
| 343 | + $username = $user->getName(); |
| 344 | + $fullname = $user->getRealName(); |
| 345 | + list ($firstname, $lastname) = preg_split('/ /', $fullname); |
| 346 | + |
| 347 | + $wgOut->addWikiText("== Todo List for $fullname =="); |
| 348 | + $wgOut->addWikiText("<dpl> uses=Template:Todo\n notuses=Template:Status Legend\n include={Todo}.dpl\n |
| 349 | +includematch=/${fullname}/i\n </dpl>"); |
| 350 | + } |
| 351 | + } |
| 352 | +} |
| 353 | + |
| 354 | +class TaskListByProject extends SpecialPage |
| 355 | +{ |
| 356 | + function TaskListByProject() { |
| 357 | + SpecialPage::SpecialPage("TaskListByProject"); |
| 358 | + self::loadMessages(); |
| 359 | + return true; |
| 360 | + } |
| 361 | + |
| 362 | + function loadMessages() { |
| 363 | + static $messagesLoaded = false; |
| 364 | + global $wgMessageCache; |
| 365 | + if ($messagesLoaded) return; |
| 366 | + $messagesLoaded = true; |
| 367 | + |
| 368 | + $allMessages = array( |
| 369 | + 'en' => array( |
| 370 | + 'tasklistbyproject' => 'Task List By Project', |
| 371 | + ) |
| 372 | + ); |
| 373 | + |
| 374 | + foreach ( $allMessages as $lang => $langMessages ) { |
| 375 | + $wgMessageCache->addMessages( $langMessages, $lang ); |
| 376 | + } |
| 377 | + return true; |
| 378 | + } |
| 379 | + |
| 380 | + function execute($proj) { |
| 381 | + global $wgRequest, $wgOut; |
| 382 | + |
| 383 | + $this->setHeaders(); |
| 384 | + $wgOut->setPagetitle(wfMsg('tasklistbyproject')); |
| 385 | + |
| 386 | + $project = ''; |
| 387 | + |
| 388 | + if (isset($proj)) { |
| 389 | + $proj = str_replace('+', ' ', $proj); |
| 390 | + $validProjects = getValidProjects(); |
| 391 | + $project = validateProject($validProjects, $proj); |
| 392 | + if ($project == 'Unknown Project') { |
| 393 | + $wgOut->addWikiText("Project '''$proj''' is not a valid project. For a list of valid projects, see |
| 394 | +[[MediaWiki:TodoTasksValidProjects]]."); |
| 395 | + self::ValidProjectsForm(); |
| 396 | + return; |
| 397 | + } |
| 398 | + } |
| 399 | + |
| 400 | + self::ValidProjectsForm(); |
| 401 | + |
| 402 | + if ($project == '') |
| 403 | + $project = $wgRequest->getVal('project'); |
| 404 | + if ($project) { |
| 405 | + $wgOut->addWikiText("----"); |
| 406 | + $wgOut->addWikiText("Assigned Tasks for '''$project'''"); |
| 407 | + $dpl = "<dpl>\n uses=Template:Todo \n notuses=Template:Status Legend \n include={Todo}.dpl \n"; |
| 408 | + $dpl .= 'includematch=/project\s*=\s*([^\x2c]*\x2c)*\s*' . $project . '\s*(\x2c[^\x2c]*\s*)*$/i'; |
| 409 | + $dpl .= "\n </dpl>"; |
| 410 | + $wgOut->addWikiText($dpl); |
| 411 | + } |
| 412 | + } |
| 413 | + |
| 414 | + function ValidProjectsForm() { |
| 415 | + global $wgOut; |
| 416 | + |
| 417 | + $titleObj = SpecialPage::getTitleFor( "TaskListByProject" ); |
| 418 | + $kiaction = $titleObj->getLocalUrl(); |
| 419 | + $wgOut->addHtml("<FORM ACTION=\"{$kiaction}\" METHOD=GET><LABEL FOR=project>Select Project: </LABEL>"); |
| 420 | + $wgOut->addHtml("<select name=project>"); |
| 421 | + |
| 422 | + $validprojects = preg_split('/\s*\*\s*/', getValidProjects(), -1, PREG_SPLIT_NO_EMPTY); |
| 423 | + foreach ($validprojects as $vp) |
| 424 | + $wgOut->addHtml("<option value=\"$vp\">$vp</option>"); |
| 425 | + |
| 426 | + $wgOut->addHtml("</select><INPUT TYPE=submit VALUE='Display'>"); |
| 427 | + } |
| 428 | +} |
| 429 | + |
| 430 | + |
| 431 | +?> |