diff options
Diffstat (limited to 'inc')
68 files changed, 1738 insertions, 1544 deletions
diff --git a/inc/Action/Resendpwd.php b/inc/Action/Resendpwd.php index f3f8d3bad..dfa4a99d0 100644 --- a/inc/Action/Resendpwd.php +++ b/inc/Action/Resendpwd.php @@ -73,7 +73,7 @@ class Resendpwd extends AbstractAclAction { if($token) { // we're in token phase - get user info from token - $tfile = $conf['cachedir'] . '/' . $token{0} . '/' . $token . '.pwauth'; + $tfile = $conf['cachedir'] . '/' . $token[0] . '/' . $token . '.pwauth'; if(!file_exists($tfile)) { msg($lang['resendpwdbadauth'], -1); $INPUT->remove('pwauth'); @@ -148,7 +148,7 @@ class Resendpwd extends AbstractAclAction { // generate auth token $token = md5(auth_randombytes(16)); // random secret - $tfile = $conf['cachedir'] . '/' . $token{0} . '/' . $token . '.pwauth'; + $tfile = $conf['cachedir'] . '/' . $token[0] . '/' . $token . '.pwauth'; $url = wl('', array('do' => 'resendpwd', 'pwauth' => $token), true, '&'); io_saveFile($tfile, $user); diff --git a/inc/Ajax.php b/inc/Ajax.php index e8cbc84d5..9f4b31668 100644 --- a/inc/Ajax.php +++ b/inc/Ajax.php @@ -257,7 +257,7 @@ class Ajax { global $NS, $MSG, $INPUT; $id = ''; - if($_FILES['qqfile']['tmp_name']) { + if(isset($_FILES['qqfile']['tmp_name'])) { $id = $INPUT->post->str('mediaid', $_FILES['qqfile']['name']); } elseif($INPUT->get->has('qqfile')) { $id = $INPUT->get->str('qqfile'); @@ -273,10 +273,10 @@ class Ajax { io_createNamespace("$ns:xxx", 'media'); } - if($_FILES['qqfile']['error']) unset($_FILES['qqfile']); + if(isset($_FILES['qqfile']['error']) && $_FILES['qqfile']['error']) unset($_FILES['qqfile']); $res = false; - if($_FILES['qqfile']['tmp_name']) $res = media_upload($NS, $AUTH, $_FILES['qqfile']); + if(isset($_FILES['qqfile']['tmp_name'])) $res = media_upload($NS, $AUTH, $_FILES['qqfile']); if($INPUT->get->has('qqfile')) $res = media_upload_xhr($NS, $AUTH); if($res) { diff --git a/inc/Cache/Cache.php b/inc/Cache/Cache.php index 599dc5f59..af82e6bf6 100644 --- a/inc/Cache/Cache.php +++ b/inc/Cache/Cache.php @@ -2,7 +2,7 @@ namespace dokuwiki\Cache; -use \dokuwiki\Debug\PropertyDeprecationHelper; +use dokuwiki\Debug\PropertyDeprecationHelper; use dokuwiki\Extension\Event; /** @@ -78,9 +78,13 @@ class Cache $this->depends = $depends; $this->addDependencies(); - if ($this->_event) { - return $this->stats(Event::createAndTrigger( - $this->_event, $this, array($this, 'makeDefaultCacheDecision')) + if ($this->getEvent()) { + return $this->stats( + Event::createAndTrigger( + $this->getEvent(), + $this, + array($this, 'makeDefaultCacheDecision') + ) ); } @@ -105,7 +109,6 @@ class Cache */ public function makeDefaultCacheDecision() { - if ($this->_nocache) { return false; } // caching turned off @@ -170,7 +173,7 @@ class Cache return false; } - return io_savefile($this->cache, $data); + return io_saveFile($this->cache, $data); } /** diff --git a/inc/Cache/CacheInstructions.php b/inc/Cache/CacheInstructions.php index 3c4786105..acd02abae 100644 --- a/inc/Cache/CacheInstructions.php +++ b/inc/Cache/CacheInstructions.php @@ -41,6 +41,6 @@ class CacheInstructions extends \dokuwiki\Cache\CacheParser return false; } - return io_savefile($this->cache, serialize($instructions)); + return io_saveFile($this->cache, serialize($instructions)); } } diff --git a/inc/Debug/DebugHelper.php b/inc/Debug/DebugHelper.php index 3d4b20d62..74dfa9420 100644 --- a/inc/Debug/DebugHelper.php +++ b/inc/Debug/DebugHelper.php @@ -84,6 +84,48 @@ class DebugHelper } /** + * Trigger a custom deprecation event + * + * Usually dbgDeprecatedFunction() or dbgDeprecatedProperty() should be used instead. + * This method is intended only for those situation where they are not applicable. + * + * @param string $alternative + * @param string $deprecatedThing + * @param string $caller + * @param string $file + * @param int $line + * @param int $callerOffset How many lines should be removed from the beginning of the backtrace + */ + public static function dbgCustomDeprecationEvent( + $alternative, + $deprecatedThing, + $caller, + $file, + $line, + $callerOffset = 1 + ) { + global $conf; + /** @var EventHandler $EVENT_HANDLER */ + global $EVENT_HANDLER; + if (!$conf['allowdebug'] && !$EVENT_HANDLER->hasHandlerForEvent(self::INFO_DEPRECATION_LOG_EVENT)) { + // avoid any work if no one cares + return; + } + + $backtrace = array_slice(debug_backtrace(), $callerOffset); + + self::triggerDeprecationEvent( + $backtrace, + $alternative, + $deprecatedThing, + $caller, + $file, + $line + ); + + } + + /** * @param array $backtrace * @param string $alternative * @param string $deprecatedThing diff --git a/inc/Extension/Event.php b/inc/Extension/Event.php index bbaa52e55..32f346c72 100644 --- a/inc/Extension/Event.php +++ b/inc/Extension/Event.php @@ -126,12 +126,7 @@ class Event } if ($this->advise_before($enablePrevent) && is_callable($action)) { - if (is_array($action)) { - list($obj, $method) = $action; - $this->result = $obj->$method($this->data); - } else { - $this->result = $action($this->data); - } + $this->result = call_user_func_array($action, [&$this->data]); } $this->advise_after(); diff --git a/inc/Extension/SyntaxPlugin.php b/inc/Extension/SyntaxPlugin.php index e5dda9bdc..ea8f51b4d 100644 --- a/inc/Extension/SyntaxPlugin.php +++ b/inc/Extension/SyntaxPlugin.php @@ -2,8 +2,8 @@ namespace dokuwiki\Extension; -use \Doku_Handler; -use \Doku_Renderer; +use Doku_Handler; +use Doku_Renderer; /** * Syntax Plugin Prototype diff --git a/inc/Form/LegacyForm.php b/inc/Form/LegacyForm.php index 1b47ba204..b30c8df9a 100644 --- a/inc/Form/LegacyForm.php +++ b/inc/Form/LegacyForm.php @@ -116,7 +116,7 @@ class LegacyForm extends Form { $control = array(); foreach($legacy as $key => $val) { - if($key{0} == '_') { + if($key[0] == '_') { $control[substr($key, 1)] = $val; } elseif($key == 'name') { $control[$key] = $val; diff --git a/inc/IXR_Library.php b/inc/IXR_Library.php index e671b6880..bb1655f10 100644 --- a/inc/IXR_Library.php +++ b/inc/IXR_Library.php @@ -822,6 +822,7 @@ EOD; */ class IXR_Date { + const XMLRPC_ISO8601 = "Ymd\TH:i:sO" ; /** @var DateTime */ protected $date; @@ -861,7 +862,7 @@ class IXR_Date { * @return string */ public function getIso() { - return $this->date->format(DateTime::ISO8601); + return $this->date->format(self::XMLRPC_ISO8601); } /** diff --git a/inc/JpegMeta.php b/inc/JpegMeta.php index b11f07145..9ed1e2db4 100644 --- a/inc/JpegMeta.php +++ b/inc/JpegMeta.php @@ -414,7 +414,7 @@ class JpegMeta { // make sure datetimes are in correct format if(strlen($field) >= 8 && strtolower(substr($field, 0, 8)) == 'datetime') { - if(strlen($value) < 8 || $value{4} != ':' || $value{7} != ':') { + if(strlen($value) < 8 || $value[4] != ':' || $value[7] != ':') { $value = date('Y:m:d H:i:s', strtotime($value)); } } @@ -649,8 +649,8 @@ class JpegMeta { $dates['ExifDateTime'] = $this->_info['exif']['DateTime']; $aux = $this->_info['exif']['DateTime']; - $aux{4} = "-"; - $aux{7} = "-"; + $aux[4] = "-"; + $aux[7] = "-"; $t = strtotime($aux); if ($t && $t > $latestTime) { @@ -668,8 +668,8 @@ class JpegMeta { $dates['ExifDateTimeOriginal'] = $this->_info['exif']['DateTime']; $aux = $this->_info['exif']['DateTimeOriginal']; - $aux{4} = "-"; - $aux{7} = "-"; + $aux[4] = "-"; + $aux[7] = "-"; $t = strtotime($aux); if ($t && $t > $latestTime) { @@ -687,8 +687,8 @@ class JpegMeta { $dates['ExifDateTimeDigitized'] = $this->_info['exif']['DateTime']; $aux = $this->_info['exif']['DateTimeDigitized']; - $aux{4} = "-"; - $aux{7} = "-"; + $aux[4] = "-"; + $aux[7] = "-"; $t = strtotime($aux); if ($t && $t > $latestTime) { @@ -2402,7 +2402,7 @@ class JpegMeta { $pos += 1; $header = ''; for ($i = 0; $i < $strlen; $i++) { - $header .= $data{$pos + $i}; + $header .= $data[$pos + $i]; } $pos += $strlen + 1 - ($strlen % 2); // The string is padded to even length, counting the length byte itself @@ -2983,7 +2983,7 @@ class JpegMeta { * @return int */ function _getByte(&$data, $pos) { - return ord($data{$pos}); + return ord($data[$pos]); } /*************************************************************/ @@ -2999,7 +2999,7 @@ class JpegMeta { function _putByte(&$data, $pos, $val) { $val = intval($val); - $data{$pos} = chr($val); + $data[$pos] = chr($val); return $pos + 1; } @@ -3007,11 +3007,11 @@ class JpegMeta { /*************************************************************/ function _getShort(&$data, $pos, $bigEndian = true) { if ($bigEndian) { - return (ord($data{$pos}) << 8) - + ord($data{$pos + 1}); + return (ord($data[$pos]) << 8) + + ord($data[$pos + 1]); } else { - return ord($data{$pos}) - + (ord($data{$pos + 1}) << 8); + return ord($data[$pos]) + + (ord($data[$pos + 1]) << 8); } } @@ -3020,11 +3020,11 @@ class JpegMeta { $val = intval($val); if ($bigEndian) { - $data{$pos + 0} = chr(($val & 0x0000FF00) >> 8); - $data{$pos + 1} = chr(($val & 0x000000FF) >> 0); + $data[$pos + 0] = chr(($val & 0x0000FF00) >> 8); + $data[$pos + 1] = chr(($val & 0x000000FF) >> 0); } else { - $data{$pos + 0} = chr(($val & 0x00FF) >> 0); - $data{$pos + 1} = chr(($val & 0xFF00) >> 8); + $data[$pos + 0] = chr(($val & 0x00FF) >> 0); + $data[$pos + 1] = chr(($val & 0xFF00) >> 8); } return $pos + 2; @@ -3042,15 +3042,15 @@ class JpegMeta { */ function _getLong(&$data, $pos, $bigEndian = true) { if ($bigEndian) { - return (ord($data{$pos}) << 24) - + (ord($data{$pos + 1}) << 16) - + (ord($data{$pos + 2}) << 8) - + ord($data{$pos + 3}); + return (ord($data[$pos]) << 24) + + (ord($data[$pos + 1]) << 16) + + (ord($data[$pos + 2]) << 8) + + ord($data[$pos + 3]); } else { - return ord($data{$pos}) - + (ord($data{$pos + 1}) << 8) - + (ord($data{$pos + 2}) << 16) - + (ord($data{$pos + 3}) << 24); + return ord($data[$pos]) + + (ord($data[$pos + 1]) << 8) + + (ord($data[$pos + 2]) << 16) + + (ord($data[$pos + 3]) << 24); } } @@ -3069,15 +3069,15 @@ class JpegMeta { $val = intval($val); if ($bigEndian) { - $data{$pos + 0} = chr(($val & 0xFF000000) >> 24); - $data{$pos + 1} = chr(($val & 0x00FF0000) >> 16); - $data{$pos + 2} = chr(($val & 0x0000FF00) >> 8); - $data{$pos + 3} = chr(($val & 0x000000FF) >> 0); + $data[$pos + 0] = chr(($val & 0xFF000000) >> 24); + $data[$pos + 1] = chr(($val & 0x00FF0000) >> 16); + $data[$pos + 2] = chr(($val & 0x0000FF00) >> 8); + $data[$pos + 3] = chr(($val & 0x000000FF) >> 0); } else { - $data{$pos + 0} = chr(($val & 0x000000FF) >> 0); - $data{$pos + 1} = chr(($val & 0x0000FF00) >> 8); - $data{$pos + 2} = chr(($val & 0x00FF0000) >> 16); - $data{$pos + 3} = chr(($val & 0xFF000000) >> 24); + $data[$pos + 0] = chr(($val & 0x000000FF) >> 0); + $data[$pos + 1] = chr(($val & 0x0000FF00) >> 8); + $data[$pos + 2] = chr(($val & 0x00FF0000) >> 16); + $data[$pos + 3] = chr(($val & 0xFF000000) >> 24); } return $pos + 4; @@ -3089,10 +3089,10 @@ class JpegMeta { $max = strlen($data); while ($pos < $max) { - if (ord($data{$pos}) == 0) { + if (ord($data[$pos]) == 0) { return $str; } else { - $str .= $data{$pos}; + $str .= $data[$pos]; } $pos++; } @@ -3114,7 +3114,7 @@ class JpegMeta { function _putString(&$data, $pos, &$str) { $len = strlen($str); for ($i = 0; $i < $len; $i++) { - $data{$pos + $i} = $str{$i}; + $data[$pos + $i] = $str[$i]; } return $pos + $len; @@ -3138,7 +3138,7 @@ class JpegMeta { echo sprintf('%04d', $count) . ': '; } - $c = ord($data{$start}); + $c = ord($data[$start]); $count++; $start++; diff --git a/inc/Mailer.class.php b/inc/Mailer.class.php index 328073bc1..db9a2bdc2 100644 --- a/inc/Mailer.class.php +++ b/inc/Mailer.class.php @@ -350,7 +350,7 @@ class Mailer { * addresses. Addresses must be separated by a comma. If the display * name includes a comma then it MUST be properly enclosed by '"' to * prevent spliting at the wrong point. - * + * * Example: * cc("föö <foo@bar.com>, me@somewhere.com","TBcc"); * to("foo, Dr." <foo@bar.com>, me@somewhere.com"); @@ -635,6 +635,8 @@ class Mailer { $ip = clientIP(); $cip = gethostsbyaddrs($ip); + $name = isset($INFO) ? $INFO['userinfo']['name'] : ''; + $mail = isset($INFO) ? $INFO['userinfo']['mail'] : ''; $this->replacements['text'] = array( 'DATE' => dformat(), @@ -644,8 +646,8 @@ class Mailer { 'TITLE' => $conf['title'], 'DOKUWIKIURL' => DOKU_URL, 'USER' => $INPUT->server->str('REMOTE_USER'), - 'NAME' => $INFO['userinfo']['name'], - 'MAIL' => $INFO['userinfo']['mail'] + 'NAME' => $name, + 'MAIL' => $mail ); $signature = str_replace( '@DOKUWIKIURL@', @@ -662,9 +664,9 @@ class Mailer { 'TITLE' => hsc($conf['title']), 'DOKUWIKIURL' => '<a href="' . DOKU_URL . '">' . DOKU_URL . '</a>', 'USER' => hsc($INPUT->server->str('REMOTE_USER')), - 'NAME' => hsc($INFO['userinfo']['name']), - 'MAIL' => '<a href="mailto:"' . hsc($INFO['userinfo']['mail']) . '">' . - hsc($INFO['userinfo']['mail']) . '</a>' + 'NAME' => hsc($name), + 'MAIL' => '<a href="mailto:"' . hsc($mail) . '">' . + hsc($mail) . '</a>' ); $signature = $lang['email_signature_text']; if(!empty($lang['email_signature_html'])) { diff --git a/inc/Menu/Item/AbstractItem.php b/inc/Menu/Item/AbstractItem.php index 45ead5562..c6b04bfd3 100644 --- a/inc/Menu/Item/AbstractItem.php +++ b/inc/Menu/Item/AbstractItem.php @@ -114,7 +114,7 @@ abstract class AbstractItem { * @return string */ public function getLink() { - if($this->id[0] == '#') { + if($this->id && $this->id[0] == '#') { return $this->id; } else { return wl($this->id, $this->params, false, '&'); diff --git a/inc/Parsing/Parser.php b/inc/Parsing/Parser.php index b2070569f..63f014161 100644 --- a/inc/Parsing/Parser.php +++ b/inc/Parsing/Parser.php @@ -107,7 +107,21 @@ class Parser { // Normalize CRs and pad doc $doc = "\n" . str_replace("\r\n", "\n", $doc) . "\n"; $this->lexer->parse($doc); - $this->handler->finalize(); + + if (!method_exists($this->handler, 'finalize')) { + /** @deprecated 2019-10 we have a legacy handler from a plugin, assume legacy _finalize exists */ + + \dokuwiki\Debug\DebugHelper::dbgCustomDeprecationEvent( + 'finalize()', + get_class($this->handler) . '::_finalize()', + __METHOD__, + __FILE__, + __LINE__ + ); + $this->handler->_finalize(); + } else { + $this->handler->finalize(); + } return $this->handler->calls; } diff --git a/inc/PassHash.php b/inc/PassHash.php index 8ac623cd1..c0e61f409 100644 --- a/inc/PassHash.php +++ b/inc/PassHash.php @@ -77,6 +77,11 @@ class PassHash { $method = 'sha512'; $salt = $m[2]; $magic = $m[1]; + } elseif(preg_match('/^\$(argon2id?)/', $hash, $m)) { + if(!defined('PASSWORD_'.strtoupper($m[1]))) { + throw new \Exception('This PHP installation has no '.strtoupper($m[1]).' support'); + } + return password_verify($clear,$hash); } elseif($len == 32) { $method = 'md5'; } elseif($len == 40) { @@ -206,7 +211,7 @@ class PassHash { $text .= substr($bin, 0, min(16, $i)); } for($i = $len; $i > 0; $i >>= 1) { - $text .= ($i & 1) ? chr(0) : $clear{0}; + $text .= ($i & 1) ? chr(0) : $clear[0]; } $bin = pack("H32", md5($text)); for($i = 0; $i < 1000; $i++) { @@ -591,6 +596,43 @@ class PassHash { return ':B:'.$salt.':'.md5($salt.'-'.md5($clear)); } + + /** + * Password hashing method 'argon2i' + * + * Uses php's own password_hash function to create argon2i password hash + * Default Cost and thread options are used for now. + * + * @link https://www.php.net/manual/de/function.password-hash.php + * + * @param string $clear The clear text to hash + * @return string Hashed password + */ + public function hash_argon2i($clear) { + if(!defined('PASSWORD_ARGON2I')) { + throw new \Exception('This PHP installation has no ARGON2I support'); + } + return password_hash($clear,PASSWORD_ARGON2I); + } + + /** + * Password hashing method 'argon2id' + * + * Uses php's own password_hash function to create argon2id password hash + * Default Cost and thread options are used for now. + * + * @link https://www.php.net/manual/de/function.password-hash.php + * + * @param string $clear The clear text to hash + * @return string Hashed password + */ + public function hash_argon2id($clear) { + if(!defined('PASSWORD_ARGON2ID')) { + throw new \Exception('This PHP installation has no ARGON2ID support'); + } + return password_hash($clear,PASSWORD_ARGON2ID); + } + /** * Wraps around native hash_hmac() or reimplents it * diff --git a/inc/Remote/ApiCore.php b/inc/Remote/ApiCore.php index 9a041452d..12a32b8a4 100644 --- a/inc/Remote/ApiCore.php +++ b/inc/Remote/ApiCore.php @@ -489,7 +489,7 @@ class ApiCore $data = array( 'name' => $id, 'lastModified' => $this->api->toDate($rev), - 'author' => (($info['user']) ? $info['user'] : $info['ip']), + 'author' => is_array($info) ? (($info['user']) ? $info['user'] : $info['ip']) : null, 'version' => $rev ); diff --git a/inc/Remote/XmlRpcServer.php b/inc/Remote/XmlRpcServer.php index 1b0097856..0a16af1cc 100644 --- a/inc/Remote/XmlRpcServer.php +++ b/inc/Remote/XmlRpcServer.php @@ -12,12 +12,12 @@ class XmlRpcServer extends \IXR_Server /** * Constructor. Register methods and run Server */ - public function __construct() + public function __construct($wait=false) { $this->remote = new Api(); $this->remote->setDateTransformation(array($this, 'toDate')); $this->remote->setFileTransformation(array($this, 'toFile')); - parent::__construct(); + parent::__construct(false, false, $wait); } /** diff --git a/inc/Search/Indexer.php b/inc/Search/Indexer.php new file mode 100644 index 000000000..a29e5b28b --- /dev/null +++ b/inc/Search/Indexer.php @@ -0,0 +1,1214 @@ +<?php + +namespace dokuwiki\Search; + +use dokuwiki\Extension\Event; + +/** + * Class that encapsulates operations on the indexer database. + * + * @author Tom N Harris <tnharris@whoopdedo.org> + */ +class Indexer { + /** + * @var array $pidCache Cache for getPID() + */ + protected $pidCache = array(); + + /** + * Adds the contents of a page to the fulltext index + * + * The added text replaces previous words for the same page. + * An empty value erases the page. + * + * @param string $page a page name + * @param string $text the body of the page + * @return string|boolean the function completed successfully + * + * @author Tom N Harris <tnharris@whoopdedo.org> + * @author Andreas Gohr <andi@splitbrain.org> + */ + public function addPageWords($page, $text) { + if (!$this->lock()) + return "locked"; + + // load known documents + $pid = $this->getPIDNoLock($page); + if ($pid === false) { + $this->unlock(); + return false; + } + + $pagewords = array(); + // get word usage in page + $words = $this->getPageWords($text); + if ($words === false) { + $this->unlock(); + return false; + } + + if (!empty($words)) { + foreach (array_keys($words) as $wlen) { + $index = $this->getIndex('i', $wlen); + foreach ($words[$wlen] as $wid => $freq) { + $idx = ($wid<count($index)) ? $index[$wid] : ''; + $index[$wid] = $this->updateTuple($idx, $pid, $freq); + $pagewords[] = "$wlen*$wid"; + } + if (!$this->saveIndex('i', $wlen, $index)) { + $this->unlock(); + return false; + } + } + } + + // Remove obsolete index entries + $pageword_idx = $this->getIndexKey('pageword', '', $pid); + if ($pageword_idx !== '') { + $oldwords = explode(':',$pageword_idx); + $delwords = array_diff($oldwords, $pagewords); + $upwords = array(); + foreach ($delwords as $word) { + if ($word != '') { + list($wlen, $wid) = explode('*', $word); + $wid = (int)$wid; + $upwords[$wlen][] = $wid; + } + } + foreach ($upwords as $wlen => $widx) { + $index = $this->getIndex('i', $wlen); + foreach ($widx as $wid) { + $index[$wid] = $this->updateTuple($index[$wid], $pid, 0); + } + $this->saveIndex('i', $wlen, $index); + } + } + // Save the reverse index + $pageword_idx = join(':', $pagewords); + if (!$this->saveIndexKey('pageword', '', $pid, $pageword_idx)) { + $this->unlock(); + return false; + } + + $this->unlock(); + return true; + } + + /** + * Split the words in a page and add them to the index. + * + * @param string $text content of the page + * @return array list of word IDs and number of times used + * + * @author Andreas Gohr <andi@splitbrain.org> + * @author Christopher Smith <chris@jalakai.co.uk> + * @author Tom N Harris <tnharris@whoopdedo.org> + */ + protected function getPageWords($text) { + + $tokens = $this->tokenizer($text); + $tokens = array_count_values($tokens); // count the frequency of each token + + $words = array(); + foreach ($tokens as $w=>$c) { + $l = wordlen($w); + if (isset($words[$l])){ + $words[$l][$w] = $c + (isset($words[$l][$w]) ? $words[$l][$w] : 0); + }else{ + $words[$l] = array($w => $c); + } + } + + // arrive here with $words = array(wordlen => array(word => frequency)) + $word_idx_modified = false; + $index = array(); //resulting index + foreach (array_keys($words) as $wlen) { + $word_idx = $this->getIndex('w', $wlen); + foreach ($words[$wlen] as $word => $freq) { + $word = (string)$word; + $wid = array_search($word, $word_idx, true); + if ($wid === false) { + $wid = count($word_idx); + $word_idx[] = $word; + $word_idx_modified = true; + } + if (!isset($index[$wlen])) + $index[$wlen] = array(); + $index[$wlen][$wid] = $freq; + } + // save back the word index + if ($word_idx_modified && !$this->saveIndex('w', $wlen, $word_idx)) + return false; + } + + return $index; + } + + /** + * Add/update keys to/of the metadata index. + * + * Adding new keys does not remove other keys for the page. + * An empty value will erase the key. + * The $key parameter can be an array to add multiple keys. $value will + * not be used if $key is an array. + * + * @param string $page a page name + * @param mixed $key a key string or array of key=>value pairs + * @param mixed $value the value or list of values + * @return boolean|string the function completed successfully + * + * @author Tom N Harris <tnharris@whoopdedo.org> + * @author Michael Hamann <michael@content-space.de> + */ + public function addMetaKeys($page, $key, $value=null) { + if (!is_array($key)) { + $key = array($key => $value); + } elseif (!is_null($value)) { + // $key is array, but $value is not null + trigger_error("array passed to addMetaKeys but value is not null", E_USER_WARNING); + } + + if (!$this->lock()) + return "locked"; + + // load known documents + $pid = $this->getPIDNoLock($page); + if ($pid === false) { + $this->unlock(); + return false; + } + + // Special handling for titles so the index file is simpler + if (array_key_exists('title', $key)) { + $value = $key['title']; + if (is_array($value)) { + $value = $value[0]; + } + $this->saveIndexKey('title', '', $pid, $value); + unset($key['title']); + } + + foreach ($key as $name => $values) { + $metaname = idx_cleanName($name); + $this->addIndexKey('metadata', '', $metaname); + $metaidx = $this->getIndex($metaname.'_i', ''); + $metawords = $this->getIndex($metaname.'_w', ''); + $addwords = false; + + if (!is_array($values)) $values = array($values); + + $val_idx = $this->getIndexKey($metaname.'_p', '', $pid); + if ($val_idx !== '') { + $val_idx = explode(':', $val_idx); + // -1 means remove, 0 keep, 1 add + $val_idx = array_combine($val_idx, array_fill(0, count($val_idx), -1)); + } else { + $val_idx = array(); + } + + foreach ($values as $val) { + $val = (string)$val; + if ($val !== "") { + $id = array_search($val, $metawords, true); + if ($id === false) { + // didn't find $val, so we'll add it to the end of metawords and create a placeholder in metaidx + $id = count($metawords); + $metawords[$id] = $val; + $metaidx[$id] = ''; + $addwords = true; + } + // test if value is already in the index + if (isset($val_idx[$id]) && $val_idx[$id] <= 0){ + $val_idx[$id] = 0; + } else { // else add it + $val_idx[$id] = 1; + } + } + } + + if ($addwords) { + $this->saveIndex($metaname.'_w', '', $metawords); + } + $vals_changed = false; + foreach ($val_idx as $id => $action) { + if ($action == -1) { + $metaidx[$id] = $this->updateTuple($metaidx[$id], $pid, 0); + $vals_changed = true; + unset($val_idx[$id]); + } elseif ($action == 1) { + $metaidx[$id] = $this->updateTuple($metaidx[$id], $pid, 1); + $vals_changed = true; + } + } + + if ($vals_changed) { + $this->saveIndex($metaname.'_i', '', $metaidx); + $val_idx = implode(':', array_keys($val_idx)); + $this->saveIndexKey($metaname.'_p', '', $pid, $val_idx); + } + + unset($metaidx); + unset($metawords); + } + + $this->unlock(); + return true; + } + + /** + * Rename a page in the search index without changing the indexed content. This function doesn't check if the + * old or new name exists in the filesystem. It returns an error if the old page isn't in the page list of the + * indexer and it deletes all previously indexed content of the new page. + * + * @param string $oldpage The old page name + * @param string $newpage The new page name + * @return string|bool If the page was successfully renamed, can be a message in the case of an error + */ + public function renamePage($oldpage, $newpage) { + if (!$this->lock()) return 'locked'; + + $pages = $this->getPages(); + + $id = array_search($oldpage, $pages, true); + if ($id === false) { + $this->unlock(); + return 'page is not in index'; + } + + $new_id = array_search($newpage, $pages, true); + if ($new_id !== false) { + // make sure the page is not in the index anymore + if ($this->deletePageNoLock($newpage) !== true) { + return false; + } + + $pages[$new_id] = 'deleted:'.time().rand(0, 9999); + } + + $pages[$id] = $newpage; + + // update index + if (!$this->saveIndex('page', '', $pages)) { + $this->unlock(); + return false; + } + + // reset the pid cache + $this->pidCache = array(); + + $this->unlock(); + return true; + } + + /** + * Renames a meta value in the index. This doesn't change the meta value in the pages, it assumes that all pages + * will be updated. + * + * @param string $key The metadata key of which a value shall be changed + * @param string $oldvalue The old value that shall be renamed + * @param string $newvalue The new value to which the old value shall be renamed, if exists values will be merged + * @return bool|string If renaming the value has been successful, false or error message on error. + */ + public function renameMetaValue($key, $oldvalue, $newvalue) { + if (!$this->lock()) return 'locked'; + + // change the relation references index + $metavalues = $this->getIndex($key, '_w'); + $oldid = array_search($oldvalue, $metavalues, true); + if ($oldid !== false) { + $newid = array_search($newvalue, $metavalues, true); + if ($newid !== false) { + // free memory + unset ($metavalues); + + // okay, now we have two entries for the same value. we need to merge them. + $indexline = $this->getIndexKey($key.'_i', '', $oldid); + if ($indexline != '') { + $newindexline = $this->getIndexKey($key.'_i', '', $newid); + $pagekeys = $this->getIndex($key.'_p', ''); + $parts = explode(':', $indexline); + foreach ($parts as $part) { + list($id, $count) = explode('*', $part); + $newindexline = $this->updateTuple($newindexline, $id, $count); + + $keyline = explode(':', $pagekeys[$id]); + // remove old meta value + $keyline = array_diff($keyline, array($oldid)); + // add new meta value when not already present + if (!in_array($newid, $keyline)) { + array_push($keyline, $newid); + } + $pagekeys[$id] = implode(':', $keyline); + } + $this->saveIndex($key.'_p', '', $pagekeys); + unset($pagekeys); + $this->saveIndexKey($key.'_i', '', $oldid, ''); + $this->saveIndexKey($key.'_i', '', $newid, $newindexline); + } + } else { + $metavalues[$oldid] = $newvalue; + if (!$this->saveIndex($key.'_w', '', $metavalues)) { + $this->unlock(); + return false; + } + } + } + + $this->unlock(); + return true; + } + + /** + * Remove a page from the index + * + * Erases entries in all known indexes. + * + * @param string $page a page name + * @return string|boolean the function completed successfully + * + * @author Tom N Harris <tnharris@whoopdedo.org> + */ + public function deletePage($page) { + if (!$this->lock()) + return "locked"; + + $result = $this->deletePageNoLock($page); + + $this->unlock(); + + return $result; + } + + /** + * Remove a page from the index without locking the index, only use this function if the index is already locked + * + * Erases entries in all known indexes. + * + * @param string $page a page name + * @return boolean the function completed successfully + * + * @author Tom N Harris <tnharris@whoopdedo.org> + */ + protected function deletePageNoLock($page) { + // load known documents + $pid = $this->getPIDNoLock($page); + if ($pid === false) { + return false; + } + + // Remove obsolete index entries + $pageword_idx = $this->getIndexKey('pageword', '', $pid); + if ($pageword_idx !== '') { + $delwords = explode(':',$pageword_idx); + $upwords = array(); + foreach ($delwords as $word) { + if ($word != '') { + list($wlen,$wid) = explode('*', $word); + $wid = (int)$wid; + $upwords[$wlen][] = $wid; + } + } + foreach ($upwords as $wlen => $widx) { + $index = $this->getIndex('i', $wlen); + foreach ($widx as $wid) { + $index[$wid] = $this->updateTuple($index[$wid], $pid, 0); + } + $this->saveIndex('i', $wlen, $index); + } + } + // Save the reverse index + if (!$this->saveIndexKey('pageword', '', $pid, "")) { + return false; + } + + $this->saveIndexKey('title', '', $pid, ""); + $keyidx = $this->getIndex('metadata', ''); + foreach ($keyidx as $metaname) { + $val_idx = explode(':', $this->getIndexKey($metaname.'_p', '', $pid)); + $meta_idx = $this->getIndex($metaname.'_i', ''); + foreach ($val_idx as $id) { + if ($id === '') continue; + $meta_idx[$id] = $this->updateTuple($meta_idx[$id], $pid, 0); + } + $this->saveIndex($metaname.'_i', '', $meta_idx); + $this->saveIndexKey($metaname.'_p', '', $pid, ''); + } + + return true; + } + + /** + * Clear the whole index + * + * @return bool If the index has been cleared successfully + */ + public function clear() { + global $conf; + + if (!$this->lock()) return false; + + @unlink($conf['indexdir'].'/page.idx'); + @unlink($conf['indexdir'].'/title.idx'); + @unlink($conf['indexdir'].'/pageword.idx'); + @unlink($conf['indexdir'].'/metadata.idx'); + $dir = @opendir($conf['indexdir']); + if($dir!==false){ + while(($f = readdir($dir)) !== false){ + if(substr($f,-4)=='.idx' && + (substr($f,0,1)=='i' || substr($f,0,1)=='w' + || substr($f,-6)=='_w.idx' || substr($f,-6)=='_i.idx' || substr($f,-6)=='_p.idx')) + @unlink($conf['indexdir']."/$f"); + } + } + @unlink($conf['indexdir'].'/lengths.idx'); + + // clear the pid cache + $this->pidCache = array(); + + $this->unlock(); + return true; + } + + /** + * Split the text into words for fulltext search + * + * TODO: does this also need &$stopwords ? + * + * @triggers INDEXER_TEXT_PREPARE + * This event allows plugins to modify the text before it gets tokenized. + * Plugins intercepting this event should also intercept INDEX_VERSION_GET + * + * @param string $text plain text + * @param boolean $wc are wildcards allowed? + * @return array list of words in the text + * + * @author Tom N Harris <tnharris@whoopdedo.org> + * @author Andreas Gohr <andi@splitbrain.org> + */ + public function tokenizer($text, $wc=false) { + $wc = ($wc) ? '' : '\*'; + $stopwords =& idx_get_stopwords(); + + // prepare the text to be tokenized + $evt = new Event('INDEXER_TEXT_PREPARE', $text); + if ($evt->advise_before(true)) { + if (preg_match('/[^0-9A-Za-z ]/u', $text)) { + $text = \dokuwiki\Utf8\Asian::separateAsianWords($text); + } + } + $evt->advise_after(); + unset($evt); + + $text = strtr($text, + array( + "\r" => ' ', + "\n" => ' ', + "\t" => ' ', + "\xC2\xAD" => '', //soft-hyphen + ) + ); + if (preg_match('/[^0-9A-Za-z ]/u', $text)) + $text = \dokuwiki\Utf8\Clean::stripspecials($text, ' ', '\._\-:'.$wc); + + $wordlist = explode(' ', $text); + foreach ($wordlist as $i => $word) { + $wordlist[$i] = (preg_match('/[^0-9A-Za-z]/u', $word)) ? + \dokuwiki\Utf8\PhpString::strtolower($word) : strtolower($word); + } + + foreach ($wordlist as $i => $word) { + if ((!is_numeric($word) && strlen($word) < IDX_MINWORDLENGTH) + || array_search($word, $stopwords, true) !== false) + unset($wordlist[$i]); + } + return array_values($wordlist); + } + + /** + * Get the numeric PID of a page + * + * @param string $page The page to get the PID for + * @return bool|int The page id on success, false on error + */ + public function getPID($page) { + // return PID without locking when it is in the cache + if (isset($this->pidCache[$page])) return $this->pidCache[$page]; + + if (!$this->lock()) + return false; + + // load known documents + $pid = $this->getPIDNoLock($page); + if ($pid === false) { + $this->unlock(); + return false; + } + + $this->unlock(); + return $pid; + } + + /** + * Get the numeric PID of a page without locking the index. + * Only use this function when the index is already locked. + * + * @param string $page The page to get the PID for + * @return bool|int The page id on success, false on error + */ + protected function getPIDNoLock($page) { + // avoid expensive addIndexKey operation for the most recently requested pages by using a cache + if (isset($this->pidCache[$page])) return $this->pidCache[$page]; + $pid = $this->addIndexKey('page', '', $page); + // limit cache to 10 entries by discarding the oldest element as in DokuWiki usually only the most recently + // added item will be requested again + if (count($this->pidCache) > 10) array_shift($this->pidCache); + $this->pidCache[$page] = $pid; + return $pid; + } + + /** + * Get the page id of a numeric PID + * + * @param int $pid The PID to get the page id for + * @return string The page id + */ + public function getPageFromPID($pid) { + return $this->getIndexKey('page', '', $pid); + } + + /** + * Find pages in the fulltext index containing the words, + * + * The search words must be pre-tokenized, meaning only letters and + * numbers with an optional wildcard + * + * The returned array will have the original tokens as key. The values + * in the returned list is an array with the page names as keys and the + * number of times that token appears on the page as value. + * + * @param array $tokens list of words to search for + * @return array list of page names with usage counts + * + * @author Tom N Harris <tnharris@whoopdedo.org> + * @author Andreas Gohr <andi@splitbrain.org> + */ + public function lookup(&$tokens) { + $result = array(); + $wids = $this->getIndexWords($tokens, $result); + if (empty($wids)) return array(); + // load known words and documents + $page_idx = $this->getIndex('page', ''); + $docs = array(); + foreach (array_keys($wids) as $wlen) { + $wids[$wlen] = array_unique($wids[$wlen]); + $index = $this->getIndex('i', $wlen); + foreach($wids[$wlen] as $ixid) { + if ($ixid < count($index)) + $docs["$wlen*$ixid"] = $this->parseTuples($page_idx, $index[$ixid]); + } + } + // merge found pages into final result array + $final = array(); + foreach ($result as $word => $res) { + $final[$word] = array(); + foreach ($res as $wid) { + // handle the case when ($ixid < count($index)) has been false + // and thus $docs[$wid] hasn't been set. + if (!isset($docs[$wid])) continue; + $hits = &$docs[$wid]; + foreach ($hits as $hitkey => $hitcnt) { + // make sure the document still exists + if (!page_exists($hitkey, '', false)) continue; + if (!isset($final[$word][$hitkey])) + $final[$word][$hitkey] = $hitcnt; + else + $final[$word][$hitkey] += $hitcnt; + } + } + } + return $final; + } + + /** + * Find pages containing a metadata key. + * + * The metadata values are compared as case-sensitive strings. Pass a + * callback function that returns true or false to use a different + * comparison function. The function will be called with the $value being + * searched for as the first argument, and the word in the index as the + * second argument. The function preg_match can be used directly if the + * values are regexes. + * + * @param string $key name of the metadata key to look for + * @param string $value search term to look for, must be a string or array of strings + * @param callback $func comparison function + * @return array lists with page names, keys are query values if $value is array + * + * @author Tom N Harris <tnharris@whoopdedo.org> + * @author Michael Hamann <michael@content-space.de> + */ + public function lookupKey($key, &$value, $func=null) { + if (!is_array($value)) + $value_array = array($value); + else + $value_array =& $value; + + // the matching ids for the provided value(s) + $value_ids = array(); + + $metaname = idx_cleanName($key); + + // get all words in order to search the matching ids + if ($key == 'title') { + $words = $this->getIndex('title', ''); + } else { + $words = $this->getIndex($metaname.'_w', ''); + } + + if (!is_null($func)) { + foreach ($value_array as $val) { + foreach ($words as $i => $word) { + if (call_user_func_array($func, array($val, $word))) + $value_ids[$i][] = $val; + } + } + } else { + foreach ($value_array as $val) { + $xval = $val; + $caret = '^'; + $dollar = '$'; + // check for wildcards + if (substr($xval, 0, 1) == '*') { + $xval = substr($xval, 1); + $caret = ''; + } + if (substr($xval, -1, 1) == '*') { + $xval = substr($xval, 0, -1); + $dollar = ''; + } + if (!$caret || !$dollar) { + $re = $caret.preg_quote($xval, '/').$dollar; + foreach(array_keys(preg_grep('/'.$re.'/', $words)) as $i) + $value_ids[$i][] = $val; + } else { + if (($i = array_search($val, $words, true)) !== false) + $value_ids[$i][] = $val; + } + } + } + + unset($words); // free the used memory + + // initialize the result so it won't be null + $result = array(); + foreach ($value_array as $val) { + $result[$val] = array(); + } + + $page_idx = $this->getIndex('page', ''); + + // Special handling for titles + if ($key == 'title') { + foreach ($value_ids as $pid => $val_list) { + $page = $page_idx[$pid]; + foreach ($val_list as $val) { + $result[$val][] = $page; + } + } + } else { + // load all lines and pages so the used lines can be taken and matched with the pages + $lines = $this->getIndex($metaname.'_i', ''); + + foreach ($value_ids as $value_id => $val_list) { + // parse the tuples of the form page_id*1:page2_id*1 and so on, return value + // is an array with page_id => 1, page2_id => 1 etc. so take the keys only + $pages = array_keys($this->parseTuples($page_idx, $lines[$value_id])); + foreach ($val_list as $val) { + $result[$val] = array_merge($result[$val], $pages); + } + } + } + if (!is_array($value)) $result = $result[$value]; + return $result; + } + + /** + * Find the index ID of each search term. + * + * The query terms should only contain valid characters, with a '*' at + * either the beginning or end of the word (or both). + * The $result parameter can be used to merge the index locations with + * the appropriate query term. + * + * @param array $words The query terms. + * @param array $result Set to word => array("length*id" ...) + * @return array Set to length => array(id ...) + * + * @author Tom N Harris <tnharris@whoopdedo.org> + */ + protected function getIndexWords(&$words, &$result) { + $tokens = array(); + $tokenlength = array(); + $tokenwild = array(); + foreach ($words as $word) { + $result[$word] = array(); + $caret = '^'; + $dollar = '$'; + $xword = $word; + $wlen = wordlen($word); + + // check for wildcards + if (substr($xword, 0, 1) == '*') { + $xword = substr($xword, 1); + $caret = ''; + $wlen -= 1; + } + if (substr($xword, -1, 1) == '*') { + $xword = substr($xword, 0, -1); + $dollar = ''; + $wlen -= 1; + } + if ($wlen < IDX_MINWORDLENGTH && $caret && $dollar && !is_numeric($xword)) + continue; + if (!isset($tokens[$xword])) + $tokenlength[$wlen][] = $xword; + if (!$caret || !$dollar) { + $re = $caret.preg_quote($xword, '/').$dollar; + $tokens[$xword][] = array($word, '/'.$re.'/'); + if (!isset($tokenwild[$xword])) + $tokenwild[$xword] = $wlen; + } else { + $tokens[$xword][] = array($word, null); + } + } + asort($tokenwild); + // $tokens = array( base word => array( [ query term , regexp ] ... ) ... ) + // $tokenlength = array( base word length => base word ... ) + // $tokenwild = array( base word => base word length ... ) + $length_filter = empty($tokenwild) ? $tokenlength : min(array_keys($tokenlength)); + $indexes_known = $this->indexLengths($length_filter); + if (!empty($tokenwild)) sort($indexes_known); + // get word IDs + $wids = array(); + foreach ($indexes_known as $ixlen) { + $word_idx = $this->getIndex('w', $ixlen); + // handle exact search + if (isset($tokenlength[$ixlen])) { + foreach ($tokenlength[$ixlen] as $xword) { + $wid = array_search($xword, $word_idx, true); + if ($wid !== false) { + $wids[$ixlen][] = $wid; + foreach ($tokens[$xword] as $w) + $result[$w[0]][] = "$ixlen*$wid"; + } + } + } + // handle wildcard search + foreach ($tokenwild as $xword => $wlen) { + if ($wlen >= $ixlen) break; + foreach ($tokens[$xword] as $w) { + if (is_null($w[1])) continue; + foreach(array_keys(preg_grep($w[1], $word_idx)) as $wid) { + $wids[$ixlen][] = $wid; + $result[$w[0]][] = "$ixlen*$wid"; + } + } + } + } + return $wids; + } + + /** + * Return a list of all pages + * Warning: pages may not exist! + * + * @param string $key list only pages containing the metadata key (optional) + * @return array list of page names + * + * @author Tom N Harris <tnharris@whoopdedo.org> + */ + public function getPages($key=null) { + $page_idx = $this->getIndex('page', ''); + if (is_null($key)) return $page_idx; + + $metaname = idx_cleanName($key); + + // Special handling for titles + if ($key == 'title') { + $title_idx = $this->getIndex('title', ''); + array_splice($page_idx, count($title_idx)); + foreach ($title_idx as $i => $title) + if ($title === "") unset($page_idx[$i]); + return array_values($page_idx); + } + + $pages = array(); + $lines = $this->getIndex($metaname.'_i', ''); + foreach ($lines as $line) { + $pages = array_merge($pages, $this->parseTuples($page_idx, $line)); + } + return array_keys($pages); + } + + /** + * Return a list of words sorted by number of times used + * + * @param int $min bottom frequency threshold + * @param int $max upper frequency limit. No limit if $max<$min + * @param int $minlen minimum length of words to count + * @param string $key metadata key to list. Uses the fulltext index if not given + * @return array list of words as the keys and frequency as values + * + * @author Tom N Harris <tnharris@whoopdedo.org> + */ + public function histogram($min=1, $max=0, $minlen=3, $key=null) { + if ($min < 1) + $min = 1; + if ($max < $min) + $max = 0; + + $result = array(); + + if ($key == 'title') { + $index = $this->getIndex('title', ''); + $index = array_count_values($index); + foreach ($index as $val => $cnt) { + if ($cnt >= $min && (!$max || $cnt <= $max) && strlen($val) >= $minlen) + $result[$val] = $cnt; + } + } + elseif (!is_null($key)) { + $metaname = idx_cleanName($key); + $index = $this->getIndex($metaname.'_i', ''); + $val_idx = array(); + foreach ($index as $wid => $line) { + $freq = $this->countTuples($line); + if ($freq >= $min && (!$max || $freq <= $max)) + $val_idx[$wid] = $freq; + } + if (!empty($val_idx)) { + $words = $this->getIndex($metaname.'_w', ''); + foreach ($val_idx as $wid => $freq) { + if (strlen($words[$wid]) >= $minlen) + $result[$words[$wid]] = $freq; + } + } + } + else { + $lengths = idx_listIndexLengths(); + foreach ($lengths as $length) { + if ($length < $minlen) continue; + $index = $this->getIndex('i', $length); + $words = null; + foreach ($index as $wid => $line) { + $freq = $this->countTuples($line); + if ($freq >= $min && (!$max || $freq <= $max)) { + if ($words === null) + $words = $this->getIndex('w', $length); + $result[$words[$wid]] = $freq; + } + } + } + } + + arsort($result); + return $result; + } + + /** + * Lock the indexer. + * + * @author Tom N Harris <tnharris@whoopdedo.org> + * + * @return bool|string + */ + protected function lock() { + global $conf; + $status = true; + $run = 0; + $lock = $conf['lockdir'].'/_indexer.lock'; + while (!@mkdir($lock, $conf['dmode'])) { + usleep(50); + if(is_dir($lock) && time()-@filemtime($lock) > 60*5){ + // looks like a stale lock - remove it + if (!@rmdir($lock)) { + $status = "removing the stale lock failed"; + return false; + } else { + $status = "stale lock removed"; + } + }elseif($run++ == 1000){ + // we waited 5 seconds for that lock + return false; + } + } + if (!empty($conf['dperm'])) { + chmod($lock, $conf['dperm']); + } + return $status; + } + + /** + * Release the indexer lock. + * + * @author Tom N Harris <tnharris@whoopdedo.org> + * + * @return bool + */ + protected function unlock() { + global $conf; + @rmdir($conf['lockdir'].'/_indexer.lock'); + return true; + } + + /** + * Retrieve the entire index. + * + * The $suffix argument is for an index that is split into + * multiple parts. Different index files should use different + * base names. + * + * @param string $idx name of the index + * @param string $suffix subpart identifier + * @return array list of lines without CR or LF + * + * @author Tom N Harris <tnharris@whoopdedo.org> + */ + protected function getIndex($idx, $suffix) { + global $conf; + $fn = $conf['indexdir'].'/'.$idx.$suffix.'.idx'; + if (!file_exists($fn)) return array(); + return file($fn, FILE_IGNORE_NEW_LINES); + } + + /** + * Replace the contents of the index with an array. + * + * @param string $idx name of the index + * @param string $suffix subpart identifier + * @param array $lines list of lines without LF + * @return bool If saving succeeded + * + * @author Tom N Harris <tnharris@whoopdedo.org> + */ + protected function saveIndex($idx, $suffix, &$lines) { + global $conf; + $fn = $conf['indexdir'].'/'.$idx.$suffix; + $fh = @fopen($fn.'.tmp', 'w'); + if (!$fh) return false; + fwrite($fh, join("\n", $lines)); + if (!empty($lines)) + fwrite($fh, "\n"); + fclose($fh); + if ($conf['fperm']) + chmod($fn.'.tmp', $conf['fperm']); + io_rename($fn.'.tmp', $fn.'.idx'); + return true; + } + + /** + * Retrieve a line from the index. + * + * @param string $idx name of the index + * @param string $suffix subpart identifier + * @param int $id the line number + * @return string a line with trailing whitespace removed + * + * @author Tom N Harris <tnharris@whoopdedo.org> + */ + protected function getIndexKey($idx, $suffix, $id) { + global $conf; + $fn = $conf['indexdir'].'/'.$idx.$suffix.'.idx'; + if (!file_exists($fn)) return ''; + $fh = @fopen($fn, 'r'); + if (!$fh) return ''; + $ln = -1; + while (($line = fgets($fh)) !== false) { + if (++$ln == $id) break; + } + fclose($fh); + return rtrim((string)$line); + } + + /** + * Write a line into the index. + * + * @param string $idx name of the index + * @param string $suffix subpart identifier + * @param int $id the line number + * @param string $line line to write + * @return bool If saving succeeded + * + * @author Tom N Harris <tnharris@whoopdedo.org> + */ + protected function saveIndexKey($idx, $suffix, $id, $line) { + global $conf; + if (substr($line, -1) != "\n") + $line .= "\n"; + $fn = $conf['indexdir'].'/'.$idx.$suffix; + $fh = @fopen($fn.'.tmp', 'w'); + if (!$fh) return false; + $ih = @fopen($fn.'.idx', 'r'); + if ($ih) { + $ln = -1; + while (($curline = fgets($ih)) !== false) { + fwrite($fh, (++$ln == $id) ? $line : $curline); + } + if ($id > $ln) { + while ($id > ++$ln) + fwrite($fh, "\n"); + fwrite($fh, $line); + } + fclose($ih); + } else { + $ln = -1; + while ($id > ++$ln) + fwrite($fh, "\n"); + fwrite($fh, $line); + } + fclose($fh); + if ($conf['fperm']) + chmod($fn.'.tmp', $conf['fperm']); + io_rename($fn.'.tmp', $fn.'.idx'); + return true; + } + + /** + * Retrieve or insert a value in the index. + * + * @param string $idx name of the index + * @param string $suffix subpart identifier + * @param string $value line to find in the index + * @return int|bool line number of the value in the index or false if writing the index failed + * + * @author Tom N Harris <tnharris@whoopdedo.org> + */ + protected function addIndexKey($idx, $suffix, $value) { + $index = $this->getIndex($idx, $suffix); + $id = array_search($value, $index, true); + if ($id === false) { + $id = count($index); + $index[$id] = $value; + if (!$this->saveIndex($idx, $suffix, $index)) { + trigger_error("Failed to write $idx index", E_USER_ERROR); + return false; + } + } + return $id; + } + + /** + * Get the list of lengths indexed in the wiki. + * + * Read the index directory or a cache file and returns + * a sorted array of lengths of the words used in the wiki. + * + * @author YoBoY <yoboy.leguesh@gmail.com> + * + * @return array + */ + protected function listIndexLengths() { + return idx_listIndexLengths(); + } + + /** + * Get the word lengths that have been indexed. + * + * Reads the index directory and returns an array of lengths + * that there are indices for. + * + * @author YoBoY <yoboy.leguesh@gmail.com> + * + * @param array|int $filter + * @return array + */ + protected function indexLengths($filter) { + global $conf; + $idx = array(); + if (is_array($filter)) { + // testing if index files exist only + $path = $conf['indexdir']."/i"; + foreach ($filter as $key => $value) { + if (file_exists($path.$key.'.idx')) + $idx[] = $key; + } + } else { + $lengths = idx_listIndexLengths(); + foreach ($lengths as $key => $length) { + // keep all the values equal or superior + if ((int)$length >= (int)$filter) + $idx[] = $length; + } + } + return $idx; + } + + /** + * Insert or replace a tuple in a line. + * + * @author Tom N Harris <tnharris@whoopdedo.org> + * + * @param string $line + * @param string|int $id + * @param int $count + * @return string + */ + protected function updateTuple($line, $id, $count) { + if ($line != ''){ + $line = preg_replace('/(^|:)'.preg_quote($id,'/').'\*\d*/', '', $line); + } + $line = trim($line, ':'); + if ($count) { + if ($line) { + return "$id*$count:".$line; + } else { + return "$id*$count"; + } + } + return $line; + } + + /** + * Split a line into an array of tuples. + * + * @author Tom N Harris <tnharris@whoopdedo.org> + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param array $keys + * @param string $line + * @return array + */ + protected function parseTuples(&$keys, $line) { + $result = array(); + if ($line == '') return $result; + $parts = explode(':', $line); + foreach ($parts as $tuple) { + if ($tuple === '') continue; + list($key, $cnt) = explode('*', $tuple); + if (!$cnt) continue; + $key = $keys[$key]; + if ($key === false || is_null($key)) continue; + $result[$key] = $cnt; + } + return $result; + } + + /** + * Sum the counts in a list of tuples. + * + * @author Tom N Harris <tnharris@whoopdedo.org> + * + * @param string $line + * @return int + */ + protected function countTuples($line) { + $freq = 0; + $parts = explode(':', $line); + foreach ($parts as $tuple) { + if ($tuple === '') continue; + list(/* $pid */, $cnt) = explode('*', $tuple); + $freq += (int)$cnt; + } + return $freq; + } +} diff --git a/inc/Subscriptions/MediaSubscriptionSender.php b/inc/Subscriptions/MediaSubscriptionSender.php index 5a0f86ed6..1757c2b1c 100644 --- a/inc/Subscriptions/MediaSubscriptionSender.php +++ b/inc/Subscriptions/MediaSubscriptionSender.php @@ -16,8 +16,9 @@ class MediaSubscriptionSender extends SubscriptionSender * @param string $template Mail template ('uploadmail', ...) * @param string $id Media file for which the notification is * @param int|bool $rev Old revision if any + * @param int|bool $current_rev New revision if any */ - public function sendMediaDiff($subscriber_mail, $template, $id, $rev = false) + public function sendMediaDiff($subscriber_mail, $template, $id, $rev = false, $current_rev = false) { global $conf; @@ -26,7 +27,7 @@ class MediaSubscriptionSender extends SubscriptionSender $trep = [ 'MIME' => $mime, - 'MEDIA' => ml($id, '', true, '&', true), + 'MEDIA' => ml($id, $current_rev?('rev='.$current_rev):'', true, '&', true), 'SIZE' => filesize_h(filesize($file)), ]; diff --git a/inc/Subscriptions/PageSubscriptionSender.php b/inc/Subscriptions/PageSubscriptionSender.php index 8fca582a4..e5577c1af 100644 --- a/inc/Subscriptions/PageSubscriptionSender.php +++ b/inc/Subscriptions/PageSubscriptionSender.php @@ -19,17 +19,18 @@ class PageSubscriptionSender extends SubscriptionSender * @param string $id Page for which the notification is * @param int|null $rev Old revision if any * @param string $summary Change summary if any + * @param int|null $current_rev New revision if any * * @return bool true if successfully sent */ - public function sendPageDiff($subscriber_mail, $template, $id, $rev = null, $summary = '') + public function sendPageDiff($subscriber_mail, $template, $id, $rev = null, $summary = '', $current_rev = null) { global $DIFF_INLINESTYLES; // prepare replacements (keys not set in hrep will be taken from trep) $trep = [ 'PAGE' => $id, - 'NEWPAGE' => wl($id, '', true, '&'), + 'NEWPAGE' => wl($id, $current_rev?('rev='.$current_rev):'', true, '&'), 'SUMMARY' => $summary, 'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'), ]; diff --git a/inc/Utf8/Clean.php b/inc/Utf8/Clean.php index 79c46810c..0975ff559 100644 --- a/inc/Utf8/Clean.php +++ b/inc/Utf8/Clean.php @@ -65,8 +65,8 @@ class Clean $ascii = ''; $len = strlen($str); for ($i = 0; $i < $len; $i++) { - if (ord($str{$i}) < 128) { - $ascii .= $str{$i}; + if (ord($str[$i]) < 128) { + $ascii .= $str[$i]; } } return $ascii; diff --git a/inc/Utf8/Unicode.php b/inc/Utf8/Unicode.php index c706d716b..4b6426533 100644 --- a/inc/Utf8/Unicode.php +++ b/inc/Utf8/Unicode.php @@ -46,7 +46,7 @@ class Unicode for ($i = 0; $i < $len; $i++) { - $in = ord($str{$i}); + $in = ord($str[$i]); if ($mState === 0) { diff --git a/inc/auth.php b/inc/auth.php index f99f6501f..0630a76f0 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -139,7 +139,7 @@ function auth_loadACL() { $out = array(); foreach($acl as $line) { $line = trim($line); - if(empty($line) || ($line{0} == '#')) continue; // skip blank lines & comments + if(empty($line) || ($line[0] == '#')) continue; // skip blank lines & comments list($id,$rest) = preg_split('/[ \t]+/',$line,2); // substitute user wildcard first (its 1:1) @@ -154,10 +154,12 @@ function auth_loadACL() { // substitute group wildcard (its 1:m) if(strstr($line, '%GROUP%')){ // if user is not logged in, grps is empty, no output will be added (i.e. skipped) - foreach((array) $USERINFO['grps'] as $grp){ - $nid = str_replace('%GROUP%',cleanID($grp),$id); - $nrest = str_replace('%GROUP%','@'.auth_nameencode($grp),$rest); - $out[] = "$nid\t$nrest"; + if(isset($USERINFO['grps'])){ + foreach((array) $USERINFO['grps'] as $grp){ + $nid = str_replace('%GROUP%',cleanID($grp),$id); + $nrest = str_replace('%GROUP%','@'.auth_nameencode($grp),$rest); + $out[] = "$nid\t$nrest"; + } } } else { $out[] = "$id\t$rest"; @@ -470,7 +472,7 @@ function auth_ismanager($user = null, $groups = null, $adminonly = false) { } } if(is_null($groups)) { - $groups = (array) $USERINFO['grps']; + $groups = $USERINFO ? (array) $USERINFO['grps'] : array(); } // check superuser match @@ -564,7 +566,7 @@ function auth_quickaclcheck($id) { global $INPUT; # if no ACL is used always return upload rights if(!$conf['useacl']) return AUTH_UPLOAD; - return auth_aclcheck($id, $INPUT->server->str('REMOTE_USER'), $USERINFO['grps']); + return auth_aclcheck($id, $INPUT->server->str('REMOTE_USER'), is_array($USERINFO) ? $USERINFO['grps'] : array()); } /** @@ -737,7 +739,7 @@ function auth_nameencode($name, $skip_group = false) { if($name == '%GROUP%') return $name; if(!isset($cache[$name][$skip_group])) { - if($skip_group && $name{0} == '@') { + if($skip_group && $name[0] == '@') { $cache[$name][$skip_group] = '@'.preg_replace_callback( '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/', 'auth_nameencode_callback', substr($name, 1) @@ -1076,7 +1078,7 @@ function act_resendpwd() { if($token) { // we're in token phase - get user info from token - $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth'; + $tfile = $conf['cachedir'].'/'.$token[0].'/'.$token.'.pwauth'; if(!file_exists($tfile)) { msg($lang['resendpwdbadauth'], -1); $INPUT->remove('pwauth'); @@ -1151,7 +1153,7 @@ function act_resendpwd() { // generate auth token $token = md5(auth_randombytes(16)); // random secret - $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth'; + $tfile = $conf['cachedir'].'/'.$token[0].'/'.$token.'.pwauth'; $url = wl('', array('do'=> 'resendpwd', 'pwauth'=> $token), true, '&'); io_saveFile($tfile, $user); diff --git a/inc/changelog.php b/inc/changelog.php index b96a1d43a..652016cb8 100644 --- a/inc/changelog.php +++ b/inc/changelog.php @@ -113,7 +113,7 @@ function addLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extr // newly created $meta['date']['created'] = $created; if ($user){ - $meta['creator'] = $INFO['userinfo']['name']; + $meta['creator'] = isset($INFO) ? $INFO['userinfo']['name'] : null; $meta['user'] = $user; } } elseif (($wasCreated || $wasReverted) && !empty($oldmeta['persistent']['date']['created'])) { @@ -121,10 +121,10 @@ function addLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extr $meta['date']['created'] = $oldmeta['persistent']['date']['created']; $meta['date']['modified'] = $created; // use the files ctime here $meta['creator'] = $oldmeta['persistent']['creator']; - if ($user) $meta['contributor'][$user] = $INFO['userinfo']['name']; + if ($user) $meta['contributor'][$user] = isset($INFO) ? $INFO['userinfo']['name'] : null; } elseif (!$minor) { // non-minor modification $meta['date']['modified'] = $date; - if ($user) $meta['contributor'][$user] = $INFO['userinfo']['name']; + if ($user) $meta['contributor'][$user] = isset($INFO) ? $INFO['userinfo']['name'] : null; } $meta['last_change'] = $logline; p_set_metadata($id, $meta); @@ -228,9 +228,9 @@ function getRecents($first,$num,$ns='',$flags=0){ // read all recent changes. (kept short) if ($flags & RECENTS_MEDIA_CHANGES) { - $lines = @file($conf['media_changelog']); + $lines = @file($conf['media_changelog']) ?: []; } else { - $lines = @file($conf['changelog']); + $lines = @file($conf['changelog']) ?: []; } if (!is_array($lines)) { $lines = array(); @@ -240,7 +240,7 @@ function getRecents($first,$num,$ns='',$flags=0){ $media_lines = array(); if ($flags & RECENTS_MEDIA_PAGES_MIXED) { - $media_lines = @file($conf['media_changelog']); + $media_lines = @file($conf['media_changelog']) ?: []; if (!is_array($media_lines)) { $media_lines = array(); } diff --git a/inc/cli.php b/inc/cli.php index e64798386..cb4dabf04 100644 --- a/inc/cli.php +++ b/inc/cli.php @@ -402,13 +402,13 @@ class DokuCLI_Options { } // first non-option - if($arg{0} != '-') { + if($arg[0] != '-') { $non_opts = array_merge($non_opts, array_slice($this->args, $i)); break; } // long option - if(strlen($arg) > 1 && $arg{1} == '-') { + if(strlen($arg) > 1 && $arg[1] == '-') { list($opt, $val) = explode('=', substr($arg, 2), 2); if(!isset($this->setup[$this->command]['opts'][$opt])) { diff --git a/inc/common.php b/inc/common.php index 6822ccbd3..c462934b2 100644 --- a/inc/common.php +++ b/inc/common.php @@ -280,16 +280,23 @@ function pageinfo() { p_set_metadata($ID, array('last_change' => $revinfo)); } - $info['ip'] = $revinfo['ip']; - $info['user'] = $revinfo['user']; - $info['sum'] = $revinfo['sum']; - // See also $INFO['meta']['last_change'] which is the most recent log line for page $ID. - // Use $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT in place of $info['minor']. - - if($revinfo['user']) { - $info['editor'] = $revinfo['user']; - } else { - $info['editor'] = $revinfo['ip']; + if($revinfo !== false){ + $info['ip'] = $revinfo['ip']; + $info['user'] = $revinfo['user']; + $info['sum'] = $revinfo['sum']; + // See also $INFO['meta']['last_change'] which is the most recent log line for page $ID. + // Use $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT in place of $info['minor']. + + if($revinfo['user']) { + $info['editor'] = $revinfo['user']; + } else { + $info['editor'] = $revinfo['ip']; + } + }else{ + $info['ip'] = null; + $info['user'] = null; + $info['sum'] = null; + $info['editor'] = null; } // draft @@ -312,7 +319,7 @@ function jsinfo() { } //export minimal info to JS, plugins can add more $JSINFO['id'] = $ID; - $JSINFO['namespace'] = (string) $INFO['namespace']; + $JSINFO['namespace'] = isset($INFO) ? (string) $INFO['namespace'] : ''; $JSINFO['ACT'] = act_clean($ACT); $JSINFO['useHeadingNavigation'] = (int) useHeading('navigation'); $JSINFO['useHeadingContent'] = (int) useHeading('content'); @@ -362,16 +369,16 @@ function buildURLparams($params, $sep = '&') { * * @author Andreas Gohr * - * @param array $params array with (attribute name-attribute value) pairs - * @param bool $skipempty skip empty string values? + * @param array $params array with (attribute name-attribute value) pairs + * @param bool $skipEmptyStrings skip empty string values? * @return string */ -function buildAttributes($params, $skipempty = false) { +function buildAttributes($params, $skipEmptyStrings = false) { $url = ''; $white = false; foreach($params as $key => $val) { - if($key{0} == '_') continue; - if($val === '' && $skipempty) continue; + if($key[0] == '_') continue; + if($val === '' && $skipEmptyStrings) continue; if($white) $url .= ' '; $url .= $key.'="'; @@ -397,12 +404,13 @@ function breadcrumbs() { global $ID; global $ACT; global $conf; + global $INFO; //first visit? $crumbs = isset($_SESSION[DOKU_COOKIE]['bc']) ? $_SESSION[DOKU_COOKIE]['bc'] : array(); - //we only save on show and existing visible wiki documents + //we only save on show and existing visible readable wiki documents $file = wikiFN($ID); - if($ACT != 'show' || isHiddenPage($ID) || !file_exists($file)) { + if($ACT != 'show' || $INFO['perm'] < AUTH_READ || isHiddenPage($ID) || !file_exists($file)) { $_SESSION[DOKU_COOKIE]['bc'] = $crumbs; return $crumbs; } @@ -782,7 +790,7 @@ function checkwordblock($text = '') { */ function clientIP($single = false) { /* @var Input $INPUT */ - global $INPUT; + global $INPUT, $conf; $ip = array(); $ip[] = $INPUT->server->str('REMOTE_ADDR'); @@ -829,17 +837,18 @@ function clientIP($single = false) { if(!$single) return join(',', $ip); - // decide which IP to use, trying to avoid local addresses - $ip = array_reverse($ip); + // skip trusted local addresses foreach($ip as $i) { - if(preg_match('/^(::1|[fF][eE]80:|127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/', $i)) { + if(!empty($conf['trustedproxy']) && preg_match('/'.$conf['trustedproxy'].'/', $i)) { continue; } else { return $i; } } - // still here? just use the first (last) address - return $ip[0]; + + // still here? just use the last address + // this case all ips in the list are trusted + return $ip[count($ip)-1]; } /** @@ -1180,8 +1189,8 @@ function parsePageTemplate(&$data) { \dokuwiki\Utf8\PhpString::ucwords($page), \dokuwiki\Utf8\PhpString::strtoupper($page), $INPUT->server->str('REMOTE_USER'), - $USERINFO['name'], - $USERINFO['mail'], + $USERINFO ? $USERINFO['name'] : '', + $USERINFO ? $USERINFO['mail'] : '', $conf['dformat'], ), $tpl ); @@ -1425,8 +1434,8 @@ function saveWikiText($id, $text, $summary, $minor = false) { ); // send notify mails - notify($svdta['id'], 'admin', $svdta['oldRevision'], $svdta['summary'], $minor); - notify($svdta['id'], 'subscribers', $svdta['oldRevision'], $svdta['summary'], $minor); + notify($svdta['id'], 'admin', $svdta['oldRevision'], $svdta['summary'], $minor, $svdta['newRevision']); + notify($svdta['id'], 'subscribers', $svdta['oldRevision'], $svdta['summary'], $minor, $svdta['newRevision']); // update the purgefile (timestamp of the last time anything within the wiki was changed) io_saveFile($conf['cachedir'].'/purgefile', time()); @@ -1468,11 +1477,12 @@ function saveOldRevision($id) { * @param string $summary What changed * @param boolean $minor Is this a minor edit? * @param string[] $replace Additional string substitutions, @KEY@ to be replaced by value + * @param int|string $current_rev New page revision * @return bool * * @author Andreas Gohr <andi@splitbrain.org> */ -function notify($id, $who, $rev = '', $summary = '', $minor = false, $replace = array()) { +function notify($id, $who, $rev = '', $summary = '', $minor = false, $replace = array(), $current_rev = false) { global $conf; /* @var Input $INPUT */ global $INPUT; @@ -1499,7 +1509,7 @@ function notify($id, $who, $rev = '', $summary = '', $minor = false, $replace = // prepare content $subscription = new PageSubscriptionSender(); - return $subscription->sendPageDiff($to, $tpl, $id, $rev, $summary); + return $subscription->sendPageDiff($to, $tpl, $id, $rev, $summary, $current_rev); } /** @@ -1523,11 +1533,7 @@ function getGoogleQuery() { if(!preg_match('/(google|bing|yahoo|ask|duckduckgo|babylon|aol|yandex)/',$url['host'])) return ''; $query = array(); - // temporary workaround against PHP bug #49733 - // see http://bugs.php.net/bug.php?id=49733 - if(UTF8_MBSTRING) $enc = mb_internal_encoding(); parse_str($url['query'], $query); - if(UTF8_MBSTRING) mb_internal_encoding($enc); $q = ''; if(isset($query['q'])){ @@ -1540,6 +1546,8 @@ function getGoogleQuery() { $q = trim($q); if(!$q) return ''; + // ignore if query includes a full URL + if(strpos($q, '//') !== false) return ''; $q = preg_split('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>\\/]+/', $q, -1, PREG_SPLIT_NO_EMPTY); return $q; } diff --git a/inc/deprecated.php b/inc/deprecated.php index d3de76080..205037344 100644 --- a/inc/deprecated.php +++ b/inc/deprecated.php @@ -563,3 +563,8 @@ class Subscription { $manager->notifyAddresses($data); } } + +/** + * @deprecated 2019-12-29 use \dokuwiki\Search\Indexer + */ +class Doku_Indexer extends \dokuwiki\Search\Indexer {}; diff --git a/inc/fulltext.php b/inc/fulltext.php index 48fe28da2..670f048d3 100644 --- a/inc/fulltext.php +++ b/inc/fulltext.php @@ -629,8 +629,8 @@ function ft_resultComplement($args) { * @author Andreas Gohr <andi@splitbrain.org> * @author Kazutaka Miyasaka <kazmiya@gmail.com> * - * @param Doku_Indexer $Indexer - * @param string $query search query + * @param dokuwiki\Search\Indexer $Indexer + * @param string $query search query * @return array of search formulas */ function ft_queryParser($Indexer, $query){ @@ -864,10 +864,10 @@ function ft_queryParser($Indexer, $query){ * * @author Kazutaka Miyasaka <kazmiya@gmail.com> * - * @param Doku_Indexer $Indexer - * @param string $term - * @param bool $consider_asian - * @param bool $phrase_mode + * @param dokuwiki\Search\Indexer $Indexer + * @param string $term + * @param bool $consider_asian + * @param bool $phrase_mode * @return string */ function ft_termParser($Indexer, $term, $consider_asian = true, $phrase_mode = false) { diff --git a/inc/html.php b/inc/html.php index 773d55364..1f494d46b 100644 --- a/inc/html.php +++ b/inc/html.php @@ -50,7 +50,7 @@ function html_login($svg = false){ print p_locale_xhtml('login'); print '<div class="centeralign">'.NL; - $form = new Doku_Form(array('id' => 'dw__login')); + $form = new Doku_Form(array('id' => 'dw__login', 'action'=>wl($ID))); $form->startFieldset($lang['btn_login']); $form->addHidden('id', $ID); $form->addHidden('do', 'login'); @@ -108,7 +108,7 @@ function html_denied() { function html_secedit($text,$show=true){ global $INFO; - if(!$INFO['writable'] || !$show || $INFO['rev']){ + if((isset($INFO) && !$INFO['writable']) || !$show || (isset($INFO) && $INFO['rev'])){ return preg_replace(SEC_EDIT_PATTERN,'',$text); } @@ -707,7 +707,7 @@ function html_recent($first = 0, $show_changes = 'both') { '</p></div>'; } - $form = new Doku_Form(array('id' => 'dw__recent', 'method' => 'GET', 'class' => 'changes')); + $form = new Doku_Form(array('id' => 'dw__recent', 'method' => 'GET', 'class' => 'changes', 'action'=>wl($ID))); $form->addHidden('sectok', null); $form->addHidden('do', 'recent'); $form->addHidden('id', $ID); @@ -964,7 +964,7 @@ function html_li_index($item){ if($item['type'] == "f"){ // scroll to the current item - if($item['id'] == $INFO['id'] && $ACT == 'index') { + if(isset($INFO) && $item['id'] == $INFO['id'] && $ACT == 'index') { $id = ' id="scroll__here"'; $class = ' bounce'; } @@ -1585,7 +1585,7 @@ function html_insert_softbreaks($diffhtml) { */ function html_softbreak_callback($match){ // if match is an html tag, return it intact - if ($match[0]{0} == '<') return $match[0]; + if ($match[0][0] == '<') return $match[0]; // its a long string without a breaking character, // make certain characters into breaking characters by inserting a @@ -1940,7 +1940,7 @@ function html_edit(){ if ($wr) { // sets changed to true when previewed - echo '<script type="text/javascript">/*<![CDATA[*/'. NL; + echo '<script>/*<![CDATA[*/'. NL; echo 'textChanged = ' . ($mod ? 'true' : 'false'); echo '/*!]]>*/</script>' . NL; } ?> diff --git a/inc/indexer.php b/inc/indexer.php index f2c8a9aa8..ab02b8ea2 100644 --- a/inc/indexer.php +++ b/inc/indexer.php @@ -8,6 +8,7 @@ */ use dokuwiki\Extension\Event; +use dokuwiki\Search\Indexer; // Version tag used to force rebuild on upgrade define('INDEXER_VERSION', 8); @@ -67,1225 +68,16 @@ function wordlen($w){ } /** - * Class that encapsulates operations on the indexer database. - * - * @author Tom N Harris <tnharris@whoopdedo.org> - */ -class Doku_Indexer { - /** - * @var array $pidCache Cache for getPID() - */ - protected $pidCache = array(); - - /** - * Adds the contents of a page to the fulltext index - * - * The added text replaces previous words for the same page. - * An empty value erases the page. - * - * @param string $page a page name - * @param string $text the body of the page - * @return string|boolean the function completed successfully - * - * @author Tom N Harris <tnharris@whoopdedo.org> - * @author Andreas Gohr <andi@splitbrain.org> - */ - public function addPageWords($page, $text) { - if (!$this->lock()) - return "locked"; - - // load known documents - $pid = $this->getPIDNoLock($page); - if ($pid === false) { - $this->unlock(); - return false; - } - - $pagewords = array(); - // get word usage in page - $words = $this->getPageWords($text); - if ($words === false) { - $this->unlock(); - return false; - } - - if (!empty($words)) { - foreach (array_keys($words) as $wlen) { - $index = $this->getIndex('i', $wlen); - foreach ($words[$wlen] as $wid => $freq) { - $idx = ($wid<count($index)) ? $index[$wid] : ''; - $index[$wid] = $this->updateTuple($idx, $pid, $freq); - $pagewords[] = "$wlen*$wid"; - } - if (!$this->saveIndex('i', $wlen, $index)) { - $this->unlock(); - return false; - } - } - } - - // Remove obsolete index entries - $pageword_idx = $this->getIndexKey('pageword', '', $pid); - if ($pageword_idx !== '') { - $oldwords = explode(':',$pageword_idx); - $delwords = array_diff($oldwords, $pagewords); - $upwords = array(); - foreach ($delwords as $word) { - if ($word != '') { - list($wlen,$wid) = explode('*', $word); - $wid = (int)$wid; - $upwords[$wlen][] = $wid; - } - } - foreach ($upwords as $wlen => $widx) { - $index = $this->getIndex('i', $wlen); - foreach ($widx as $wid) { - $index[$wid] = $this->updateTuple($index[$wid], $pid, 0); - } - $this->saveIndex('i', $wlen, $index); - } - } - // Save the reverse index - $pageword_idx = join(':', $pagewords); - if (!$this->saveIndexKey('pageword', '', $pid, $pageword_idx)) { - $this->unlock(); - return false; - } - - $this->unlock(); - return true; - } - - /** - * Split the words in a page and add them to the index. - * - * @param string $text content of the page - * @return array list of word IDs and number of times used - * - * @author Andreas Gohr <andi@splitbrain.org> - * @author Christopher Smith <chris@jalakai.co.uk> - * @author Tom N Harris <tnharris@whoopdedo.org> - */ - protected function getPageWords($text) { - - $tokens = $this->tokenizer($text); - $tokens = array_count_values($tokens); // count the frequency of each token - - $words = array(); - foreach ($tokens as $w=>$c) { - $l = wordlen($w); - if (isset($words[$l])){ - $words[$l][$w] = $c + (isset($words[$l][$w]) ? $words[$l][$w] : 0); - }else{ - $words[$l] = array($w => $c); - } - } - - // arrive here with $words = array(wordlen => array(word => frequency)) - $word_idx_modified = false; - $index = array(); //resulting index - foreach (array_keys($words) as $wlen) { - $word_idx = $this->getIndex('w', $wlen); - foreach ($words[$wlen] as $word => $freq) { - $word = (string)$word; - $wid = array_search($word, $word_idx, true); - if ($wid === false) { - $wid = count($word_idx); - $word_idx[] = $word; - $word_idx_modified = true; - } - if (!isset($index[$wlen])) - $index[$wlen] = array(); - $index[$wlen][$wid] = $freq; - } - // save back the word index - if ($word_idx_modified && !$this->saveIndex('w', $wlen, $word_idx)) - return false; - } - - return $index; - } - - /** - * Add/update keys to/of the metadata index. - * - * Adding new keys does not remove other keys for the page. - * An empty value will erase the key. - * The $key parameter can be an array to add multiple keys. $value will - * not be used if $key is an array. - * - * @param string $page a page name - * @param mixed $key a key string or array of key=>value pairs - * @param mixed $value the value or list of values - * @return boolean|string the function completed successfully - * - * @author Tom N Harris <tnharris@whoopdedo.org> - * @author Michael Hamann <michael@content-space.de> - */ - public function addMetaKeys($page, $key, $value=null) { - if (!is_array($key)) { - $key = array($key => $value); - } elseif (!is_null($value)) { - // $key is array, but $value is not null - trigger_error("array passed to addMetaKeys but value is not null", E_USER_WARNING); - } - - if (!$this->lock()) - return "locked"; - - // load known documents - $pid = $this->getPIDNoLock($page); - if ($pid === false) { - $this->unlock(); - return false; - } - - // Special handling for titles so the index file is simpler - if (array_key_exists('title', $key)) { - $value = $key['title']; - if (is_array($value)) { - $value = $value[0]; - } - $this->saveIndexKey('title', '', $pid, $value); - unset($key['title']); - } - - foreach ($key as $name => $values) { - $metaname = idx_cleanName($name); - $this->addIndexKey('metadata', '', $metaname); - $metaidx = $this->getIndex($metaname.'_i', ''); - $metawords = $this->getIndex($metaname.'_w', ''); - $addwords = false; - - if (!is_array($values)) $values = array($values); - - $val_idx = $this->getIndexKey($metaname.'_p', '', $pid); - if ($val_idx !== '') { - $val_idx = explode(':', $val_idx); - // -1 means remove, 0 keep, 1 add - $val_idx = array_combine($val_idx, array_fill(0, count($val_idx), -1)); - } else { - $val_idx = array(); - } - - foreach ($values as $val) { - $val = (string)$val; - if ($val !== "") { - $id = array_search($val, $metawords, true); - if ($id === false) { - // didn't find $val, so we'll add it to the end of metawords and create a placeholder in metaidx - $id = count($metawords); - $metawords[$id] = $val; - $metaidx[$id] = ''; - $addwords = true; - } - // test if value is already in the index - if (isset($val_idx[$id]) && $val_idx[$id] <= 0){ - $val_idx[$id] = 0; - } else { // else add it - $val_idx[$id] = 1; - } - } - } - - if ($addwords) { - $this->saveIndex($metaname.'_w', '', $metawords); - } - $vals_changed = false; - foreach ($val_idx as $id => $action) { - if ($action == -1) { - $metaidx[$id] = $this->updateTuple($metaidx[$id], $pid, 0); - $vals_changed = true; - unset($val_idx[$id]); - } elseif ($action == 1) { - $metaidx[$id] = $this->updateTuple($metaidx[$id], $pid, 1); - $vals_changed = true; - } - } - - if ($vals_changed) { - $this->saveIndex($metaname.'_i', '', $metaidx); - $val_idx = implode(':', array_keys($val_idx)); - $this->saveIndexKey($metaname.'_p', '', $pid, $val_idx); - } - - unset($metaidx); - unset($metawords); - } - - $this->unlock(); - return true; - } - - /** - * Rename a page in the search index without changing the indexed content. This function doesn't check if the - * old or new name exists in the filesystem. It returns an error if the old page isn't in the page list of the - * indexer and it deletes all previously indexed content of the new page. - * - * @param string $oldpage The old page name - * @param string $newpage The new page name - * @return string|bool If the page was successfully renamed, can be a message in the case of an error - */ - public function renamePage($oldpage, $newpage) { - if (!$this->lock()) return 'locked'; - - $pages = $this->getPages(); - - $id = array_search($oldpage, $pages, true); - if ($id === false) { - $this->unlock(); - return 'page is not in index'; - } - - $new_id = array_search($newpage, $pages, true); - if ($new_id !== false) { - // make sure the page is not in the index anymore - if ($this->deletePageNoLock($newpage) !== true) { - return false; - } - - $pages[$new_id] = 'deleted:'.time().rand(0, 9999); - } - - $pages[$id] = $newpage; - - // update index - if (!$this->saveIndex('page', '', $pages)) { - $this->unlock(); - return false; - } - - // reset the pid cache - $this->pidCache = array(); - - $this->unlock(); - return true; - } - - /** - * Renames a meta value in the index. This doesn't change the meta value in the pages, it assumes that all pages - * will be updated. - * - * @param string $key The metadata key of which a value shall be changed - * @param string $oldvalue The old value that shall be renamed - * @param string $newvalue The new value to which the old value shall be renamed, if exists values will be merged - * @return bool|string If renaming the value has been successful, false or error message on error. - */ - public function renameMetaValue($key, $oldvalue, $newvalue) { - if (!$this->lock()) return 'locked'; - - // change the relation references index - $metavalues = $this->getIndex($key, '_w'); - $oldid = array_search($oldvalue, $metavalues, true); - if ($oldid !== false) { - $newid = array_search($newvalue, $metavalues, true); - if ($newid !== false) { - // free memory - unset ($metavalues); - - // okay, now we have two entries for the same value. we need to merge them. - $indexline = $this->getIndexKey($key.'_i', '', $oldid); - if ($indexline != '') { - $newindexline = $this->getIndexKey($key.'_i', '', $newid); - $pagekeys = $this->getIndex($key.'_p', ''); - $parts = explode(':', $indexline); - foreach ($parts as $part) { - list($id, $count) = explode('*', $part); - $newindexline = $this->updateTuple($newindexline, $id, $count); - - $keyline = explode(':', $pagekeys[$id]); - // remove old meta value - $keyline = array_diff($keyline, array($oldid)); - // add new meta value when not already present - if (!in_array($newid, $keyline)) { - array_push($keyline, $newid); - } - $pagekeys[$id] = implode(':', $keyline); - } - $this->saveIndex($key.'_p', '', $pagekeys); - unset($pagekeys); - $this->saveIndexKey($key.'_i', '', $oldid, ''); - $this->saveIndexKey($key.'_i', '', $newid, $newindexline); - } - } else { - $metavalues[$oldid] = $newvalue; - if (!$this->saveIndex($key.'_w', '', $metavalues)) { - $this->unlock(); - return false; - } - } - } - - $this->unlock(); - return true; - } - - /** - * Remove a page from the index - * - * Erases entries in all known indexes. - * - * @param string $page a page name - * @return string|boolean the function completed successfully - * - * @author Tom N Harris <tnharris@whoopdedo.org> - */ - public function deletePage($page) { - if (!$this->lock()) - return "locked"; - - $result = $this->deletePageNoLock($page); - - $this->unlock(); - - return $result; - } - - /** - * Remove a page from the index without locking the index, only use this function if the index is already locked - * - * Erases entries in all known indexes. - * - * @param string $page a page name - * @return boolean the function completed successfully - * - * @author Tom N Harris <tnharris@whoopdedo.org> - */ - protected function deletePageNoLock($page) { - // load known documents - $pid = $this->getPIDNoLock($page); - if ($pid === false) { - return false; - } - - // Remove obsolete index entries - $pageword_idx = $this->getIndexKey('pageword', '', $pid); - if ($pageword_idx !== '') { - $delwords = explode(':',$pageword_idx); - $upwords = array(); - foreach ($delwords as $word) { - if ($word != '') { - list($wlen,$wid) = explode('*', $word); - $wid = (int)$wid; - $upwords[$wlen][] = $wid; - } - } - foreach ($upwords as $wlen => $widx) { - $index = $this->getIndex('i', $wlen); - foreach ($widx as $wid) { - $index[$wid] = $this->updateTuple($index[$wid], $pid, 0); - } - $this->saveIndex('i', $wlen, $index); - } - } - // Save the reverse index - if (!$this->saveIndexKey('pageword', '', $pid, "")) { - return false; - } - - $this->saveIndexKey('title', '', $pid, ""); - $keyidx = $this->getIndex('metadata', ''); - foreach ($keyidx as $metaname) { - $val_idx = explode(':', $this->getIndexKey($metaname.'_p', '', $pid)); - $meta_idx = $this->getIndex($metaname.'_i', ''); - foreach ($val_idx as $id) { - if ($id === '') continue; - $meta_idx[$id] = $this->updateTuple($meta_idx[$id], $pid, 0); - } - $this->saveIndex($metaname.'_i', '', $meta_idx); - $this->saveIndexKey($metaname.'_p', '', $pid, ''); - } - - return true; - } - - /** - * Clear the whole index - * - * @return bool If the index has been cleared successfully - */ - public function clear() { - global $conf; - - if (!$this->lock()) return false; - - @unlink($conf['indexdir'].'/page.idx'); - @unlink($conf['indexdir'].'/title.idx'); - @unlink($conf['indexdir'].'/pageword.idx'); - @unlink($conf['indexdir'].'/metadata.idx'); - $dir = @opendir($conf['indexdir']); - if($dir!==false){ - while(($f = readdir($dir)) !== false){ - if(substr($f,-4)=='.idx' && - (substr($f,0,1)=='i' || substr($f,0,1)=='w' - || substr($f,-6)=='_w.idx' || substr($f,-6)=='_i.idx' || substr($f,-6)=='_p.idx')) - @unlink($conf['indexdir']."/$f"); - } - } - @unlink($conf['indexdir'].'/lengths.idx'); - - // clear the pid cache - $this->pidCache = array(); - - $this->unlock(); - return true; - } - - /** - * Split the text into words for fulltext search - * - * TODO: does this also need &$stopwords ? - * - * @triggers INDEXER_TEXT_PREPARE - * This event allows plugins to modify the text before it gets tokenized. - * Plugins intercepting this event should also intercept INDEX_VERSION_GET - * - * @param string $text plain text - * @param boolean $wc are wildcards allowed? - * @return array list of words in the text - * - * @author Tom N Harris <tnharris@whoopdedo.org> - * @author Andreas Gohr <andi@splitbrain.org> - */ - public function tokenizer($text, $wc=false) { - $wc = ($wc) ? '' : '\*'; - $stopwords =& idx_get_stopwords(); - - // prepare the text to be tokenized - $evt = new Event('INDEXER_TEXT_PREPARE', $text); - if ($evt->advise_before(true)) { - if (preg_match('/[^0-9A-Za-z ]/u', $text)) { - $text = \dokuwiki\Utf8\Asian::separateAsianWords($text); - } - } - $evt->advise_after(); - unset($evt); - - $text = strtr($text, - array( - "\r" => ' ', - "\n" => ' ', - "\t" => ' ', - "\xC2\xAD" => '', //soft-hyphen - ) - ); - if (preg_match('/[^0-9A-Za-z ]/u', $text)) - $text = \dokuwiki\Utf8\Clean::stripspecials($text, ' ', '\._\-:'.$wc); - - $wordlist = explode(' ', $text); - foreach ($wordlist as $i => $word) { - $wordlist[$i] = (preg_match('/[^0-9A-Za-z]/u', $word)) ? - \dokuwiki\Utf8\PhpString::strtolower($word) : strtolower($word); - } - - foreach ($wordlist as $i => $word) { - if ((!is_numeric($word) && strlen($word) < IDX_MINWORDLENGTH) - || array_search($word, $stopwords, true) !== false) - unset($wordlist[$i]); - } - return array_values($wordlist); - } - - /** - * Get the numeric PID of a page - * - * @param string $page The page to get the PID for - * @return bool|int The page id on success, false on error - */ - public function getPID($page) { - // return PID without locking when it is in the cache - if (isset($this->pidCache[$page])) return $this->pidCache[$page]; - - if (!$this->lock()) - return false; - - // load known documents - $pid = $this->getPIDNoLock($page); - if ($pid === false) { - $this->unlock(); - return false; - } - - $this->unlock(); - return $pid; - } - - /** - * Get the numeric PID of a page without locking the index. - * Only use this function when the index is already locked. - * - * @param string $page The page to get the PID for - * @return bool|int The page id on success, false on error - */ - protected function getPIDNoLock($page) { - // avoid expensive addIndexKey operation for the most recently requested pages by using a cache - if (isset($this->pidCache[$page])) return $this->pidCache[$page]; - $pid = $this->addIndexKey('page', '', $page); - // limit cache to 10 entries by discarding the oldest element as in DokuWiki usually only the most recently - // added item will be requested again - if (count($this->pidCache) > 10) array_shift($this->pidCache); - $this->pidCache[$page] = $pid; - return $pid; - } - - /** - * Get the page id of a numeric PID - * - * @param int $pid The PID to get the page id for - * @return string The page id - */ - public function getPageFromPID($pid) { - return $this->getIndexKey('page', '', $pid); - } - - /** - * Find pages in the fulltext index containing the words, - * - * The search words must be pre-tokenized, meaning only letters and - * numbers with an optional wildcard - * - * The returned array will have the original tokens as key. The values - * in the returned list is an array with the page names as keys and the - * number of times that token appears on the page as value. - * - * @param array $tokens list of words to search for - * @return array list of page names with usage counts - * - * @author Tom N Harris <tnharris@whoopdedo.org> - * @author Andreas Gohr <andi@splitbrain.org> - */ - public function lookup(&$tokens) { - $result = array(); - $wids = $this->getIndexWords($tokens, $result); - if (empty($wids)) return array(); - // load known words and documents - $page_idx = $this->getIndex('page', ''); - $docs = array(); - foreach (array_keys($wids) as $wlen) { - $wids[$wlen] = array_unique($wids[$wlen]); - $index = $this->getIndex('i', $wlen); - foreach($wids[$wlen] as $ixid) { - if ($ixid < count($index)) - $docs["$wlen*$ixid"] = $this->parseTuples($page_idx, $index[$ixid]); - } - } - // merge found pages into final result array - $final = array(); - foreach ($result as $word => $res) { - $final[$word] = array(); - foreach ($res as $wid) { - // handle the case when ($ixid < count($index)) has been false - // and thus $docs[$wid] hasn't been set. - if (!isset($docs[$wid])) continue; - $hits = &$docs[$wid]; - foreach ($hits as $hitkey => $hitcnt) { - // make sure the document still exists - if (!page_exists($hitkey, '', false)) continue; - if (!isset($final[$word][$hitkey])) - $final[$word][$hitkey] = $hitcnt; - else - $final[$word][$hitkey] += $hitcnt; - } - } - } - return $final; - } - - /** - * Find pages containing a metadata key. - * - * The metadata values are compared as case-sensitive strings. Pass a - * callback function that returns true or false to use a different - * comparison function. The function will be called with the $value being - * searched for as the first argument, and the word in the index as the - * second argument. The function preg_match can be used directly if the - * values are regexes. - * - * @param string $key name of the metadata key to look for - * @param string $value search term to look for, must be a string or array of strings - * @param callback $func comparison function - * @return array lists with page names, keys are query values if $value is array - * - * @author Tom N Harris <tnharris@whoopdedo.org> - * @author Michael Hamann <michael@content-space.de> - */ - public function lookupKey($key, &$value, $func=null) { - if (!is_array($value)) - $value_array = array($value); - else - $value_array =& $value; - - // the matching ids for the provided value(s) - $value_ids = array(); - - $metaname = idx_cleanName($key); - - // get all words in order to search the matching ids - if ($key == 'title') { - $words = $this->getIndex('title', ''); - } else { - $words = $this->getIndex($metaname.'_w', ''); - } - - if (!is_null($func)) { - foreach ($value_array as $val) { - foreach ($words as $i => $word) { - if (call_user_func_array($func, array($val, $word))) - $value_ids[$i][] = $val; - } - } - } else { - foreach ($value_array as $val) { - $xval = $val; - $caret = '^'; - $dollar = '$'; - // check for wildcards - if (substr($xval, 0, 1) == '*') { - $xval = substr($xval, 1); - $caret = ''; - } - if (substr($xval, -1, 1) == '*') { - $xval = substr($xval, 0, -1); - $dollar = ''; - } - if (!$caret || !$dollar) { - $re = $caret.preg_quote($xval, '/').$dollar; - foreach(array_keys(preg_grep('/'.$re.'/', $words)) as $i) - $value_ids[$i][] = $val; - } else { - if (($i = array_search($val, $words, true)) !== false) - $value_ids[$i][] = $val; - } - } - } - - unset($words); // free the used memory - - // initialize the result so it won't be null - $result = array(); - foreach ($value_array as $val) { - $result[$val] = array(); - } - - $page_idx = $this->getIndex('page', ''); - - // Special handling for titles - if ($key == 'title') { - foreach ($value_ids as $pid => $val_list) { - $page = $page_idx[$pid]; - foreach ($val_list as $val) { - $result[$val][] = $page; - } - } - } else { - // load all lines and pages so the used lines can be taken and matched with the pages - $lines = $this->getIndex($metaname.'_i', ''); - - foreach ($value_ids as $value_id => $val_list) { - // parse the tuples of the form page_id*1:page2_id*1 and so on, return value - // is an array with page_id => 1, page2_id => 1 etc. so take the keys only - $pages = array_keys($this->parseTuples($page_idx, $lines[$value_id])); - foreach ($val_list as $val) { - $result[$val] = array_merge($result[$val], $pages); - } - } - } - if (!is_array($value)) $result = $result[$value]; - return $result; - } - - /** - * Find the index ID of each search term. - * - * The query terms should only contain valid characters, with a '*' at - * either the beginning or end of the word (or both). - * The $result parameter can be used to merge the index locations with - * the appropriate query term. - * - * @param array $words The query terms. - * @param array $result Set to word => array("length*id" ...) - * @return array Set to length => array(id ...) - * - * @author Tom N Harris <tnharris@whoopdedo.org> - */ - protected function getIndexWords(&$words, &$result) { - $tokens = array(); - $tokenlength = array(); - $tokenwild = array(); - foreach ($words as $word) { - $result[$word] = array(); - $caret = '^'; - $dollar = '$'; - $xword = $word; - $wlen = wordlen($word); - - // check for wildcards - if (substr($xword, 0, 1) == '*') { - $xword = substr($xword, 1); - $caret = ''; - $wlen -= 1; - } - if (substr($xword, -1, 1) == '*') { - $xword = substr($xword, 0, -1); - $dollar = ''; - $wlen -= 1; - } - if ($wlen < IDX_MINWORDLENGTH && $caret && $dollar && !is_numeric($xword)) - continue; - if (!isset($tokens[$xword])) - $tokenlength[$wlen][] = $xword; - if (!$caret || !$dollar) { - $re = $caret.preg_quote($xword, '/').$dollar; - $tokens[$xword][] = array($word, '/'.$re.'/'); - if (!isset($tokenwild[$xword])) - $tokenwild[$xword] = $wlen; - } else { - $tokens[$xword][] = array($word, null); - } - } - asort($tokenwild); - // $tokens = array( base word => array( [ query term , regexp ] ... ) ... ) - // $tokenlength = array( base word length => base word ... ) - // $tokenwild = array( base word => base word length ... ) - $length_filter = empty($tokenwild) ? $tokenlength : min(array_keys($tokenlength)); - $indexes_known = $this->indexLengths($length_filter); - if (!empty($tokenwild)) sort($indexes_known); - // get word IDs - $wids = array(); - foreach ($indexes_known as $ixlen) { - $word_idx = $this->getIndex('w', $ixlen); - // handle exact search - if (isset($tokenlength[$ixlen])) { - foreach ($tokenlength[$ixlen] as $xword) { - $wid = array_search($xword, $word_idx, true); - if ($wid !== false) { - $wids[$ixlen][] = $wid; - foreach ($tokens[$xword] as $w) - $result[$w[0]][] = "$ixlen*$wid"; - } - } - } - // handle wildcard search - foreach ($tokenwild as $xword => $wlen) { - if ($wlen >= $ixlen) break; - foreach ($tokens[$xword] as $w) { - if (is_null($w[1])) continue; - foreach(array_keys(preg_grep($w[1], $word_idx)) as $wid) { - $wids[$ixlen][] = $wid; - $result[$w[0]][] = "$ixlen*$wid"; - } - } - } - } - return $wids; - } - - /** - * Return a list of all pages - * Warning: pages may not exist! - * - * @param string $key list only pages containing the metadata key (optional) - * @return array list of page names - * - * @author Tom N Harris <tnharris@whoopdedo.org> - */ - public function getPages($key=null) { - $page_idx = $this->getIndex('page', ''); - if (is_null($key)) return $page_idx; - - $metaname = idx_cleanName($key); - - // Special handling for titles - if ($key == 'title') { - $title_idx = $this->getIndex('title', ''); - array_splice($page_idx, count($title_idx)); - foreach ($title_idx as $i => $title) - if ($title === "") unset($page_idx[$i]); - return array_values($page_idx); - } - - $pages = array(); - $lines = $this->getIndex($metaname.'_i', ''); - foreach ($lines as $line) { - $pages = array_merge($pages, $this->parseTuples($page_idx, $line)); - } - return array_keys($pages); - } - - /** - * Return a list of words sorted by number of times used - * - * @param int $min bottom frequency threshold - * @param int $max upper frequency limit. No limit if $max<$min - * @param int $minlen minimum length of words to count - * @param string $key metadata key to list. Uses the fulltext index if not given - * @return array list of words as the keys and frequency as values - * - * @author Tom N Harris <tnharris@whoopdedo.org> - */ - public function histogram($min=1, $max=0, $minlen=3, $key=null) { - if ($min < 1) - $min = 1; - if ($max < $min) - $max = 0; - - $result = array(); - - if ($key == 'title') { - $index = $this->getIndex('title', ''); - $index = array_count_values($index); - foreach ($index as $val => $cnt) { - if ($cnt >= $min && (!$max || $cnt <= $max) && strlen($val) >= $minlen) - $result[$val] = $cnt; - } - } - elseif (!is_null($key)) { - $metaname = idx_cleanName($key); - $index = $this->getIndex($metaname.'_i', ''); - $val_idx = array(); - foreach ($index as $wid => $line) { - $freq = $this->countTuples($line); - if ($freq >= $min && (!$max || $freq <= $max)) - $val_idx[$wid] = $freq; - } - if (!empty($val_idx)) { - $words = $this->getIndex($metaname.'_w', ''); - foreach ($val_idx as $wid => $freq) { - if (strlen($words[$wid]) >= $minlen) - $result[$words[$wid]] = $freq; - } - } - } - else { - $lengths = idx_listIndexLengths(); - foreach ($lengths as $length) { - if ($length < $minlen) continue; - $index = $this->getIndex('i', $length); - $words = null; - foreach ($index as $wid => $line) { - $freq = $this->countTuples($line); - if ($freq >= $min && (!$max || $freq <= $max)) { - if ($words === null) - $words = $this->getIndex('w', $length); - $result[$words[$wid]] = $freq; - } - } - } - } - - arsort($result); - return $result; - } - - /** - * Lock the indexer. - * - * @author Tom N Harris <tnharris@whoopdedo.org> - * - * @return bool|string - */ - protected function lock() { - global $conf; - $status = true; - $run = 0; - $lock = $conf['lockdir'].'/_indexer.lock'; - while (!@mkdir($lock, $conf['dmode'])) { - usleep(50); - if(is_dir($lock) && time()-@filemtime($lock) > 60*5){ - // looks like a stale lock - remove it - if (!@rmdir($lock)) { - $status = "removing the stale lock failed"; - return false; - } else { - $status = "stale lock removed"; - } - }elseif($run++ == 1000){ - // we waited 5 seconds for that lock - return false; - } - } - if (!empty($conf['dperm'])) { - chmod($lock, $conf['dperm']); - } - return $status; - } - - /** - * Release the indexer lock. - * - * @author Tom N Harris <tnharris@whoopdedo.org> - * - * @return bool - */ - protected function unlock() { - global $conf; - @rmdir($conf['lockdir'].'/_indexer.lock'); - return true; - } - - /** - * Retrieve the entire index. - * - * The $suffix argument is for an index that is split into - * multiple parts. Different index files should use different - * base names. - * - * @param string $idx name of the index - * @param string $suffix subpart identifier - * @return array list of lines without CR or LF - * - * @author Tom N Harris <tnharris@whoopdedo.org> - */ - protected function getIndex($idx, $suffix) { - global $conf; - $fn = $conf['indexdir'].'/'.$idx.$suffix.'.idx'; - if (!file_exists($fn)) return array(); - return file($fn, FILE_IGNORE_NEW_LINES); - } - - /** - * Replace the contents of the index with an array. - * - * @param string $idx name of the index - * @param string $suffix subpart identifier - * @param array $lines list of lines without LF - * @return bool If saving succeeded - * - * @author Tom N Harris <tnharris@whoopdedo.org> - */ - protected function saveIndex($idx, $suffix, &$lines) { - global $conf; - $fn = $conf['indexdir'].'/'.$idx.$suffix; - $fh = @fopen($fn.'.tmp', 'w'); - if (!$fh) return false; - fwrite($fh, join("\n", $lines)); - if (!empty($lines)) - fwrite($fh, "\n"); - fclose($fh); - if (isset($conf['fperm'])) - chmod($fn.'.tmp', $conf['fperm']); - io_rename($fn.'.tmp', $fn.'.idx'); - return true; - } - - /** - * Retrieve a line from the index. - * - * @param string $idx name of the index - * @param string $suffix subpart identifier - * @param int $id the line number - * @return string a line with trailing whitespace removed - * - * @author Tom N Harris <tnharris@whoopdedo.org> - */ - protected function getIndexKey($idx, $suffix, $id) { - global $conf; - $fn = $conf['indexdir'].'/'.$idx.$suffix.'.idx'; - if (!file_exists($fn)) return ''; - $fh = @fopen($fn, 'r'); - if (!$fh) return ''; - $ln = -1; - while (($line = fgets($fh)) !== false) { - if (++$ln == $id) break; - } - fclose($fh); - return rtrim((string)$line); - } - - /** - * Write a line into the index. - * - * @param string $idx name of the index - * @param string $suffix subpart identifier - * @param int $id the line number - * @param string $line line to write - * @return bool If saving succeeded - * - * @author Tom N Harris <tnharris@whoopdedo.org> - */ - protected function saveIndexKey($idx, $suffix, $id, $line) { - global $conf; - if (substr($line, -1) != "\n") - $line .= "\n"; - $fn = $conf['indexdir'].'/'.$idx.$suffix; - $fh = @fopen($fn.'.tmp', 'w'); - if (!$fh) return false; - $ih = @fopen($fn.'.idx', 'r'); - if ($ih) { - $ln = -1; - while (($curline = fgets($ih)) !== false) { - fwrite($fh, (++$ln == $id) ? $line : $curline); - } - if ($id > $ln) { - while ($id > ++$ln) - fwrite($fh, "\n"); - fwrite($fh, $line); - } - fclose($ih); - } else { - $ln = -1; - while ($id > ++$ln) - fwrite($fh, "\n"); - fwrite($fh, $line); - } - fclose($fh); - if (isset($conf['fperm'])) - chmod($fn.'.tmp', $conf['fperm']); - io_rename($fn.'.tmp', $fn.'.idx'); - return true; - } - - /** - * Retrieve or insert a value in the index. - * - * @param string $idx name of the index - * @param string $suffix subpart identifier - * @param string $value line to find in the index - * @return int|bool line number of the value in the index or false if writing the index failed - * - * @author Tom N Harris <tnharris@whoopdedo.org> - */ - protected function addIndexKey($idx, $suffix, $value) { - $index = $this->getIndex($idx, $suffix); - $id = array_search($value, $index, true); - if ($id === false) { - $id = count($index); - $index[$id] = $value; - if (!$this->saveIndex($idx, $suffix, $index)) { - trigger_error("Failed to write $idx index", E_USER_ERROR); - return false; - } - } - return $id; - } - - /** - * Get the list of lengths indexed in the wiki. - * - * Read the index directory or a cache file and returns - * a sorted array of lengths of the words used in the wiki. - * - * @author YoBoY <yoboy.leguesh@gmail.com> - * - * @return array - */ - protected function listIndexLengths() { - return idx_listIndexLengths(); - } - - /** - * Get the word lengths that have been indexed. - * - * Reads the index directory and returns an array of lengths - * that there are indices for. - * - * @author YoBoY <yoboy.leguesh@gmail.com> - * - * @param array|int $filter - * @return array - */ - protected function indexLengths($filter) { - global $conf; - $idx = array(); - if (is_array($filter)) { - // testing if index files exist only - $path = $conf['indexdir']."/i"; - foreach ($filter as $key => $value) { - if (file_exists($path.$key.'.idx')) - $idx[] = $key; - } - } else { - $lengths = idx_listIndexLengths(); - foreach ($lengths as $key => $length) { - // keep all the values equal or superior - if ((int)$length >= (int)$filter) - $idx[] = $length; - } - } - return $idx; - } - - /** - * Insert or replace a tuple in a line. - * - * @author Tom N Harris <tnharris@whoopdedo.org> - * - * @param string $line - * @param string|int $id - * @param int $count - * @return string - */ - protected function updateTuple($line, $id, $count) { - if ($line != ''){ - $line = preg_replace('/(^|:)'.preg_quote($id,'/').'\*\d*/', '', $line); - } - $line = trim($line, ':'); - if ($count) { - if ($line) { - return "$id*$count:".$line; - } else { - return "$id*$count"; - } - } - return $line; - } - - /** - * Split a line into an array of tuples. - * - * @author Tom N Harris <tnharris@whoopdedo.org> - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param array $keys - * @param string $line - * @return array - */ - protected function parseTuples(&$keys, $line) { - $result = array(); - if ($line == '') return $result; - $parts = explode(':', $line); - foreach ($parts as $tuple) { - if ($tuple === '') continue; - list($key, $cnt) = explode('*', $tuple); - if (!$cnt) continue; - $key = $keys[$key]; - if ($key === false || is_null($key)) continue; - $result[$key] = $cnt; - } - return $result; - } - - /** - * Sum the counts in a list of tuples. - * - * @author Tom N Harris <tnharris@whoopdedo.org> - * - * @param string $line - * @return int - */ - protected function countTuples($line) { - $freq = 0; - $parts = explode(':', $line); - foreach ($parts as $tuple) { - if ($tuple === '') continue; - list(/* $pid */, $cnt) = explode('*', $tuple); - $freq += (int)$cnt; - } - return $freq; - } -} - -/** * Create an instance of the indexer. * - * @return Doku_Indexer a Doku_Indexer + * @return Indexer an Indexer * * @author Tom N Harris <tnharris@whoopdedo.org> */ function idx_get_indexer() { static $Indexer; if (!isset($Indexer)) { - $Indexer = new Doku_Indexer(); + $Indexer = new Indexer(); } return $Indexer; } diff --git a/inc/init.php b/inc/init.php index 548ac80db..f9bb53472 100644 --- a/inc/init.php +++ b/inc/init.php @@ -340,7 +340,7 @@ function init_files(){ $fh = @fopen($file,'a'); if($fh){ fclose($fh); - if(!empty($conf['fperm'])) chmod($file, $conf['fperm']); + if($conf['fperm']) chmod($file, $conf['fperm']); }else{ nice_die("$file is not writable. Check your permissions settings!"); } @@ -405,7 +405,7 @@ function init_creationmodes(){ // check what is set automatically by the system on file creation // and set the fperm param if it's not what we want - $auto_fmode = 0666 & ~$umask; + $auto_fmode = $conf['fmode'] & ~$umask; if($auto_fmode != $conf['fmode']) $conf['fperm'] = $conf['fmode']; // check what is set automatically by the system on file creation @@ -576,7 +576,7 @@ function fullpath($path,$exists=false){ $iswin = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' || !empty($GLOBALS['DOKU_UNITTEST_ASSUME_WINDOWS'])); // find the (indestructable) root of the path - keeps windows stuff intact - if($path{0} == '/'){ + if($path[0] == '/'){ $root = '/'; }elseif($iswin){ // match drive letter and UNC paths diff --git a/inc/io.php b/inc/io.php index 18aae25e7..1dfabe845 100644 --- a/inc/io.php +++ b/inc/io.php @@ -252,7 +252,7 @@ function _io_saveFile($file, $content, $append) { fclose($fh); } - if(!$fileexists and !empty($conf['fperm'])) chmod($file, $conf['fperm']); + if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']); return true; } diff --git a/inc/lang/cs/lang.php b/inc/lang/cs/lang.php index 7a9b7200a..8d46dd0aa 100644 --- a/inc/lang/cs/lang.php +++ b/inc/lang/cs/lang.php @@ -3,6 +3,7 @@ /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * + * @author Petr Kajzar <petr.kajzar@lf1.cuni.cz> * @author Robert Surý <rsurycz@seznam.cz> * @author Martin Hořínek <hev@hev.cz> * @author Jonáš Dyba <jonas.dyba@gmail.com> diff --git a/inc/lang/de/lang.php b/inc/lang/de/lang.php index d4a325559..47bc6af8f 100644 --- a/inc/lang/de/lang.php +++ b/inc/lang/de/lang.php @@ -3,6 +3,8 @@ /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * + * @author Eric Haberstroh <ehaberstroh@gmail.com> + * @author C!own77 <clown77@posteo.de> * @author Anonymous <anonymous@example.org> * @author Michaelsy <github@informantum.de> * @author Benjamin Molitor <bmolitor@uos.de> diff --git a/inc/lang/en/mailtext.txt b/inc/lang/en/mailtext.txt index aea14d459..eac40356d 100644 --- a/inc/lang/en/mailtext.txt +++ b/inc/lang/en/mailtext.txt @@ -1,12 +1,15 @@ A page in your DokuWiki was added or changed. Here are the details: -Date : @DATE@ -Browser : @BROWSER@ -IP-Address : @IPADDRESS@ -Hostname : @HOSTNAME@ -Old Revision: @OLDPAGE@ -New Revision: @NEWPAGE@ -Edit Summary: @SUMMARY@ -User : @USER@ +Browser : @BROWSER@ +IP Address : @IPADDRESS@ +Hostname : @HOSTNAME@ +Old Revision : @OLDPAGE@ +New Revision : @NEWPAGE@ +Date of New Revision: @DATE@ +Edit Summary : @SUMMARY@ +User : @USER@ + +There may be newer changes after this revision. If this +happens, a message will be shown on the top of the rev page. @DIFF@ diff --git a/inc/lang/en/subscr_single.txt b/inc/lang/en/subscr_single.txt index 8f097dc3e..046b9945d 100644 --- a/inc/lang/en/subscr_single.txt +++ b/inc/lang/en/subscr_single.txt @@ -7,11 +7,11 @@ Here are the changes: @DIFF@ -------------------------------------------------------- -Date : @DATE@ -User : @USER@ -Edit Summary: @SUMMARY@ -Old Revision: @OLDPAGE@ -New Revision: @NEWPAGE@ +User : @USER@ +Edit Summary : @SUMMARY@ +Old Revision : @OLDPAGE@ +New Revision : @NEWPAGE@ +Date of New Revision: @DATE@ To cancel the page notifications, log into the wiki at @DOKUWIKIURL@ then visit diff --git a/inc/lang/es/lang.php b/inc/lang/es/lang.php index 7924a7da7..95a1d644e 100644 --- a/inc/lang/es/lang.php +++ b/inc/lang/es/lang.php @@ -3,10 +3,10 @@ /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * + * @author Domingo Redal <docxml@gmail.com> * @author Liliana <lilianasaidon@gmail.com> * @author Alex Cachinero <anarres@protonmail.com> * @author WIRESLINKEA <wireslinkea@gmail.com> - * @author Domingo Redal <docxml@gmail.com> * @author Zigor Astarbe <zigor@astarbe.com> * @author Adrián Ariza <adrian_ariza.ciudad.com.ar> * @author Gabiel Molina <gabriel191@gmail.com> diff --git a/inc/lang/fi/lang.php b/inc/lang/fi/lang.php index ebcca3347..a872b156f 100644 --- a/inc/lang/fi/lang.php +++ b/inc/lang/fi/lang.php @@ -2,7 +2,8 @@ /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) - * + * + * @author Tuomo Hartikainen <tuomo.hartikainen@heksia.fi> * @author Petteri <petteri@gmail.com> * @author Matti Pöllä <mpo@iki.fi> * @author Otto Vainio <otto@valjakko.net> @@ -71,43 +72,6 @@ $lang['badpassconfirm'] = 'Valitan. Salasana oli väärin'; $lang['minoredit'] = 'Pieni muutos'; $lang['draftdate'] = 'Luonnos tallennettu automaattisesti'; $lang['nosecedit'] = 'Sivu on muuttunut välillä ja kappaleen tiedot olivat vanhentuneet. Koko sivu ladattu.'; -$lang['regmissing'] = 'Kaikki kentät tulee täyttää.'; -$lang['reguexists'] = 'Käyttäjä tällä käyttäjänimellä on jo olemassa.'; -$lang['regsuccess'] = 'Käyttäjä luotiin ja salasana lähetettiin sähköpostilla.'; -$lang['regsuccess2'] = 'Käyttäjänimi on luotu.'; -$lang['regfail'] = 'Valitsemaasi käyttäjää ei voitu luoda.'; -$lang['regmailfail'] = 'Näyttää siltä, että salasanan lähettämisessä tapahtui virhe. Ota yhteys ylläpitäjään!'; -$lang['regbadmail'] = 'Antamasi sähköpostiosoite näyttää epäkelvolta. Jos pidät tätä virheenä ota yhteys ylläpitäjään.'; -$lang['regbadpass'] = 'Annetut kaksi salasanaa eivät täsmää. Yritä uudelleen.'; -$lang['regpwmail'] = 'DokuWiki salasanasi'; -$lang['reghere'] = 'Puuttuuko sinulta käyttäjätili? Hanki sellainen'; -$lang['profna'] = 'Tässä wikissä profiilien muokkaaminen ei ole mahdollista'; -$lang['profnochange'] = 'Ei muutoksia.'; -$lang['profnoempty'] = 'Tyhjä nimi tai sähköpostiosoite ei ole sallittu.'; -$lang['profchanged'] = 'Käyttäjän profiilin päivitys onnistui.'; -$lang['profnodelete'] = 'Tässä wikissä ei voi poistaa käyttäjiä'; -$lang['profdeleteuser'] = 'Poista tili'; -$lang['profdeleted'] = 'Käyttäjätilisi on postettu tästä wikistä'; -$lang['profconfdelete'] = 'Haluan poistaa käyttäjätilini tästä wikistä. <br/> Tätä toimintoa ei voi myöhemmin peruuttaa.'; -$lang['profconfdeletemissing'] = 'Vahvistus rastia ei valittu'; -$lang['pwdforget'] = 'Unohtuiko salasana? Hanki uusi'; -$lang['resendna'] = 'Tämä wiki ei tue salasanan uudelleenlähettämistä.'; -$lang['resendpwd'] = 'Aseta uusisalasana'; -$lang['resendpwdmissing'] = 'Kaikki kentät on täytettävä.'; -$lang['resendpwdnouser'] = 'Käyttäjää ei löydy tietokannastamme.'; -$lang['resendpwdbadauth'] = 'Tunnistuskoodi on virheellinen. Varmista, että käytit koko varmistuslinkkiä.'; -$lang['resendpwdconfirm'] = 'Varmistuslinkki on lähetetty sähköpostilla'; -$lang['resendpwdsuccess'] = 'Uusi salasanasi on lähetetty sähköpostilla.'; -$lang['license'] = 'Jollei muuta ole mainittu, niin sisältö tässä wikissä on lisensoitu seuraavalla lisenssillä:'; -$lang['licenseok'] = 'Huom: Muokkaamalla tätä sivua suostut lisensoimaan sisällön seuraavan lisenssin mukaisesti:'; -$lang['searchmedia'] = 'Etsi tiedostoa nimeltä:'; -$lang['searchmedia_in'] = 'Etsi kohteesta %s'; -$lang['txt_upload'] = 'Valitse tiedosto lähetettäväksi:'; -$lang['txt_filename'] = 'Lähetä nimellä (valinnainen):'; -$lang['txt_overwrt'] = 'Ylikirjoita olemassa oleva'; -$lang['maxuploadsize'] = 'Palvelimelle siirto max. %s / tiedosto.'; -$lang['lockedby'] = 'Tällä hetkellä tiedoston on lukinnut:'; -$lang['lockexpire'] = 'Lukitus päättyy:'; $lang['js']['willexpire'] = 'Lukituksesi tämän sivun muokkaukseen päättyy minuutin kuluttua.\nRistiriitojen välttämiseksi paina esikatselu-nappia nollataksesi lukitusajan.'; $lang['js']['notsavedyet'] = 'Dokumentissa on tallentamattomia muutoksia, jotka häviävät. Haluatko varmasti jatkaa?'; @@ -151,6 +115,43 @@ $lang['js']['media_done_btn'] = 'Valmis'; $lang['js']['media_drop'] = 'Pudota lähetettävät tiedostot tähän'; $lang['js']['media_cancel'] = 'Poista'; $lang['js']['media_overwrt'] = 'Ylikirjoita olemassa olevat tiedostot'; +$lang['regmissing'] = 'Kaikki kentät tulee täyttää.'; +$lang['reguexists'] = 'Käyttäjä tällä käyttäjänimellä on jo olemassa.'; +$lang['regsuccess'] = 'Käyttäjä luotiin ja salasana lähetettiin sähköpostilla.'; +$lang['regsuccess2'] = 'Käyttäjänimi on luotu.'; +$lang['regfail'] = 'Valitsemaasi käyttäjää ei voitu luoda.'; +$lang['regmailfail'] = 'Näyttää siltä, että salasanan lähettämisessä tapahtui virhe. Ota yhteys ylläpitäjään!'; +$lang['regbadmail'] = 'Antamasi sähköpostiosoite näyttää epäkelvolta. Jos pidät tätä virheenä ota yhteys ylläpitäjään.'; +$lang['regbadpass'] = 'Annetut kaksi salasanaa eivät täsmää. Yritä uudelleen.'; +$lang['regpwmail'] = 'DokuWiki salasanasi'; +$lang['reghere'] = 'Puuttuuko sinulta käyttäjätili? Hanki sellainen'; +$lang['profna'] = 'Tässä wikissä profiilien muokkaaminen ei ole mahdollista'; +$lang['profnochange'] = 'Ei muutoksia.'; +$lang['profnoempty'] = 'Tyhjä nimi tai sähköpostiosoite ei ole sallittu.'; +$lang['profchanged'] = 'Käyttäjän profiilin päivitys onnistui.'; +$lang['profnodelete'] = 'Tässä wikissä ei voi poistaa käyttäjiä'; +$lang['profdeleteuser'] = 'Poista tili'; +$lang['profdeleted'] = 'Käyttäjätilisi on postettu tästä wikistä'; +$lang['profconfdelete'] = 'Haluan poistaa käyttäjätilini tästä wikistä. <br/> Tätä toimintoa ei voi myöhemmin peruuttaa.'; +$lang['profconfdeletemissing'] = 'Vahvistus rastia ei valittu'; +$lang['pwdforget'] = 'Unohtuiko salasana? Hanki uusi'; +$lang['resendna'] = 'Tämä wiki ei tue salasanan uudelleenlähettämistä.'; +$lang['resendpwd'] = 'Aseta uusisalasana'; +$lang['resendpwdmissing'] = 'Kaikki kentät on täytettävä.'; +$lang['resendpwdnouser'] = 'Käyttäjää ei löydy tietokannastamme.'; +$lang['resendpwdbadauth'] = 'Tunnistuskoodi on virheellinen. Varmista, että käytit koko varmistuslinkkiä.'; +$lang['resendpwdconfirm'] = 'Varmistuslinkki on lähetetty sähköpostilla'; +$lang['resendpwdsuccess'] = 'Uusi salasanasi on lähetetty sähköpostilla.'; +$lang['license'] = 'Jollei muuta ole mainittu, niin sisältö tässä wikissä on lisensoitu seuraavalla lisenssillä:'; +$lang['licenseok'] = 'Huom: Muokkaamalla tätä sivua suostut lisensoimaan sisällön seuraavan lisenssin mukaisesti:'; +$lang['searchmedia'] = 'Etsi tiedostoa nimeltä:'; +$lang['searchmedia_in'] = 'Etsi kohteesta %s'; +$lang['txt_upload'] = 'Valitse tiedosto lähetettäväksi:'; +$lang['txt_filename'] = 'Lähetä nimellä (valinnainen):'; +$lang['txt_overwrt'] = 'Ylikirjoita olemassa oleva'; +$lang['maxuploadsize'] = 'Palvelimelle siirto max. %s / tiedosto.'; +$lang['lockedby'] = 'Tällä hetkellä tiedoston on lukinnut:'; +$lang['lockexpire'] = 'Lukitus päättyy:'; $lang['rssfailed'] = 'Virhe tapahtui noudettaessa tätä syötettä: '; $lang['nothingfound'] = 'Mitään ei löytynyt.'; $lang['mediaselect'] = 'Mediatiedoston valinta'; @@ -338,5 +339,5 @@ $lang['searchresult'] = 'Haun tulokset'; $lang['plainhtml'] = 'pelkkä HTML'; $lang['wikimarkup'] = 'Wiki markup'; $lang['unable_to_parse_date'] = 'Parametrin "%s" jäsennys ei onnistu.'; -$lang['email_signature_text'] = 'Tämän postin loi DokuWiki osoitteessa +$lang['email_signature_text'] = 'Tämän postin loi DokuWiki osoitteessa @DOKUWIKIURL@'; diff --git a/inc/lang/fr/lang.php b/inc/lang/fr/lang.php index b43e2fc94..2480f2936 100644 --- a/inc/lang/fr/lang.php +++ b/inc/lang/fr/lang.php @@ -3,8 +3,8 @@ /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * + * @author Schplurtz le Déboulonné <Schplurtz@laposte.net> * @author PaliPalo <palipalo@hotmail.fr> - * @author Schplurtz le Déboulonné <schplurtz@laposte.net> * @author Laurent Ponthieu <contact@coopindus.fr> * @author Damien Regad <dregad@mantisbt.org> * @author Michael Bohn <mjbohn@gmail.com> @@ -304,10 +304,10 @@ $lang['img_camera'] = 'Appareil photo:'; $lang['img_keywords'] = 'Mots-clés:'; $lang['img_width'] = 'Largeur:'; $lang['img_height'] = 'Hauteur:'; -$lang['subscr_subscribe_success'] = '%s a été ajouté à la liste des abonnés de %s'; +$lang['subscr_subscribe_success'] = '%s a été ajouté à la liste des abonnés à %s'; $lang['subscr_subscribe_error'] = 'Erreur à l\'ajout de %s à la liste des abonnés de %s'; $lang['subscr_subscribe_noaddress'] = 'Il n\'y a pas d\'adresse associée à votre identifiant, vous ne pouvez pas être ajouté à la liste des abonnés.'; -$lang['subscr_unsubscribe_success'] = '%s a été supprimé de la liste des abonnés de %s'; +$lang['subscr_unsubscribe_success'] = '%s a été supprimé de la liste des abonnés à %s'; $lang['subscr_unsubscribe_error'] = 'Erreur au retrait de %s de la liste des abonnés de %s'; $lang['subscr_already_subscribed'] = '%s est déjà abonné à %s'; $lang['subscr_not_subscribed'] = '%s n\'est pas abonné à %s'; diff --git a/inc/lang/id/lang.php b/inc/lang/id/lang.php index dc9d66259..61f279839 100644 --- a/inc/lang/id/lang.php +++ b/inc/lang/id/lang.php @@ -3,6 +3,7 @@ /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * + * @author rusly-id <rusly-id@users.noreply.github.com> * @author mubaidillah <mubaidillah@gmail.com> * @author Irwan Butar Butar <irwansah.putra@gmail.com> * @author Yustinus Waruwu <juswaruwu@gmail.com> @@ -64,43 +65,6 @@ $lang['badlogin'] = 'Maaf, username atau password salah.'; $lang['badpassconfirm'] = 'Maaf, password salah'; $lang['minoredit'] = 'Perubahan Minor'; $lang['draftdate'] = 'Simpan draft secara otomatis'; -$lang['regmissing'] = 'Maaf, Anda harus mengisi semua field.'; -$lang['reguexists'] = 'Maaf, user dengan user login ini telah ada.'; -$lang['regsuccess'] = 'User telah didaftarkan dan password telah dikirim ke email Anda.'; -$lang['regsuccess2'] = 'User telah dibuatkan.'; -$lang['regmailfail'] = 'Kami menemukan kesalahan saat mengirimkan password ke alamat email Anda. Mohon hubungi administrator.'; -$lang['regbadmail'] = 'Alamat email yang Anda masukkan tidak valid - jika menurut Anda hal ini adalah kesalahan sistem, mohon hubungi admin.'; -$lang['regbadpass'] = 'Passwod yang dimasukkan tidak sama. Silahkan ulangi lagi.'; -$lang['regpwmail'] = 'Password DokuWiki Anda'; -$lang['reghere'] = 'Anda belum mempunyai account? silahkan '; -$lang['profna'] = 'Wiki ini tidak mengijinkan perubahan profil.'; -$lang['profnochange'] = 'Tidak ada perubahan.'; -$lang['profnoempty'] = 'Mohon mengisikan nama atau alamat email.'; -$lang['profchanged'] = 'Profil User berhasil diubah.'; -$lang['profnodelete'] = 'Wiki ini tidak mendukung penghapusan pengguna'; -$lang['profdeleteuser'] = 'Hapus Akun'; -$lang['profdeleted'] = 'Akun anda telah dihapus dari wiki ini'; -$lang['profconfdelete'] = 'Saya berharap menghapus akun saya dari wiki ini. -Aksi ini tidak bisa diselesaikan.'; -$lang['profconfdeletemissing'] = 'Knfirmasi check box tidak tercentang'; -$lang['pwdforget'] = 'Lupa Password? Dapatkan yang baru'; -$lang['resendna'] = 'Wiki ini tidak mendukung pengiriman ulang password.'; -$lang['resendpwd'] = 'Atur password baru'; -$lang['resendpwdmissing'] = 'Maaf, Anda harus mengisikan semua field.'; -$lang['resendpwdnouser'] = 'Maaf, user ini tidak ditemukan.'; -$lang['resendpwdbadauth'] = 'Maaf, kode autentikasi tidak valid. Pastikan Anda menggunakan keseluruhan link konfirmasi.'; -$lang['resendpwdconfirm'] = 'Link konfirmasi telah dikirim melalui email.'; -$lang['resendpwdsuccess'] = 'Password baru Anda telah dikirim melalui email.'; -$lang['license'] = 'Kecuali jika dinyatakan lain, konten pada wiki ini dilisensikan dibawah lisensi berikut:'; -$lang['licenseok'] = 'Catatan: Dengan menyunting halaman ini, Anda setuju untuk melisensikan konten Anda dibawah lisensi berikut:'; -$lang['searchmedia'] = 'Cari nama file:'; -$lang['searchmedia_in'] = 'Cari di %s'; -$lang['txt_upload'] = 'File yang akan diupload:'; -$lang['txt_filename'] = 'Masukkan nama wiki (opsional):'; -$lang['txt_overwrt'] = 'File yang telah ada akan ditindih'; -$lang['maxuploadsize'] = 'Unggah maks. %s per berkas'; -$lang['lockedby'] = 'Sedang dikunci oleh:'; -$lang['lockexpire'] = 'Penguncian artikel sampai dengan:'; $lang['js']['willexpire'] = 'Halaman yang sedang Anda kunci akan berakhir dalam waktu kurang lebih satu menit.\nUntuk menghindari konflik, gunakan tombol Preview untuk me-reset timer pengunci.'; $lang['js']['notsavedyet'] = 'Perubahan yang belum disimpan akan hilang.\nYakin akan dilanjutkan?'; $lang['js']['searchmedia'] = 'Cari file'; @@ -142,6 +106,43 @@ $lang['js']['media_done_btn'] = 'Selesai'; $lang['js']['media_drop'] = 'Tarik file disini untuk mengunggah'; $lang['js']['media_cancel'] = 'Buang'; $lang['js']['media_overwrt'] = 'Timpa berkas yang ada'; +$lang['regmissing'] = 'Maaf, Anda harus mengisi semua field.'; +$lang['reguexists'] = 'Maaf, user dengan user login ini telah ada.'; +$lang['regsuccess'] = 'User telah didaftarkan dan password telah dikirim ke email Anda.'; +$lang['regsuccess2'] = 'User telah dibuatkan.'; +$lang['regmailfail'] = 'Kami menemukan kesalahan saat mengirimkan password ke alamat email Anda. Mohon hubungi administrator.'; +$lang['regbadmail'] = 'Alamat email yang Anda masukkan tidak valid - jika menurut Anda hal ini adalah kesalahan sistem, mohon hubungi admin.'; +$lang['regbadpass'] = 'Passwod yang dimasukkan tidak sama. Silahkan ulangi lagi.'; +$lang['regpwmail'] = 'Password DokuWiki Anda'; +$lang['reghere'] = 'Anda belum mempunyai account? silahkan '; +$lang['profna'] = 'Wiki ini tidak mengijinkan perubahan profil.'; +$lang['profnochange'] = 'Tidak ada perubahan.'; +$lang['profnoempty'] = 'Mohon mengisikan nama atau alamat email.'; +$lang['profchanged'] = 'Profil User berhasil diubah.'; +$lang['profnodelete'] = 'Wiki ini tidak mendukung penghapusan pengguna'; +$lang['profdeleteuser'] = 'Hapus Akun'; +$lang['profdeleted'] = 'Akun anda telah dihapus dari wiki ini'; +$lang['profconfdelete'] = 'Saya berharap menghapus akun saya dari wiki ini. +Aksi ini tidak bisa diselesaikan.'; +$lang['profconfdeletemissing'] = 'Knfirmasi check box tidak tercentang'; +$lang['pwdforget'] = 'Lupa Password? Dapatkan yang baru'; +$lang['resendna'] = 'Wiki ini tidak mendukung pengiriman ulang password.'; +$lang['resendpwd'] = 'Atur password baru'; +$lang['resendpwdmissing'] = 'Maaf, Anda harus mengisikan semua field.'; +$lang['resendpwdnouser'] = 'Maaf, user ini tidak ditemukan.'; +$lang['resendpwdbadauth'] = 'Maaf, kode autentikasi tidak valid. Pastikan Anda menggunakan keseluruhan link konfirmasi.'; +$lang['resendpwdconfirm'] = 'Link konfirmasi telah dikirim melalui email.'; +$lang['resendpwdsuccess'] = 'Password baru Anda telah dikirim melalui email.'; +$lang['license'] = 'Kecuali jika dinyatakan lain, konten pada wiki ini dilisensikan dibawah lisensi berikut:'; +$lang['licenseok'] = 'Catatan: Dengan menyunting halaman ini, Anda setuju untuk melisensikan konten Anda dibawah lisensi berikut:'; +$lang['searchmedia'] = 'Cari nama file:'; +$lang['searchmedia_in'] = 'Cari di %s'; +$lang['txt_upload'] = 'File yang akan diupload:'; +$lang['txt_filename'] = 'Masukkan nama wiki (opsional):'; +$lang['txt_overwrt'] = 'File yang telah ada akan ditindih'; +$lang['maxuploadsize'] = 'Unggah maks. %s per berkas'; +$lang['lockedby'] = 'Sedang dikunci oleh:'; +$lang['lockexpire'] = 'Penguncian artikel sampai dengan:'; $lang['rssfailed'] = 'Error terjadi saat mengambil feed: '; $lang['nothingfound'] = 'Tidak menemukan samasekali.'; $lang['mediaselect'] = 'Pilihan Mediafile'; @@ -308,5 +309,5 @@ $lang['media_restore'] = 'Kembalikan versi ini'; $lang['currentns'] = 'Namespace saat ini'; $lang['searchresult'] = 'Hasil Pencarian'; $lang['wikimarkup'] = 'Markah Wiki'; -$lang['email_signature_text'] = 'Email ini dibuat otomatis oleh DokuWiki +$lang['email_signature_text'] = 'Email ini dibuat otomatis oleh DokuWiki @DOKUWIKIURL@'; diff --git a/inc/lang/ja/lang.php b/inc/lang/ja/lang.php index 17b410a43..968edf4eb 100644 --- a/inc/lang/ja/lang.php +++ b/inc/lang/ja/lang.php @@ -97,10 +97,10 @@ $lang['js']['mediaoriginal'] = 'オリジナルのサイズ'; $lang['js']['medialnk'] = '詳細ページへのリンク'; $lang['js']['mediadirect'] = 'オリジナルへの直リンク'; $lang['js']['medianolnk'] = 'リンク無し'; -$lang['js']['medianolink'] = 'イメージをリンクしない'; -$lang['js']['medialeft'] = 'イメージを左に寄せる'; -$lang['js']['mediaright'] = 'イメージを右に寄せる'; -$lang['js']['mediacenter'] = 'イメージを中央に寄せる'; +$lang['js']['medianolink'] = '画像へリンクしない'; +$lang['js']['medialeft'] = '画像を左に寄せる'; +$lang['js']['mediaright'] = '画像を右に寄せる'; +$lang['js']['mediacenter'] = '画像を中央に寄せる'; $lang['js']['medianoalign'] = '位置を設定しない'; $lang['js']['nosmblinks'] = 'Windows の共有フォルダへリンクは Microsoft Internet Explorer でしか機能しませんが、リンクをコピーして貼り付けることは可能です。'; $lang['js']['linkwiz'] = 'リンクウィザード'; @@ -124,9 +124,9 @@ $lang['search_contains'] = '部分一致'; $lang['search_custom_match'] = 'カスタム'; $lang['search_any_ns'] = '全ての名前空間'; $lang['search_any_time'] = '全期間'; -$lang['search_past_7_days'] = 'この1週間'; -$lang['search_past_month'] = 'この1ヶ月'; -$lang['search_past_year'] = 'この1年'; +$lang['search_past_7_days'] = '1週間以内'; +$lang['search_past_month'] = '1カ月以内'; +$lang['search_past_year'] = '1年以内'; $lang['search_sort_by_hits'] = 'ヒット数順に並べる'; $lang['search_sort_by_mtime'] = '最終更新順に並べる'; $lang['regmissing'] = 'お手数ですが、全ての項目を入力してください。'; diff --git a/inc/lang/ja/locked.txt b/inc/lang/ja/locked.txt index 89d5f7ed6..d501af456 100644 --- a/inc/lang/ja/locked.txt +++ b/inc/lang/ja/locked.txt @@ -1,3 +1,3 @@ ====== 文書ロック中 ====== -この文書は、他のユーザーが編集中のためロックされています。編集が完了するか、ロックの期限が切れるのを待って下さい。 +この文書は、他のユーザーが編集中のためロックされています。編集が完了するか、ロックの期限が切れるまでお待ち下さい。 diff --git a/inc/lang/ja/mailtext.txt b/inc/lang/ja/mailtext.txt index 8d1daed86..7ea2636f2 100644 --- a/inc/lang/ja/mailtext.txt +++ b/inc/lang/ja/mailtext.txt @@ -1,12 +1,14 @@ DokuWiki 内の文書が追加もしくは変更されました。詳細は以下の通りです。 -日付 : @DATE@ -ブラウザ : @BROWSER@ -IPアドレス : @IPADDRESS@ -ホスト名 : @HOSTNAME@ -前リビジョン: @OLDPAGE@ -新リビジョン: @NEWPAGE@ -編集の概要: @SUMMARY@ -ユーザー名 : @USER@ +ブラウザ : @BROWSER@ +IPアドレス : @IPADDRESS@ +ホスト名 : @HOSTNAME@ +前リビジョン : @OLDPAGE@ +新リビジョン : @NEWPAGE@ +新リビジョンの日付: @DATE@ +編集の概要 : @SUMMARY@ +ユーザー名 : @USER@ + +この通知の後、新たなリビジョンが追加されている可能性があります。その場合、本リビジョンのページ上部にメッセージが表示されます。 @DIFF@ diff --git a/inc/lang/ja/preview.txt b/inc/lang/ja/preview.txt index f9001f01c..c0fe39d86 100644 --- a/inc/lang/ja/preview.txt +++ b/inc/lang/ja/preview.txt @@ -1,3 +1,3 @@ ====== プレビュー ====== -編集中の文書のプレビューです。確認用なので**保存されていない**ことに注意してください。 +編集中の文書のプレビューです。**まだ保存されていませんのでご注意下さい。**
\ No newline at end of file diff --git a/inc/lang/ja/registermail.txt b/inc/lang/ja/registermail.txt index ad5241a0b..5a2484087 100644 --- a/inc/lang/ja/registermail.txt +++ b/inc/lang/ja/registermail.txt @@ -1,10 +1,10 @@ 新しいユーザーが登録されました。ユーザー情報は以下の通りです。 -ユーザー名 : @NEWUSER@ -フルネーム : @NEWNAME@ -メールアドレス : @NEWEMAIL@ +ユーザー名 : @NEWUSER@ +フルネーム : @NEWNAME@ +メールアドレス: @NEWEMAIL@ -登録日 : @DATE@ -ブラウザ : @BROWSER@ -IPアドレス : @IPADDRESS@ -ホスト名 : @HOSTNAME@ +登録日 : @DATE@ +ブラウザ : @BROWSER@ +IPアドレス : @IPADDRESS@ +ホスト名 : @HOSTNAME@ diff --git a/inc/lang/ja/resetpwd.txt b/inc/lang/ja/resetpwd.txt index a538f18f9..b28bc3763 100644 --- a/inc/lang/ja/resetpwd.txt +++ b/inc/lang/ja/resetpwd.txt @@ -1,3 +1,3 @@ ====== 新しいパスワードを設定 ====== -このWikiでお使いのアカウント用の新しいパスワードを入力して下さい +このWikiでお使いのアカウント用の新しいパスワードを入力して下さい。 diff --git a/inc/lang/ja/subscr_digest.txt b/inc/lang/ja/subscr_digest.txt index 026a2fe89..7315b7622 100644 --- a/inc/lang/ja/subscr_digest.txt +++ b/inc/lang/ja/subscr_digest.txt @@ -1,6 +1,6 @@ こんにちは。 -@TITLE@ 内のページ @PAGE@ は変更されました。 +@TITLE@ 内のページ @PAGE@ に変更がありました。 変更点は以下の通りです: -------------------------------------------------------- @@ -12,6 +12,5 @@ この通知を解除するには次のウィキへログインし @DOKUWIKIURL@ -その後、 +その後、以下のページからページと名前空間の変更に対する購読を解除してください。 @SUBSCRIBE@ -ページと名前空間の変更に対する購読を解除してください。 diff --git a/inc/lang/ja/subscr_list.txt b/inc/lang/ja/subscr_list.txt index dbe37c7f8..3b841ea4b 100644 --- a/inc/lang/ja/subscr_list.txt +++ b/inc/lang/ja/subscr_list.txt @@ -1,15 +1,13 @@ こんにちは。 -@TITLE@ の 名前空間 @PAGE@ にあるページが変更されました。 +@TITLE@ の 名前空間 @PAGE@ にあるページに変更がありました。 変更点は以下の通りです: -------------------------------------------------------- @DIFF@ -------------------------------------------------------- - この通知を解除するには次のウィキへログインし @DOKUWIKIURL@ -その後、 +その後、以下のページからページと名前空間の変更に対する購読を解除してください。 @SUBSCRIBE@ -ページと名前空間の変更に対する購読を解除してください。 diff --git a/inc/lang/ja/subscr_single.txt b/inc/lang/ja/subscr_single.txt index 4dac31e02..68190bfc4 100644 --- a/inc/lang/ja/subscr_single.txt +++ b/inc/lang/ja/subscr_single.txt @@ -1,20 +1,19 @@ こんにちは。 -@TITLE@ のウィキにあるページ @PAGE@ が変更されました。 +@TITLE@ のウィキにあるページ @PAGE@ に変更がありました。 変更点は以下の通りです: -------------------------------------------------------- @DIFF@ -------------------------------------------------------- -日付 : @DATE@ -ユーザー : @USER@ -変更概要: @SUMMARY@ -古いリビジョン: @OLDPAGE@ -新しいリビジョン: @NEWPAGE@ +ユーザー : @USER@ +編集の概要 : @SUMMARY@ +古いリビジョン : @OLDPAGE@ +新しいリビジョン : @NEWPAGE@ +新しいリビジョンの日付: @DATE@ この通知を解除するには次のウィキへログインし @DOKUWIKIURL@ -その後、 +その後、以下のページからページと名前空間の変更に対する購読を解除してください。 @SUBSCRIBE@ -ページと名前空間の変更に対する購読を解除してください。 diff --git a/inc/lang/ja/updateprofile.txt b/inc/lang/ja/updateprofile.txt index f3fb01e3c..21096970b 100644 --- a/inc/lang/ja/updateprofile.txt +++ b/inc/lang/ja/updateprofile.txt @@ -1,3 +1,3 @@ ====== アカウント情報更新 ====== -変更したい項目のみ入力して下さい。ユーザー名は変更できません。 +変更したい項目のみ書き換えて下さい。ユーザー名は変更できません。 diff --git a/inc/lang/ja/uploadmail.txt b/inc/lang/ja/uploadmail.txt index 8734c91df..11fa1a46d 100644 --- a/inc/lang/ja/uploadmail.txt +++ b/inc/lang/ja/uploadmail.txt @@ -1,10 +1,11 @@ お使いのDokuWikiにファイルがアップロードされました。詳細は以下の通りです。 -ファイル : @MEDIA@ -日付 : @DATE@ -ブラウザ : @BROWSER@ -IPアドレス : @IPADDRESS@ -ホスト名 : @HOSTNAME@ -サイズ : @SIZE@ -MIMEタイプ : @MIME@ -ユーザー名 : @USER@ +ファイル : @MEDIA@ +古い版 : @OLD@ +日付 : @DATE@ +ブラウザ : @BROWSER@ +IPアドレス: @IPADDRESS@ +ホスト名 : @HOSTNAME@ +サイズ : @SIZE@ +MIMEタイプ: @MIME@ +ユーザー名: @USER@ diff --git a/inc/lang/nl/lang.php b/inc/lang/nl/lang.php index 2cd368639..f799318c3 100644 --- a/inc/lang/nl/lang.php +++ b/inc/lang/nl/lang.php @@ -3,6 +3,7 @@ /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * + * @author PBU <pbu@xs4all.nl> * @author Gerrit Uitslag <klapinklapin@gmail.com> * @author Andy <astolker@icloud.com> * @author Harriet Neitz <harrietneitz@gmail.com> diff --git a/inc/lang/no/lang.php b/inc/lang/no/lang.php index 2cb6839fa..0b4cd8629 100644 --- a/inc/lang/no/lang.php +++ b/inc/lang/no/lang.php @@ -3,8 +3,8 @@ /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * - * @author Rut Kristin Aanestad <dark@met.no> * @author Torgeir Blesvik <bletor@banenor.no> + * @author Rut Kristin Aanestad <dark@met.no> * @author ThorPrestboen <thor.erling.prestboen@gmail.com> * @author Christian McKenna <mckchr@banenor.no> * @author Reidar Mosvold <Reidar.Mosvold@hit.no> @@ -90,6 +90,7 @@ $lang['minoredit'] = 'Mindre endringer'; $lang['draftdate'] = 'Kladd autolagret'; $lang['nosecedit'] = 'Siden er i mellomtiden endret, seksjonsinfo har blitt foreldet - lastet full side istedet.'; $lang['searchcreatepage'] = 'Hvis du ikke fant det du søkte etter kan du lage eller endre siden %s.'; +$lang['js']['search_toggle_tools'] = 'Søkeverktøy'; $lang['js']['willexpire'] = 'Din redigeringslås for dette dokumentet kommer snart til å utløpe.\nFor å unngå versjonskonflikter bør du forhåndsvise dokumentet ditt for å forlenge redigeringslåsen.'; $lang['js']['notsavedyet'] = 'Ulagrede endringer vil gå tapt! Vil du fortsette?'; diff --git a/inc/lang/pt-br/lang.php b/inc/lang/pt-br/lang.php index da698a90b..c88049e1b 100644 --- a/inc/lang/pt-br/lang.php +++ b/inc/lang/pt-br/lang.php @@ -3,6 +3,7 @@ /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * + * @author Schopf <pschopf@gmail.com> * @author Frederico Gonçalves Guimarães <frederico@teia.bio.br> * @author Márcio Gomes Gonçalves <gomes@metha.com.br> * @author Luis Fernando Enciso <lfenciso@certto.com.br> diff --git a/inc/lang/pt/lang.php b/inc/lang/pt/lang.php index 3ac1596f2..5c01a073e 100644 --- a/inc/lang/pt/lang.php +++ b/inc/lang/pt/lang.php @@ -3,8 +3,8 @@ /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * + * @author Schopf <pschopf@gmail.com> * @author Mario AlexandTeixeira dos Santos <masterofclan@gmail.com> - * @author Paulo Ricardo Schopf <pschopf@gmail.com> * @author Maykon Oliveira <maykonoliveira850@gmail.com> * @author José Vieira <jmsv63@gmail.com> * @author José Carlos Monteiro <jose.c.monteiro@netcabo.pt> diff --git a/inc/lang/ru/lang.php b/inc/lang/ru/lang.php index 5e6277dfa..350e7d926 100644 --- a/inc/lang/ru/lang.php +++ b/inc/lang/ru/lang.php @@ -4,10 +4,10 @@ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * * @author Aleksandr Selivanov <alexgearbox@yandex.ru> + * @author Yuriy Skalko <yuriy.skalko@gmail.com> * @author Alexander Kh. <001.arx@gmail.com> * @author Vyacheslav Strenadko <vyacheslav.strenadko@gmail.com> * @author Wolterhon <hotmottot.1@gmail.com> - * @author Yuriy Skalko <yuriy.skalko@gmail.com> * @author Zhassulan <zyesmukanov@gmail.com> * @author Yuri Pimenov <up@ftpsearch.lv> * @author Igor Tarasov <tigr@mail15.com> @@ -214,7 +214,7 @@ $lang['accessdenied'] = 'Вы не можете просмотреть $lang['mediausage'] = 'Для ссылки на этот файл используйте следующий синтаксис:'; $lang['mediaview'] = 'Посмотреть исходный файл'; $lang['mediaroot'] = 'корень'; -$lang['mediaupload'] = 'Загрузка файла в текущее пространство имён. Для создания подпространства имён, добавьте его название перед именем файла через двоеточие. Поддерживается drag-and-drop.'; +$lang['mediaupload'] = 'Загрузка файла в текущее пространство имён. Для создания подпространства имён добавьте его название перед именем файла через двоеточие. Поддерживается drag-and-drop (перетащить-и-оставить).'; $lang['mediaextchange'] = 'Расширение изменилось с .%s на .%s!'; $lang['reference'] = 'Ссылки для'; $lang['ref_inuse'] = 'Этот файл не может быть удалён, так как он используется на следующих страницах:'; @@ -257,7 +257,7 @@ $lang['mail_newpage'] = 'страница добавлена:'; $lang['mail_changed'] = 'страница изменена:'; $lang['mail_subscribe_list'] = 'изменились страницы в пространстве имён:'; $lang['mail_new_user'] = 'новый пользователь:'; -$lang['mail_upload'] = 'файл закачан:'; +$lang['mail_upload'] = 'файл загружен:'; $lang['changes_type'] = 'Посмотреть изменения'; $lang['pages_changes'] = 'страниц'; $lang['media_changes'] = 'медиафайлов'; diff --git a/inc/lang/ru/uploadmail.txt b/inc/lang/ru/uploadmail.txt index a92d85564..7c342a702 100644 --- a/inc/lang/ru/uploadmail.txt +++ b/inc/lang/ru/uploadmail.txt @@ -1,4 +1,4 @@ -В вашу вики был закачан файл. Подробная информация: +В вашу вики был загружен файл. Подробная информация: Файл: @MEDIA@ Старая версия: @OLD@ diff --git a/inc/lang/sk/lang.php b/inc/lang/sk/lang.php index b15b94133..83c160b60 100644 --- a/inc/lang/sk/lang.php +++ b/inc/lang/sk/lang.php @@ -8,6 +8,7 @@ * auth.class language support * installer strings * + * @author Peter Mydliar <peto.mydliar@gmail.com> * @author Martin Michalek <michalek.dev@gmail.com> * @author Ondrej Vegh <ov@vsieti.sk> with help of the scholars from Zdruzena stredna skola polygraficka in Bratislava * @author Michal Mesko <michal.mesko@gmail.com> diff --git a/inc/lang/sr/admin.txt b/inc/lang/sr/admin.txt index 8798fd4a6..284410b4b 100644 --- a/inc/lang/sr/admin.txt +++ b/inc/lang/sr/admin.txt @@ -1,3 +1,3 @@ ====== Администрација ====== -Изпод се налази листа доступних администраторских опција у DokuWiki-ју. +Испод се налази листа доступних администраторских опција у DokuWiki-ју. diff --git a/inc/mail.php b/inc/mail.php index 12a669dbe..429976d72 100644 --- a/inc/mail.php +++ b/inc/mail.php @@ -308,7 +308,7 @@ function mail_quotedprintable_encode($sText,$maxlen=74,$bEmulate_imap_8bit=true) // encode x09,x20 at lineends { $iLength = strlen($sLine); - $iLastChar = ord($sLine{$iLength-1}); + $iLastChar = ord($sLine[$iLength-1]); // !!!!!!!! // imap_8_bit does not encode x20 at the very end of a text, @@ -317,7 +317,7 @@ function mail_quotedprintable_encode($sText,$maxlen=74,$bEmulate_imap_8bit=true) // or comment next line for RFC2045 conformance, if you like if (!($bEmulate_imap_8bit && ($i==count($aLines)-1))){ if (($iLastChar==0x09)||($iLastChar==0x20)) { - $sLine{$iLength-1}='='; + $sLine[$iLength-1]='='; $sLine .= ($iLastChar==0x09)?'09':'20'; } } diff --git a/inc/media.php b/inc/media.php index 41fbc0b8f..cc29bd16c 100644 --- a/inc/media.php +++ b/inc/media.php @@ -544,7 +544,7 @@ function media_upload_finish($fn_tmp, $fn, $id, $imime, $overwrite, $move = 'mov // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.) chmod($fn, $conf['fmode']); msg($lang['uploadsucc'],1); - media_notify($id,$fn,$imime,$old); + media_notify($id,$fn,$imime,$old,$new); // add a log entry to the media changelog $filesize_new = filesize($fn); $sizechange = $filesize_new - $filesize_old; @@ -672,12 +672,12 @@ function media_contentcheck($file,$mime){ * @param bool|int $old_rev revision timestamp or false * @return bool */ -function media_notify($id,$file,$mime,$old_rev=false){ +function media_notify($id,$file,$mime,$old_rev=false,$current_rev=false){ global $conf; if(empty($conf['notify'])) return false; //notify enabled? $subscription = new MediaSubscriptionSender(); - return $subscription->sendMediaDiff($conf['notify'], 'uploadmail', $id, $old_rev); + return $subscription->sendMediaDiff($conf['notify'], 'uploadmail', $id, $old_rev, $current_rev); } /** @@ -2082,7 +2082,7 @@ function media_resize_image($file, $ext, $w, $h=0){ media_resize_imageIM($ext, $file, $info[0], $info[1], $local, $w, $h) || media_resize_imageGD($ext, $file, $info[0], $info[1], $local, $w, $h) ) { - if(!empty($conf['fperm'])) @chmod($local, $conf['fperm']); + if($conf['fperm']) @chmod($local, $conf['fperm']); return $local; } //still here? resizing failed @@ -2149,7 +2149,7 @@ function media_crop_image($file, $ext, $w, $h=0){ if( $mtime > @filemtime($file) || media_crop_imageIM($ext,$file,$info[0],$info[1],$local,$cw,$ch,$cx,$cy) || media_resize_imageGD($ext,$file,$cw,$ch,$local,$cw,$ch,$cx,$cy) ){ - if(!empty($conf['fperm'])) @chmod($local, $conf['fperm']); + if($conf['fperm']) @chmod($local, $conf['fperm']); return media_resize_image($local,$ext, $w, $h); } diff --git a/inc/pageutils.php b/inc/pageutils.php index bc39a6fef..94d8b825a 100644 --- a/inc/pageutils.php +++ b/inc/pageutils.php @@ -464,7 +464,7 @@ function resolve_id($ns,$id,$clean=true){ // if the id starts with a dot we need to handle the // relative stuff - if($id && $id{0} == '.'){ + if($id && $id[0] == '.'){ // normalize initial dots without a colon $id = preg_replace('/^(\.+)(?=[^:\.])/','\1:',$id); // prepend the current namespace @@ -619,7 +619,7 @@ function resolve_pageid($ns,&$page,&$exists,$rev='',$date_at=false ){ function getCacheName($data,$ext=''){ global $conf; $md5 = md5($data); - $file = $conf['cachedir'].'/'.$md5{0}.'/'.$md5.$ext; + $file = $conf['cachedir'].'/'.$md5[0].'/'.$md5.$ext; io_makeFileDir($file); return $file; } diff --git a/inc/parser/handler.php b/inc/parser/handler.php index 19c2aafe8..e5f7c4ed5 100644 --- a/inc/parser/handler.php +++ b/inc/parser/handler.php @@ -44,11 +44,17 @@ class Doku_Handler { * @param mixed $args arguments for this call * @param int $pos byte position in the original source file */ - protected function addCall($handler, $args, $pos) { + public function addCall($handler, $args, $pos) { $call = array($handler,$args, $pos); $this->callWriter->writeCall($call); } + /** @deprecated 2019-10-31 use addCall() instead */ + public function _addCall($handler, $args, $pos) { + dbg_deprecated('addCall'); + $this->addCall($handler, $args, $pos); + } + /** * Similar to addCall, but adds a plugin call * @@ -58,7 +64,7 @@ class Doku_Handler { * @param int $pos byte position in the original source file * @param string $match matched syntax */ - protected function addPluginCall($plugin, $args, $state, $pos, $match) { + public function addPluginCall($plugin, $args, $state, $pos, $match) { $call = array('plugin',array($plugin, $args, $state, $match), $pos); $this->callWriter->writeCall($call); } diff --git a/inc/parser/metadata.php b/inc/parser/metadata.php index fa64ae2ec..849fffe8d 100644 --- a/inc/parser/metadata.php +++ b/inc/parser/metadata.php @@ -64,7 +64,7 @@ class Doku_Renderer_metadata extends Doku_Renderer $this->headers = array(); // external pages are missing create date - if (!$this->persistent['date']['created']) { + if (!isset($this->persistent['date']['created']) || !$this->persistent['date']['created']) { $this->persistent['date']['created'] = filectime(wikiFN($ID)); } if (!isset($this->persistent['user'])) { diff --git a/inc/parser/parser.php b/inc/parser/parser.php index d9fc5fb8f..aee82f01d 100644 --- a/inc/parser/parser.php +++ b/inc/parser/parser.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\Debug\PropertyDeprecationHelper; + /** * Define various types of modes used by the parser - they are used to * populate the list of modes another mode accepts @@ -48,10 +50,50 @@ $PARSER_MODES = array( * @deprecated 2018-05-04 */ class Doku_Parser extends \dokuwiki\Parsing\Parser { + use PropertyDeprecationHelper { + __set as protected deprecationHelperMagicSet; + __get as protected deprecationHelperMagicGet; + } /** @inheritdoc */ - public function __construct(Doku_Handler $handler) { + public function __construct(Doku_Handler $handler = null) { dbg_deprecated(\dokuwiki\Parsing\Parser::class); + $this->deprecatePublicProperty('modes', __CLASS__); + $this->deprecatePublicProperty('connected', __CLASS__); + + if ($handler === null) { + $handler = new Doku_Handler(); + } + parent::__construct($handler); } + + public function __set($name, $value) + { + + if ($name === 'Handler') { + $this->handler = $value; + return; + } + + if ($name === 'Lexer') { + $this->lexer = $value; + return; + } + + $this->deprecationHelperMagicSet($name, $value); + } + + public function __get($name) + { + if ($name === 'Handler') { + return $this->handler; + } + + if ($name === 'Lexer') { + return $this->lexer; + } + + return $this->deprecationHelperMagicGet($name); + } } diff --git a/inc/parser/renderer.php b/inc/parser/renderer.php index 9720cfd90..a03b84c8e 100644 --- a/inc/parser/renderer.php +++ b/inc/parser/renderer.php @@ -866,7 +866,7 @@ abstract class Doku_Renderer extends Plugin { //use placeholders $url = str_replace('{URL}', rawurlencode($reference), $url); //wiki names will be cleaned next, otherwise urlencode unsafe chars - $url = str_replace('{NAME}', ($url{0} === ':') ? $reference : + $url = str_replace('{NAME}', ($url[0] === ':') ? $reference : preg_replace_callback('/[[\\\\\]^`{|}#%]/', function($match) { return rawurlencode($match[0]); }, $reference), $url); @@ -889,7 +889,7 @@ abstract class Doku_Renderer extends Plugin { $url = $url.rawurlencode($reference); } //handle as wiki links - if($url{0} === ':') { + if($url[0] === ':') { $urlparam = null; $id = $url; if (strpos($url, '?') !== false) { diff --git a/inc/parser/xhtml.php b/inc/parser/xhtml.php index b1931d6f0..169f4f9f4 100644 --- a/inc/parser/xhtml.php +++ b/inc/parser/xhtml.php @@ -680,7 +680,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { $this->doc .= '</a></dt>'.DOKU_LF.'<dd>'; } - if($text{0} == "\n") { + if($text[0] == "\n") { $text = substr($text, 1); } if(substr($text, -1) == "\n") { @@ -917,7 +917,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { $link['pre'] = ''; $link['suf'] = ''; // highlight link to current page - if($id == $INFO['id']) { + if(isset($INFO) && $id == $INFO['id']) { $link['pre'] = '<span class="curid">'; $link['suf'] = '</span>'; } diff --git a/inc/pluginutils.php b/inc/pluginutils.php index f1ad82fe6..0514d1c6a 100644 --- a/inc/pluginutils.php +++ b/inc/pluginutils.php @@ -27,10 +27,13 @@ if(!defined('DOKU_PLUGIN_NAME_REGEX')) define('DOKU_PLUGIN_NAME_REGEX', '[a-zA-Z * @param bool $all; true to retrieve all, false to retrieve only enabled plugins * @return array with plugin names or plugin component names */ -function plugin_list($type='',$all=false) { +function plugin_list($type='',$all=false) +{ /** @var $plugin_controller PluginController */ global $plugin_controller; - return $plugin_controller->getList($type,$all); + $plugins = $plugin_controller->getList($type,$all); + sort($plugins, SORT_NATURAL|SORT_FLAG_CASE); + return $plugins; } /** @@ -44,7 +47,8 @@ function plugin_list($type='',$all=false) { * @param $disabled bool true to load even disabled plugins * @return PluginInterface|null the plugin object or null on failure */ -function plugin_load($type,$name,$new=false,$disabled=false) { +function plugin_load($type,$name,$new=false,$disabled=false) +{ /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->load($type,$name,$new,$disabled); @@ -56,7 +60,8 @@ function plugin_load($type,$name,$new=false,$disabled=false) { * @param string $plugin name of plugin * @return bool true disabled, false enabled */ -function plugin_isdisabled($plugin) { +function plugin_isdisabled($plugin) +{ /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->isdisabled($plugin); @@ -68,7 +73,8 @@ function plugin_isdisabled($plugin) { * @param string $plugin name of plugin * @return bool true saving succeed, false saving failed */ -function plugin_enable($plugin) { +function plugin_enable($plugin) +{ /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->enable($plugin); @@ -80,7 +86,8 @@ function plugin_enable($plugin) { * @param string $plugin name of plugin * @return bool true saving succeed, false saving failed */ -function plugin_disable($plugin) { +function plugin_disable($plugin) +{ /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->disable($plugin); @@ -93,7 +100,8 @@ function plugin_disable($plugin) { * @return string name of directory * @deprecated 2018-07-20 */ -function plugin_directory($plugin) { +function plugin_directory($plugin) +{ dbg_deprecated('$plugin directly'); return $plugin; } @@ -103,7 +111,8 @@ function plugin_directory($plugin) { * * @return array with arrays of plugin configs */ -function plugin_getcascade() { +function plugin_getcascade() +{ /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->getCascade(); @@ -116,7 +125,8 @@ function plugin_getcascade() { * * @return Doku_Plugin_Admin */ -function plugin_getRequestAdminPlugin(){ +function plugin_getRequestAdminPlugin() +{ static $admin_plugin = false; global $ACT,$INPUT,$INFO; diff --git a/inc/template.php b/inc/template.php index caec0b87b..798fea52e 100644 --- a/inc/template.php +++ b/inc/template.php @@ -263,7 +263,7 @@ function tpl_metaheaders($alt = true) { $head['link'][] = array( 'rel' => 'alternate', 'type'=> 'application/rss+xml', 'title'=> $lang['currentns'], - 'href' => DOKU_BASE.'feed.php?mode=list&ns='.$INFO['namespace'] + 'href' => DOKU_BASE.'feed.php?mode=list&ns='.(isset($INFO) ? $INFO['namespace'] : '') ); } if(($ACT == 'show' || $ACT == 'search') && $INFO['writable']) { @@ -332,31 +332,33 @@ function tpl_metaheaders($alt = true) { // load stylesheets $head['link'][] = array( - 'rel' => 'stylesheet', 'type'=> 'text/css', + 'rel' => 'stylesheet', 'href'=> DOKU_BASE.'lib/exe/css.php?t='.rawurlencode($conf['template']).'&tseed='.$tseed ); - $script = "var NS='".$INFO['namespace']."';"; + $script = "var NS='".(isset($INFO)?$INFO['namespace']:'')."';"; if($conf['useacl'] && $INPUT->server->str('REMOTE_USER')) { $script .= "var SIG='".toolbar_signature()."';"; } jsinfo(); $script .= 'var JSINFO = ' . json_encode($JSINFO).';'; - $head['script'][] = array('type'=> 'text/javascript', '_data'=> $script); + $head['script'][] = array('_data'=> $script); // load jquery $jquery = getCdnUrls(); foreach($jquery as $src) { $head['script'][] = array( - 'type' => 'text/javascript', 'charset' => 'utf-8', '_data' => '', 'src' => $src - ); + 'charset' => 'utf-8', + '_data' => '', + 'src' => $src, + ) + ($conf['defer_js'] ? [ 'defer' => 'defer'] : []); } // load our javascript dispatcher $head['script'][] = array( - 'type'=> 'text/javascript', 'charset'=> 'utf-8', '_data'=> '', - 'src' => DOKU_BASE.'lib/exe/js.php'.'?t='.rawurlencode($conf['template']).'&tseed='.$tseed - ); + 'charset'=> 'utf-8', '_data'=> '', + 'src' => DOKU_BASE.'lib/exe/js.php'.'?t='.rawurlencode($conf['template']).'&tseed='.$tseed, + ) + ($conf['defer_js'] ? [ 'defer' => 'defer'] : []); // trigger event here Event::createAndTrigger('TPL_METAHEADER_OUTPUT', $head, '_tpl_metaheaders_action', true); @@ -957,6 +959,7 @@ function tpl_pagetitle($id = null, $ret = false) { // page functions case 'edit' : + case 'preview' : $page_title = "✎ ".$name; break; @@ -1845,7 +1848,7 @@ function tpl_classes() { 'mode_'.$ACT, 'tpl_'.$conf['template'], $INPUT->server->bool('REMOTE_USER') ? 'loggedIn' : '', - $INFO['exists'] ? '' : 'notFound', + (isset($INFO) && $INFO['exists']) ? '' : 'notFound', ($ID == $conf['start']) ? 'home' : '', ); return join(' ', $classes); |