* @author Adrian Lang * @author Dominik Eckelmann */ class syntax_plugin_do_do extends DokuWiki_Syntax_Plugin { /** @var helper_plugin_do */ protected $hlp = null; // helper plugin protected $position = 0; private $run = array(); // page run cache private $saved = array(); // save state cache private $ids = array(); public function __construct() { $this->hlp = plugin_load('helper', 'do'); } /** @inheritDoc */ public function getType() { return 'formatting'; } /** @inheritDoc */ public function getPType() { return 'normal'; } /** @inheritDoc */ public function getSort() { return 155; } /** @inheritDoc */ public function getAllowedTypes() { return array('formatting'); } /** @inheritDoc */ public function connectTo($mode) { $this->Lexer->addEntryPattern('(?=.*?)', $mode, 'plugin_do_do'); } /** @inheritDoc */ public function postConnect() { $this->Lexer->addExitPattern('', 'plugin_do_do'); } /** @inheritDoc */ public function handle($match, $state, $pos, Doku_Handler $handler) { global $auth; $data = array( 'task' => array(), 'state' => $state ); switch ($state) { case DOKU_LEXER_ENTER: $content = trim(substr($match, 3, -1)); // get the assignment date if (preg_match('/\b(\d\d\d\d-\d\d-\d\d)\b/', $content, $grep)) { $data['task']['date'] = $grep[1]; $content = trim(str_replace($data['task']['date'], '', $content)); } // get the assigned users if ($content !== '') { $data['task']['users'] = explode(',', $content); $data['task']['users'] = array_map('trim', $data['task']['users']); if ($auth) { $data['task']['users'] = array_map(array($auth, 'cleanUser'), $data['task']['users']); } $data['task']['users'] = array_unique($data['task']['users']); $data['task']['users'] = array_filter($data['task']['users']); } $ReWriter = new Nest($handler->getCallWriter(), 'plugin_do_do'); $handler->setCallWriter($ReWriter); $handler->addPluginCall('do_do', $data, $state, $pos, $match); break; case DOKU_LEXER_UNMATCHED: $handler->addCall('cdata', array($match), $pos); break; case DOKU_LEXER_EXIT: global $ID; $data['task']['text'] = $this->_textContent( p_render( 'xhtml', array_slice($handler->getCallWriter()->calls, 1), $ignoreme ) ); $data['task']['md5'] = md5( PhpString::strtolower(preg_replace('/\s/', '', $data['task']['text'])) . $ID ); // Add missing data from ENTER and EXIT to the other $handler->getCallWriter()->calls[0][1][1]['task'] += $data['task']; $data['task'] += $handler->getCallWriter()->calls[0][1][1]['task']; $handler->addPluginCall('do_do', $data, $state, $pos, $match); $handler->getCallWriter()->process(); $ReWriter = $handler->getCallWriter(); $handler->setCallWriter($ReWriter->getCallWriter()); } return false; } /** * Return the plain-text content of an html blob, similar to * node.textContent, but trimmed */ protected function _textContent($text) { return trim(html_entity_decode(strip_tags($text), ENT_QUOTES, 'UTF-8')); } /** * Return the task as it was before this rendering run * * @param string $page - the current page * @param string $md5 - the task identifier * * @return array - the task data (empty for new tasks) */ protected function _oldTask($page, $md5) { static $oldTasks = null; // old task cache static $curPage = null; // what page are we working on? // reinit the cache whenever the page changes if ($curPage != $page) { $curPage = $page; $oldTasks = array(); $statuses = $this->hlp->loadTasks(array('id' => $page)); foreach ($statuses as $state) { $oldTasks[$state['md5']] = $state; } } if (empty($oldTasks[$md5])) { return array(); } return (array)$oldTasks[$md5]; } /** * Decide if a task needs to be saved. * * Returns true on the first call, false on subsequent calls * for a given task */ protected function _needsSave($page, $md5) { if (isset($this->saved[$page][$md5])) { return false; } $this->saved[$page][$md5] = 1; return true; } /** @inheritDoc */ public function render($mode, Doku_Renderer $R, $data) { global $ID; // we don't care for QC FIXME we probably should ignore even more renderers if ($mode == 'qc') { return false; } // augment current task with original creator info and old assignees $oldtask = $this->_oldTask($ID, $data['task']['md5']); if ($oldtask) { $data['task']['creator'] = $oldtask['creator']; $data['task']['msg'] = $oldtask['msg']; $data['task']['status'] = $oldtask['status']; } // save data to sqlite during meta data run if ($mode === 'metadata') { $this->_save($data); return true; } // show simple task with status icon for export renderers if ($mode != 'xhtml') { $R->info['cache'] = false; switch ($data['state']) { case DOKU_LEXER_ENTER: $pre = ($oldtask && $oldtask['status']) ? '' : 'un'; $R->externalmedia(DOKU_URL . "lib/plugins/do/pix/${pre}done.png"); break; case DOKU_LEXER_EXIT: if ($data['task']['msg']) { $R->cdata(' (' . $data['task']['msg'] . ')'); } } return true; } // handle XHTML output with status management switch ($data['state']) { case DOKU_LEXER_ENTER: $param = array( 'do' => 'plugin_do', 'do_page' => $ID, 'do_md5' => $data['task']['md5'] ); $id = ''; if (!in_array($data['task']['md5'], $this->ids)) { $id = 'id="plgdo__' . $data['task']['md5'] . '" '; $this->ids[] = $data['task']['md5']; } $pre = ($oldtask && $oldtask['status']) ? '' : 'un'; $R->doc .= '' . ' ' . ' ' . ' ' . ' '; break; case DOKU_LEXER_EXIT: $R->doc .= '' . '' . (empty($data['task']['msg']) ? '' : '(' . $this->getLang('js')['note_done'] . hsc($data['task']['msg']) . ')') . ''; if (isset($data['task']['users']) || isset($data['task']['date'])) { $R->doc .= ' ('; if (isset($data['task']['users'])) { $R->doc .= $this->getLang('user'); $users = $data['task']['users']; $userCount = count($users); for ($i = 0; $i < $userCount; $i++) { $R->doc .= ' ' . $this->hlp->getPrettyUser($users[$i]) . ''; if ($i < $userCount - 1) { $R->doc .= ', '; } } if (isset($data['task']['date'])) { $R->doc .= '. '; } } if (isset($data['task']['date'])) { $R->doc .= $this->getLang('date') . ' ' . hsc($data['task']['date']) . ''; } $R->doc .= ')'; } $R->doc .= ''; break; } return true; } /** * Save data in the metadata renderer * * @param array $data */ protected function _save($data) { global $ID; global $auth; // on the first run for this page, clean up if (!isset($this->run[$ID])) { $this->hlp->cleanPageTasks($ID); $this->run[$ID] = true; } // we save at the end of our instructions if ($data['state'] !== DOKU_LEXER_EXIT) { return; } // did we save already? if (!$this->_needsSave($ID, $data['task']['md5'])) { return; } // make sure data is complete if (!isset($data['task']['creator'])) { $data['task']['creator'] = $_SERVER['REMOTE_USER']; } $data['task']['page'] = $ID; $data['task']['pos'] = ++$this->position; // save it $this->hlp->saveTask($data['task']); // now decide if we should mail anyone if (!$auth) { return; } if (!isset($data['task']['users'])) { return; } if (!$this->getConf('notify_assignee')) { return; } // don't mail current or original editor or old assignees $oldtask = $this->_oldTask($ID, $data['task']['md5']); $receivers = array_diff( $data['task']['users'], (array)$oldtask['users'], array($_SERVER['REMOTE_USER'], $data['task']['creator']) ); // now mail any new assignees if task is still open if (!$data['task']['status']) { $this->hlp->sendMail($receivers, 'open', $data['task'], $data['task']['creator']); } } }