*/
// must be run within Dokuwiki
if (!defined('DOKU_INC')) die();
if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");
if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once DOKU_PLUGIN.'syntax.php';
# Nesting counter, patterns disabled when non-zero
$DOKUTRANSLATE_NEST = 0;
# Generate edit button for paragraph
function parEditButton($parId) {
global $ID;
global $INFO;
$ret = '';
$params = array(
'do' => 'edit',
'rev' => $INFO['lastmod'],
'parid' => $parId,
);
$ret .= '
';
$ret .= html_btn('secedit', $ID, '', $params, 'post');
$ret .= '
';
return $ret;
}
function startEditForm(&$renderer, $erase = true) {
global $DOKUTRANSLATE_EDITFORM;
global $DOKUTRANSLATE_NEST;
global $ACT;
global $TEXT;
# Insert saved edit form
$renderer->doc .= '';
$renderer->doc .= $DOKUTRANSLATE_EDITFORM;
# Render preview from submitted text (the saved page may look different
# if dokutranslate markup is present in the text)
if ($ACT == 'preview') {
$renderer->doc .= p_locale_xhtml('preview');
$DOKUTRANSLATE_NEST++;
$previewIns = p_get_instructions($TEXT);
$DOKUTRANSLATE_NEST--;
$renderer->nest($previewIns);
}
$renderer->doc .= '
';
if ($erase) {
# Insert erasure start marker
$renderer->doc .= '';
}
}
function endEditForm(&$renderer) {
# Insert erasure end marker
$renderer->doc .= '';
}
function loadTranslationMeta($id) {
global $REV;
# Loading meta for current version is simple
if (empty($REV)) {
return unserialize(io_readFile(metaFN($id, '.translate'), false));
}
# Old revision, do it the hard way...
$ret = array();
$meta = unserialize(io_readFile(metaFN($id, '.translateHistory'), false));
$oldrev = intval($REV);
for ($i = 0; $i < count($meta[$oldrev]); $i++) {
$tmp = empty($meta[$oldrev][$i]['changed']) ? $oldrev : $meta[$oldrev][$i]['changed'];
$ret[$i] = $meta[$tmp][$i];
$ret[$i]['changed'] = $tmp;
}
return $ret;
}
function parReviewClass($meta, $parid) {
static $classes = array('mistrans', 'reph', 'incaccept', 'accept');
# No reviews, no class
if (empty($meta[$parid]['reviews'])) {
return '';
}
# Start with max possible value of $clsid
$clsid = count($classes) - 1;
# Find the worst review
foreach ($meta[$parid]['reviews'] as $line) {
$tmp = $line['quality'];
if ($tmp >= count($classes) - 2 && !$line['incomplete']) {
$tmp++;
}
$clsid = $tmp < $clsid ? $tmp : $clsid;
}
return empty($classes[$clsid]) ? '' : $classes[$clsid];
}
class syntax_plugin_dokutranslate extends DokuWiki_Syntax_Plugin {
private $origIns = NULL;
private $meta = NULL;
private $parCounter = 0;
public function getType() {
return 'container';
}
public function getPType() {
return 'stack';
}
public function getSort() {
return 100;
}
public function getAllowedTypes() {
return array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs');
}
public function isSingleton() {
return true;
}
public function connectTo($mode) {
global $DOKUTRANSLATE_NEST;
global $ID;
# Disable patterns when the page is not being translated or
# we're building instructions for original page
if (!@file_exists(metaFN($ID, '.translate')) || $DOKUTRANSLATE_NEST > 0) {
return;
}
$this->Lexer->addEntryPattern('~~DOKUTRANSLATE_START~~(?=.*~~DOKUTRANSLATE_END~~)',$mode,'plugin_dokutranslate');
$this->Lexer->addSpecialPattern('~~DOKUTRANSLATE_PARAGRAPH~~','plugin_dokutranslate','plugin_dokutranslate');
}
public function postConnect() {
global $DOKUTRANSLATE_NEST;
global $ID;
# Disable patterns when the page is not being translated or
# we're building instructions for original page
if (!@file_exists(metaFN($ID, '.translate')) || $DOKUTRANSLATE_NEST > 0) {
return;
}
$this->Lexer->addExitPattern('~~DOKUTRANSLATE_END~~','plugin_dokutranslate');
}
public function handle($match, $state, $pos, Doku_Handler $handler){
switch ($state) {
case DOKU_LEXER_ENTER:
case DOKU_LEXER_EXIT:
case DOKU_LEXER_SPECIAL:
return array($state, $pos, $pos + strlen($match));
}
return array($state, $match);
}
public function render($mode, Doku_Renderer $renderer, $data) {
global $DOKUTRANSLATE_NEST;
global $ID;
global $ACT;
global $TEXT;
global $REV;
# No metadata rendering
if($mode == 'metadata') {
return false;
}
# Allow exporting the page
if (substr($ACT, 0, 7) == 'export_') {
# Ignore plugin-specific markup, just let text through
if ($data[0] != DOKU_LEXER_UNMATCHED) {
return true;
}
$renderer->cdata($data[1]);
return true;
# Not exporting, allow only XHTML
} else if ($mode != 'xhtml') {
return false;
}
# Load instructions for original text on first call
if (is_null($this->origIns)) {
$DOKUTRANSLATE_NEST++;
$this->origIns = getCleanInstructions(dataPath($ID) . '/orig.txt');
$this->meta = loadTranslationMeta($ID);
$this->parCounter = 0;
$DOKUTRANSLATE_NEST--;
}
$parid = getParID();
$edithere = (in_array($ACT, array('edit', 'preview')) && $parid == $this->parCounter);
switch ($data[0]) {
# Open the table
case DOKU_LEXER_ENTER:
$renderer->doc .= '';
$cls = parReviewClass($this->meta, $this->parCounter);
# Start the cell with proper review class
if (empty($cls)) {
$renderer->doc .= '';
} else {
$renderer->doc .= ' | ';
}
# Paragraph anchor (yes, empty named anchor is valid)
$renderer->doc .= "parCounter\">\n";
# Insert edit form if we're editing the first paragraph
if ($edithere) {
startEditForm($renderer);
}
break;
# Dump original text and close the row
case DOKU_LEXER_SPECIAL:
# Generate edit button
if ($ACT == 'show') {
if (empty($REV)) {
$renderer->doc .= parEditButton($this->parCounter);
}
$renderer->doc .= $this->_renderReviews($ID, $this->meta, $this->parCounter);
# Finish erasure if we're editing this paragraph
} else if ($edithere) {
endEditForm($renderer);
$renderer->doc .= $this->_renderReviews($ID, $this->meta, $this->parCounter);
}
$renderer->doc .= " | \n";
if (needsReview($ID, $this->meta, $this->parCounter) || $edithere) {
$renderer->doc .= '';
} else {
$renderer->doc .= ' | ';
}
# If this condition fails, somebody's been messing
# with the data
if (current($this->origIns) !== FALSE) {
$renderer->nest(current($this->origIns));
next($this->origIns);
}
$renderer->doc .= " |
\n";
$this->parCounter++;
$cls = parReviewClass($this->meta, $this->parCounter);
# Start the cell with proper review class
if (empty($cls)) {
$renderer->doc .= '';
} else {
$renderer->doc .= ' | ';
}
# Paragraph anchor (yes, empty named anchor is valid)
$renderer->doc .= "parCounter\">\n";
# Insert edit form if we're editing this paragraph
if (in_array($ACT, array('edit', 'preview')) && getParID() == $this->parCounter) {
startEditForm($renderer);
}
break;
# Dump the rest of the original text and close the table
case DOKU_LEXER_EXIT:
# Generate edit button
if ($ACT == 'show') {
if (empty($REV)) {
$renderer->doc .= parEditButton($this->parCounter);
}
$renderer->doc .= $this->_renderReviews($ID, $this->meta, $this->parCounter);
# Finish erasure if we're editing the last paragraph
} else if (in_array($ACT, array('edit', 'preview'))) {
$parid = getParID();
if ($parid == $this->parCounter) {
endEditForm($renderer);
$renderer->doc .= $this->_renderReviews($ID, $this->meta, $this->parCounter);
# Invalid paragraph ID, show form here
} else if ($parid > $this->parCounter) {
startEditForm($renderer, true);
}
}
$renderer->doc .= " | \n";
if (needsReview($ID, $this->meta, $this->parCounter) || $edithere) {
$renderer->doc .= '';
} else {
$renderer->doc .= ' | ';
}
# Loop to make sure all remaining text gets dumped
# (external edit safety)
while (current($this->origIns) !== FALSE) {
$renderer->nest(current($this->origIns));
next($this->origIns);
}
$renderer->doc .= ' |
';
break;
# Just sanitize and dump the text
default:
$renderer->cdata($data[1]);
break;
}
return true;
}
function _renderReviews($id, $meta, $parid) {
# Check for permission to write reviews
$mod = canReview($id, $meta, $parid);
# No reviews and no moderator privileges => no review block
if (!$mod && empty($meta[$parid]['reviews'])) {
return '';
}
$ret = "\n";
$ret .= '
' . $this->getLang('review_header') . "
\n";
$ret .= "
\n";
$listbox = array(
array('0', $this->getLang('trans_wrong')),
array('1', $this->getLang('trans_rephrase')),
array('2', $this->getLang('trans_accepted'))
);
# Prepare review form for current user
if ($mod) {
if (isset($meta[$parid]['reviews'][$_SERVER['REMOTE_USER']])) {
$myReview = $meta[$parid]['reviews'][$_SERVER['REMOTE_USER']];
} else {
$myReview = array('message' => '', 'quality' => 0, 'incomplete' => false);
}
$form = new Doku_Form(array());
$form->addHidden('parid', strval($parid));
$form->addHidden('do', 'dokutranslate_review');
$form->addElement(form_makeTextField('review', $myReview['message'], $this->getLang('trans_message'), '', 'nowrap', array('size' => '50')));
$form->addElement(form_makeMenuField('quality', $listbox, strval($myReview['quality']), $this->getLang('trans_quality'), '', 'nowrap'));
$args = array();
if ($myReview['incomplete']) {
$args['checked'] = 'checked';
}
$form->addElement(form_makeCheckboxField('incomplete', '1', $this->getLang('trans_incomplete'), '', 'nowrap', $args));
$form->addElement(form_makeButton('submit', '', $this->getLang('add_review')));
}
# Display all reviews for this paragraph
while (list($key, $value) = each($meta[$parid]['reviews'])) {
$ret .= '' . hsc($key) . ' | ';
# Moderators can modify their own review
if ($mod && $key == $_SERVER['REMOTE_USER']) {
$ret .= $form->getForm();
} else {
$ret .= '(' . $listbox[$value['quality']][1];
if ($value['incomplete']) {
$ret .= ', ' . $this->getLang('rend_incomplete');
}
$ret .= ') ';
$ret .= hsc($value['message']);
}
$ret .= " |
\n";
}
# Current user is a moderator who didn't write a review yet,
# display the review form at the end
if ($mod && !isset($meta[$parid]['reviews'][$_SERVER['REMOTE_USER']])) {
if (empty($meta[$parid]['reviews'])) {
$ret .= '';
} else {
$ret .= ' |
';
}
$ret .= $form->getForm();
$ret .= " |
\n";
}
$ret .= "
\n";
return $ret;
}
}
// vim:ts=4:sw=4:et: