Файловый менеджер - Редактировать - /var/www/jonas-eriksen.dk/pic/private/inc.tar
Назад
parserutils.php 0000644 00000066427 15233462216 0007655 0 ustar 00 <?php /** * Utilities for accessing the parser * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Harry Fuecks <hfuecks@gmail.com> * @author Andreas Gohr <andi@splitbrain.org> */ use dokuwiki\Cache\CacheInstructions; use dokuwiki\Cache\CacheRenderer; use dokuwiki\ChangeLog\PageChangeLog; use dokuwiki\Extension\PluginController; use dokuwiki\Extension\Event; use dokuwiki\Parsing\Parser; /** * How many pages shall be rendered for getting metadata during one request * at maximum? Note that this limit isn't respected when METADATA_RENDER_UNLIMITED * is passed as render parameter to p_get_metadata. */ if (!defined('P_GET_METADATA_RENDER_LIMIT')) define('P_GET_METADATA_RENDER_LIMIT', 5); /** Don't render metadata even if it is outdated or doesn't exist */ define('METADATA_DONT_RENDER', 0); /** * Render metadata when the page is really newer or the metadata doesn't exist. * Uses just a simple check, but should work pretty well for loading simple * metadata values like the page title and avoids rendering a lot of pages in * one request. The P_GET_METADATA_RENDER_LIMIT is used in this mode. * Use this if it is unlikely that the metadata value you are requesting * does depend e.g. on pages that are included in the current page using * the include plugin (this is very likely the case for the page title, but * not for relation references). */ define('METADATA_RENDER_USING_SIMPLE_CACHE', 1); /** * Render metadata using the metadata cache logic. The P_GET_METADATA_RENDER_LIMIT * is used in this mode. Use this mode when you are requesting more complex * metadata. Although this will cause rendering more often it might actually have * the effect that less current metadata is returned as it is more likely than in * the simple cache mode that metadata needs to be rendered for all pages at once * which means that when the metadata for the page is requested that actually needs * to be updated the limit might have been reached already. */ define('METADATA_RENDER_USING_CACHE', 2); /** * Render metadata without limiting the number of pages for which metadata is * rendered. Use this mode with care, normally it should only be used in places * like the indexer or in cli scripts where the execution time normally isn't * limited. This can be combined with the simple cache using * METADATA_RENDER_USING_CACHE | METADATA_RENDER_UNLIMITED. */ define('METADATA_RENDER_UNLIMITED', 4); /** * Returns the parsed Wikitext in XHTML for the given id and revision. * * If $excuse is true an explanation is returned if the file * wasn't found * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $id page id * @param string|int $rev revision timestamp or empty string * @param bool $excuse * @param string $date_at * * @return null|string */ function p_wiki_xhtml($id, $rev='', $excuse=true,$date_at=''){ $file = wikiFN($id,$rev); $ret = ''; //ensure $id is in global $ID (needed for parsing) global $ID; $keep = $ID; $ID = $id; if($rev || $date_at){ if(file_exists($file)){ //no caching on old revisions $ret = p_render('xhtml',p_get_instructions(io_readWikiPage($file,$id,$rev)),$info,$date_at); }elseif($excuse){ $ret = p_locale_xhtml('norev'); } }else{ if(file_exists($file)){ $ret = p_cached_output($file,'xhtml',$id); }elseif($excuse){ //check if the page once existed $changelog = new PageChangeLog($id); if($changelog->hasRevisions()) { $ret = p_locale_xhtml('onceexisted'); } else { $ret = p_locale_xhtml('newpage'); } } } //restore ID (just in case) $ID = $keep; return $ret; } /** * Returns the specified local text in parsed format * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $id page id * @return null|string */ function p_locale_xhtml($id){ //fetch parsed locale $html = p_cached_output(localeFN($id)); return $html; } /** * Returns the given file parsed into the requested output format * * @author Andreas Gohr <andi@splitbrain.org> * @author Chris Smith <chris@jalakai.co.uk> * * @param string $file filename, path to file * @param string $format * @param string $id page id * @return null|string */ function p_cached_output($file, $format='xhtml', $id='') { global $conf; $cache = new CacheRenderer($id, $file, $format); if ($cache->useCache()) { $parsed = $cache->retrieveCache(false); if($conf['allowdebug'] && $format=='xhtml') { $parsed .= "\n<!-- cachefile {$cache->cache} used -->\n"; } } else { $parsed = p_render($format, p_cached_instructions($file,false,$id), $info); if ($info['cache'] && $cache->storeCache($parsed)) { // storeCache() attempts to save cachefile if($conf['allowdebug'] && $format=='xhtml') { $parsed .= "\n<!-- no cachefile used, but created {$cache->cache} -->\n"; } }else{ $cache->removeCache(); //try to delete cachefile if($conf['allowdebug'] && $format=='xhtml') { $parsed .= "\n<!-- no cachefile used, caching forbidden -->\n"; } } } return $parsed; } /** * Returns the render instructions for a file * * Uses and creates a serialized cache file * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $file filename, path to file * @param bool $cacheonly * @param string $id page id * @return array|null */ function p_cached_instructions($file,$cacheonly=false,$id='') { static $run = null; if(is_null($run)) $run = array(); $cache = new CacheInstructions($id, $file); if ($cacheonly || $cache->useCache() || (isset($run[$file]) && !defined('DOKU_UNITTEST'))) { return $cache->retrieveCache(); } else if (file_exists($file)) { // no cache - do some work $ins = p_get_instructions(io_readWikiPage($file,$id)); if ($cache->storeCache($ins)) { $run[$file] = true; // we won't rebuild these instructions in the same run again } else { msg('Unable to save cache file. Hint: disk full; file permissions; safe_mode setting.',-1); } return $ins; } return null; } /** * turns a page into a list of instructions * * @author Harry Fuecks <hfuecks@gmail.com> * @author Andreas Gohr <andi@splitbrain.org> * * @param string $text raw wiki syntax text * @return array a list of instruction arrays */ function p_get_instructions($text){ $modes = p_get_parsermodes(); // Create the parser and handler $Parser = new Parser(new Doku_Handler()); //add modes to parser foreach($modes as $mode){ $Parser->addMode($mode['mode'],$mode['obj']); } // Do the parsing Event::createAndTrigger('PARSER_WIKITEXT_PREPROCESS', $text); $p = $Parser->parse($text); // dbg($p); return $p; } /** * returns the metadata of a page * * @param string $id The id of the page the metadata should be returned from * @param string $key The key of the metdata value that shall be read (by default everything) * separate hierarchies by " " like "date created" * @param int $render If the page should be rendererd - possible values: * METADATA_DONT_RENDER, METADATA_RENDER_USING_SIMPLE_CACHE, METADATA_RENDER_USING_CACHE * METADATA_RENDER_UNLIMITED (also combined with the previous two options), * default: METADATA_RENDER_USING_CACHE * @return mixed The requested metadata fields * * @author Esther Brunner <esther@kaffeehaus.ch> * @author Michael Hamann <michael@content-space.de> */ function p_get_metadata($id, $key='', $render=METADATA_RENDER_USING_CACHE){ global $ID; static $render_count = 0; // track pages that have already been rendered in order to avoid rendering the same page // again static $rendered_pages = array(); // cache the current page // Benchmarking shows the current page's metadata is generally the only page metadata // accessed several times. This may catch a few other pages, but that shouldn't be an issue. $cache = ($ID == $id); $meta = p_read_metadata($id, $cache); if (!is_numeric($render)) { if ($render) { $render = METADATA_RENDER_USING_SIMPLE_CACHE; } else { $render = METADATA_DONT_RENDER; } } // prevent recursive calls in the cache static $recursion = false; if (!$recursion && $render != METADATA_DONT_RENDER && !isset($rendered_pages[$id])&& page_exists($id)){ $recursion = true; $cachefile = new CacheRenderer($id, wikiFN($id), 'metadata'); $do_render = false; if ($render & METADATA_RENDER_UNLIMITED || $render_count < P_GET_METADATA_RENDER_LIMIT) { if ($render & METADATA_RENDER_USING_SIMPLE_CACHE) { $pagefn = wikiFN($id); $metafn = metaFN($id, '.meta'); if (!file_exists($metafn) || @filemtime($pagefn) > @filemtime($cachefile->cache)) { $do_render = true; } } elseif (!$cachefile->useCache()){ $do_render = true; } } if ($do_render) { if (!defined('DOKU_UNITTEST')) { ++$render_count; $rendered_pages[$id] = true; } $old_meta = $meta; $meta = p_render_metadata($id, $meta); // only update the file when the metadata has been changed if ($meta == $old_meta || p_save_metadata($id, $meta)) { // store a timestamp in order to make sure that the cachefile is touched // this timestamp is also stored when the meta data is still the same $cachefile->storeCache(time()); } else { msg('Unable to save metadata file. Hint: disk full; file permissions; safe_mode setting.',-1); } } $recursion = false; } $val = $meta['current']; // filter by $key foreach(preg_split('/\s+/', $key, 2, PREG_SPLIT_NO_EMPTY) as $cur_key) { if (!isset($val[$cur_key])) { return null; } $val = $val[$cur_key]; } return $val; } /** * sets metadata elements of a page * * @see http://www.dokuwiki.org/devel:metadata#functions_to_get_and_set_metadata * * @param string $id is the ID of a wiki page * @param array $data is an array with key ⇒ value pairs to be set in the metadata * @param boolean $render whether or not the page metadata should be generated with the renderer * @param boolean $persistent indicates whether or not the particular metadata value will persist through * the next metadata rendering. * @return boolean true on success * * @author Esther Brunner <esther@kaffeehaus.ch> * @author Michael Hamann <michael@content-space.de> */ function p_set_metadata($id, $data, $render=false, $persistent=true){ if (!is_array($data)) return false; global $ID, $METADATA_RENDERERS; // if there is currently a renderer change the data in the renderer instead if (isset($METADATA_RENDERERS[$id])) { $orig =& $METADATA_RENDERERS[$id]; $meta = $orig; } else { // cache the current page $cache = ($ID == $id); $orig = p_read_metadata($id, $cache); // render metadata first? $meta = $render ? p_render_metadata($id, $orig) : $orig; } // now add the passed metadata $protected = array('description', 'date', 'contributor'); foreach ($data as $key => $value){ // be careful with sub-arrays of $meta['relation'] if ($key == 'relation'){ foreach ($value as $subkey => $subvalue){ if(isset($meta['current'][$key][$subkey]) && is_array($meta['current'][$key][$subkey])) { $meta['current'][$key][$subkey] = array_replace($meta['current'][$key][$subkey], (array)$subvalue); } else { $meta['current'][$key][$subkey] = $subvalue; } if($persistent) { if(isset($meta['persistent'][$key][$subkey]) && is_array($meta['persistent'][$key][$subkey])) { $meta['persistent'][$key][$subkey] = array_replace( $meta['persistent'][$key][$subkey], (array) $subvalue ); } else { $meta['persistent'][$key][$subkey] = $subvalue; } } } // be careful with some senisitive arrays of $meta } elseif (in_array($key, $protected)){ // these keys, must have subkeys - a legitimate value must be an array if (is_array($value)) { $meta['current'][$key] = !empty($meta['current'][$key]) ? array_replace((array)$meta['current'][$key],$value) : $value; if ($persistent) { $meta['persistent'][$key] = !empty($meta['persistent'][$key]) ? array_replace((array)$meta['persistent'][$key],$value) : $value; } } // no special treatment for the rest } else { $meta['current'][$key] = $value; if ($persistent) $meta['persistent'][$key] = $value; } } // save only if metadata changed if ($meta == $orig) return true; if (isset($METADATA_RENDERERS[$id])) { // set both keys individually as the renderer has references to the individual keys $METADATA_RENDERERS[$id]['current'] = $meta['current']; $METADATA_RENDERERS[$id]['persistent'] = $meta['persistent']; return true; } else { return p_save_metadata($id, $meta); } } /** * Purges the non-persistant part of the meta data * used on page deletion * * @author Michael Klier <chi@chimeric.de> * * @param string $id page id * @return bool success / fail */ function p_purge_metadata($id) { $meta = p_read_metadata($id); foreach($meta['current'] as $key => $value) { if(is_array($meta[$key])) { $meta['current'][$key] = array(); } else { $meta['current'][$key] = ''; } } return p_save_metadata($id, $meta); } /** * read the metadata from source/cache for $id * (internal use only - called by p_get_metadata & p_set_metadata) * * @author Christopher Smith <chris@jalakai.co.uk> * * @param string $id absolute wiki page id * @param bool $cache whether or not to cache metadata in memory * (only use for metadata likely to be accessed several times) * * @return array metadata */ function p_read_metadata($id,$cache=false) { global $cache_metadata; if (isset($cache_metadata[(string)$id])) return $cache_metadata[(string)$id]; $file = metaFN($id, '.meta'); $meta = file_exists($file) ? unserialize(io_readFile($file, false)) : array('current'=>array(),'persistent'=>array()); if ($cache) { $cache_metadata[(string)$id] = $meta; } return $meta; } /** * This is the backend function to save a metadata array to a file * * @param string $id absolute wiki page id * @param array $meta metadata * * @return bool success / fail */ function p_save_metadata($id, $meta) { // sync cached copies, including $INFO metadata global $cache_metadata, $INFO; if (isset($cache_metadata[$id])) $cache_metadata[$id] = $meta; if (!empty($INFO) && ($id == $INFO['id'])) { $INFO['meta'] = $meta['current']; } return io_saveFile(metaFN($id, '.meta'), serialize($meta)); } /** * renders the metadata of a page * * @author Esther Brunner <esther@kaffeehaus.ch> * * @param string $id page id * @param array $orig the original metadata * @return array|null array('current'=> array,'persistent'=> array); */ function p_render_metadata($id, $orig){ // make sure the correct ID is in global ID global $ID, $METADATA_RENDERERS; // avoid recursive rendering processes for the same id if (isset($METADATA_RENDERERS[$id])) { return $orig; } // store the original metadata in the global $METADATA_RENDERERS so p_set_metadata can use it $METADATA_RENDERERS[$id] =& $orig; $keep = $ID; $ID = $id; // add an extra key for the event - to tell event handlers the page whose metadata this is $orig['page'] = $id; $evt = new Event('PARSER_METADATA_RENDER', $orig); if ($evt->advise_before()) { // get instructions $instructions = p_cached_instructions(wikiFN($id),false,$id); if(is_null($instructions)){ $ID = $keep; unset($METADATA_RENDERERS[$id]); return null; // something went wrong with the instructions } // set up the renderer $renderer = new Doku_Renderer_metadata(); $renderer->meta =& $orig['current']; $renderer->persistent =& $orig['persistent']; // loop through the instructions foreach ($instructions as $instruction){ // execute the callback against the renderer call_user_func_array(array(&$renderer, $instruction[0]), (array) $instruction[1]); } $evt->result = array('current'=>&$renderer->meta,'persistent'=>&$renderer->persistent); } $evt->advise_after(); // clean up $ID = $keep; unset($METADATA_RENDERERS[$id]); return $evt->result; } /** * returns all available parser syntax modes in correct order * * @author Andreas Gohr <andi@splitbrain.org> * * @return array[] with for each plugin the array('sort' => sortnumber, 'mode' => mode string, 'obj' => plugin object) */ function p_get_parsermodes(){ global $conf; //reuse old data static $modes = null; if($modes != null && !defined('DOKU_UNITTEST')){ return $modes; } //import parser classes and mode definitions require_once DOKU_INC . 'inc/parser/parser.php'; // we now collect all syntax modes and their objects, then they will // be sorted and added to the parser in correct order $modes = array(); // add syntax plugins $pluginlist = plugin_list('syntax'); if(count($pluginlist)){ global $PARSER_MODES; $obj = null; foreach($pluginlist as $p){ /** @var \dokuwiki\Extension\SyntaxPlugin $obj */ if(!$obj = plugin_load('syntax',$p)) continue; //attempt to load plugin into $obj $PARSER_MODES[$obj->getType()][] = "plugin_$p"; //register mode type //add to modes $modes[] = array( 'sort' => $obj->getSort(), 'mode' => "plugin_$p", 'obj' => $obj, ); unset($obj); //remove the reference } } // add default modes $std_modes = array('listblock','preformatted','notoc','nocache', 'header','table','linebreak','footnote','hr', 'unformatted','php','html','code','file','quote', 'internallink','rss','media','externallink', 'emaillink','windowssharelink','eol'); if($conf['typography']){ $std_modes[] = 'quotes'; $std_modes[] = 'multiplyentity'; } foreach($std_modes as $m){ $class = 'dokuwiki\\Parsing\\ParserMode\\'.ucfirst($m); $obj = new $class(); $modes[] = array( 'sort' => $obj->getSort(), 'mode' => $m, 'obj' => $obj ); } // add formatting modes $fmt_modes = array('strong','emphasis','underline','monospace', 'subscript','superscript','deleted'); foreach($fmt_modes as $m){ $obj = new \dokuwiki\Parsing\ParserMode\Formatting($m); $modes[] = array( 'sort' => $obj->getSort(), 'mode' => $m, 'obj' => $obj ); } // add modes which need files $obj = new \dokuwiki\Parsing\ParserMode\Smiley(array_keys(getSmileys())); $modes[] = array('sort' => $obj->getSort(), 'mode' => 'smiley','obj' => $obj ); $obj = new \dokuwiki\Parsing\ParserMode\Acronym(array_keys(getAcronyms())); $modes[] = array('sort' => $obj->getSort(), 'mode' => 'acronym','obj' => $obj ); $obj = new \dokuwiki\Parsing\ParserMode\Entity(array_keys(getEntities())); $modes[] = array('sort' => $obj->getSort(), 'mode' => 'entity','obj' => $obj ); // add optional camelcase mode if($conf['camelcase']){ $obj = new \dokuwiki\Parsing\ParserMode\Camelcaselink(); $modes[] = array('sort' => $obj->getSort(), 'mode' => 'camelcaselink','obj' => $obj ); } //sort modes usort($modes,'p_sort_modes'); return $modes; } /** * Callback function for usort * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $a * @param array $b * @return int $a is lower/equal/higher than $b */ function p_sort_modes($a, $b){ if($a['sort'] == $b['sort']) return 0; return ($a['sort'] < $b['sort']) ? -1 : 1; } /** * Renders a list of instruction to the specified output mode * * In the $info array is information from the renderer returned * * @author Harry Fuecks <hfuecks@gmail.com> * @author Andreas Gohr <andi@splitbrain.org> * * @param string $mode * @param array|null|false $instructions * @param array $info returns render info like enabled toc and cache * @param string $date_at * @return null|string rendered output */ function p_render($mode,$instructions,&$info,$date_at=''){ if(is_null($instructions)) return ''; if($instructions === false) return ''; $Renderer = p_get_renderer($mode); if (is_null($Renderer)) return null; $Renderer->reset(); if($date_at) { $Renderer->date_at = $date_at; } $Renderer->smileys = getSmileys(); $Renderer->entities = getEntities(); $Renderer->acronyms = getAcronyms(); $Renderer->interwiki = getInterwiki(); // Loop through the instructions foreach ( $instructions as $instruction ) { // Execute the callback against the Renderer if(method_exists($Renderer, $instruction[0])){ call_user_func_array(array(&$Renderer, $instruction[0]), $instruction[1] ? $instruction[1] : array()); } } //set info array $info = $Renderer->info; // Post process and return the output $data = array($mode,& $Renderer->doc); Event::createAndTrigger('RENDERER_CONTENT_POSTPROCESS',$data); return $Renderer->doc; } /** * Figure out the correct renderer class to use for $mode, * instantiate and return it * * @param string $mode Mode of the renderer to get * @return null|Doku_Renderer The renderer * * @author Christopher Smith <chris@jalakai.co.uk> */ function p_get_renderer($mode) { /** @var PluginController $plugin_controller */ global $conf, $plugin_controller; $rname = !empty($conf['renderer_'.$mode]) ? $conf['renderer_'.$mode] : $mode; $rclass = "Doku_Renderer_$rname"; // if requested earlier or a bundled renderer if( class_exists($rclass) ) { $Renderer = new $rclass(); return $Renderer; } // not bundled, see if its an enabled renderer plugin & when $mode is 'xhtml', the renderer can supply that format. /** @var Doku_Renderer $Renderer */ $Renderer = $plugin_controller->load('renderer',$rname); if ($Renderer && is_a($Renderer, 'Doku_Renderer') && ($mode != 'xhtml' || $mode == $Renderer->getFormat())) { return $Renderer; } // there is a configuration error! // not bundled, not a valid enabled plugin, use $mode to try to fallback to a bundled renderer $rclass = "Doku_Renderer_$mode"; if ( class_exists($rclass) ) { // viewers should see renderered output, so restrict the warning to admins only $msg = "No renderer '$rname' found for mode '$mode', check your plugins"; if ($mode == 'xhtml') { $msg .= " and the 'renderer_xhtml' config setting"; } $msg .= ".<br/>Attempting to fallback to the bundled renderer."; msg($msg,-1,'','',MSG_ADMINS_ONLY); $Renderer = new $rclass; $Renderer->nocache(); // fallback only (and may include admin alerts), don't cache return $Renderer; } // fallback failed, alert the world msg("No renderer '$rname' found for mode '$mode'",-1); return null; } /** * Gets the first heading from a file * * @param string $id dokuwiki page id * @param int $render rerender if first heading not known * default: METADATA_RENDER_USING_SIMPLE_CACHE * Possible values: METADATA_DONT_RENDER, * METADATA_RENDER_USING_SIMPLE_CACHE, * METADATA_RENDER_USING_CACHE, * METADATA_RENDER_UNLIMITED * @return string|null The first heading * * @author Andreas Gohr <andi@splitbrain.org> * @author Michael Hamann <michael@content-space.de> */ function p_get_first_heading($id, $render=METADATA_RENDER_USING_SIMPLE_CACHE){ return p_get_metadata(cleanID($id),'title',$render); } /** * Wrapper for GeSHi Code Highlighter, provides caching of its output * * @param string $code source code to be highlighted * @param string $language language to provide highlighting * @param string $wrapper html element to wrap the returned highlighted text * @return string xhtml code * * @author Christopher Smith <chris@jalakai.co.uk> * @author Andreas Gohr <andi@splitbrain.org> */ function p_xhtml_cached_geshi($code, $language, $wrapper='pre', array $options=null) { global $conf, $config_cascade, $INPUT; $language = strtolower($language); // remove any leading or trailing blank lines $code = preg_replace('/^\s*?\n|\s*?\n$/','',$code); $optionsmd5 = md5(serialize($options)); $cache = getCacheName($language.$code.$optionsmd5,".code"); $ctime = @filemtime($cache); if($ctime && !$INPUT->bool('purge') && $ctime > filemtime(DOKU_INC.'vendor/composer/installed.json') && // libraries changed $ctime > filemtime(reset($config_cascade['main']['default']))){ // dokuwiki changed $highlighted_code = io_readFile($cache, false); } else { $geshi = new GeSHi($code, $language); $geshi->set_encoding('utf-8'); $geshi->enable_classes(); $geshi->set_header_type(GESHI_HEADER_PRE); $geshi->set_link_target($conf['target']['extern']); if($options !== null) { foreach ($options as $function => $params) { if(is_callable(array($geshi, $function))) { $geshi->$function($params); } } } // remove GeSHi's wrapper element (we'll replace it with our own later) // we need to use a GeSHi wrapper to avoid <BR> throughout the highlighted text $highlighted_code = trim(preg_replace('!^<pre[^>]*>|</pre>$!','',$geshi->parse_code()),"\n\r"); io_saveFile($cache,$highlighted_code); } // add a wrapper element if required if ($wrapper) { return "<$wrapper class=\"code $language\">$highlighted_code</$wrapper>"; } else { return $highlighted_code; } } farm.php 0000644 00000014471 15233462216 0006215 0 ustar 00 <?php /** * This overwrites DOKU_CONF. Each animal gets its own configuration and data directory. * This can be used together with preload.php. See preload.php.dist for an example setup. * For more information see http://www.dokuwiki.org/farms. * * The farm directory (constant DOKU_FARMDIR) can be any directory and needs to be set. * Animals are direct subdirectories of the farm directory. * There are two different approaches: * * An .htaccess based setup can use any animal directory name: * http://example.org/<path_to_farm>/subdir/ will need the subdirectory '$farm/subdir/'. * * A virtual host based setup needs animal directory names which have to reflect * the domain name: If an animal resides in http://www.example.org:8080/mysite/test/, * directories that will match range from '$farm/8080.www.example.org.mysite.test/' * to a simple '$farm/domain/'. * * @author Anika Henke <anika@selfthinker.org> * @author Michael Klier <chi@chimeric.de> * @author Christopher Smith <chris@jalakai.co.uk> * @author virtual host part of farm_confpath() based on conf_path() from Drupal.org's /includes/bootstrap.inc * (see https://github.com/drupal/drupal/blob/7.x/includes/bootstrap.inc#L537) * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) */ // DOKU_FARMDIR needs to be set in preload.php, the fallback is the same as DOKU_INC would be (if it was set already) if(!defined('DOKU_FARMDIR')) define('DOKU_FARMDIR', fullpath(dirname(__FILE__).'/../').'/'); if(!defined('DOKU_CONF')) define('DOKU_CONF', farm_confpath(DOKU_FARMDIR)); if(!defined('DOKU_FARM')) define('DOKU_FARM', false); /** * Find the appropriate configuration directory. * * If the .htaccess based setup is used, the configuration directory can be * any subdirectory of the farm directory. * * Otherwise try finding a matching configuration directory by stripping the * website's hostname from left to right and pathname from right to left. The * first configuration file found will be used; the remaining will ignored. * If no configuration file is found, return the default confdir './conf'. * * @param string $farm * * @return string */ function farm_confpath($farm) { // htaccess based or cli // cli usage example: animal=your_animal bin/indexer.php if(isset($_REQUEST['animal']) || ('cli' == php_sapi_name() && isset($_SERVER['animal']))) { $mode = isset($_REQUEST['animal']) ? 'htaccess' : 'cli'; $animal = $mode == 'htaccess' ? $_REQUEST['animal'] : $_SERVER['animal']; // check that $animal is a string and just a directory name and not a path if (!is_string($animal) || strpbrk($animal, '\\/') !== false) nice_die('Sorry! Invalid animal name!'); if(!is_dir($farm.'/'.$animal)) nice_die("Sorry! This Wiki doesn't exist!"); if(!defined('DOKU_FARM')) define('DOKU_FARM', $mode); return $farm.'/'.$animal.'/conf/'; } // virtual host based $uri = explode('/', $_SERVER['SCRIPT_NAME'] ? $_SERVER['SCRIPT_NAME'] : $_SERVER['SCRIPT_FILENAME']); $server = explode('.', implode('.', array_reverse(explode(':', rtrim($_SERVER['HTTP_HOST'], '.'))))); for ($i = count($uri) - 1; $i > 0; $i--) { for ($j = count($server); $j > 0; $j--) { $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i)); if(is_dir("$farm/$dir/conf/")) { if(!defined('DOKU_FARM')) define('DOKU_FARM', 'virtual'); return "$farm/$dir/conf/"; } } } // default conf directory in farm if(is_dir("$farm/default/conf/")) { if(!defined('DOKU_FARM')) define('DOKU_FARM', 'default'); return "$farm/default/conf/"; } // farmer return DOKU_INC.'conf/'; } /* Use default config files and local animal config files */ $config_cascade = array( 'main' => array( 'default' => array(DOKU_INC.'conf/dokuwiki.php'), 'local' => array(DOKU_CONF.'local.php'), 'protected' => array(DOKU_CONF.'local.protected.php'), ), 'acronyms' => array( 'default' => array(DOKU_INC.'conf/acronyms.conf'), 'local' => array(DOKU_CONF.'acronyms.local.conf'), ), 'entities' => array( 'default' => array(DOKU_INC.'conf/entities.conf'), 'local' => array(DOKU_CONF.'entities.local.conf'), ), 'interwiki' => array( 'default' => array(DOKU_INC.'conf/interwiki.conf'), 'local' => array(DOKU_CONF.'interwiki.local.conf'), ), 'license' => array( 'default' => array(DOKU_INC.'conf/license.php'), 'local' => array(DOKU_CONF.'license.local.php'), ), 'mediameta' => array( 'default' => array(DOKU_INC.'conf/mediameta.php'), 'local' => array(DOKU_CONF.'mediameta.local.php'), ), 'mime' => array( 'default' => array(DOKU_INC.'conf/mime.conf'), 'local' => array(DOKU_CONF.'mime.local.conf'), ), 'scheme' => array( 'default' => array(DOKU_INC.'conf/scheme.conf'), 'local' => array(DOKU_CONF.'scheme.local.conf'), ), 'smileys' => array( 'default' => array(DOKU_INC.'conf/smileys.conf'), 'local' => array(DOKU_CONF.'smileys.local.conf'), ), 'wordblock' => array( 'default' => array(DOKU_INC.'conf/wordblock.conf'), 'local' => array(DOKU_CONF.'wordblock.local.conf'), ), 'acl' => array( 'default' => DOKU_CONF.'acl.auth.php', ), 'plainauth.users' => array( 'default' => DOKU_CONF.'users.auth.php', ), 'plugins' => array( // needed since Angua 'default' => array(DOKU_INC.'conf/plugins.php'), 'local' => array(DOKU_CONF.'plugins.local.php'), 'protected' => array( DOKU_INC.'conf/plugins.required.php', DOKU_CONF.'plugins.protected.php', ), ), 'userstyle' => array( 'screen' => array(DOKU_CONF . 'userstyle.css', DOKU_CONF . 'userstyle.less'), 'print' => array(DOKU_CONF . 'userprint.css', DOKU_CONF . 'userprint.less'), 'feed' => array(DOKU_CONF . 'userfeed.css', DOKU_CONF . 'userfeed.less'), 'all' => array(DOKU_CONF . 'userall.css', DOKU_CONF . 'userall.less') ), 'userscript' => array( 'default' => array(DOKU_CONF . 'userscript.js') ), ); toolbar.php 0000644 00000026441 15233462216 0006732 0 ustar 00 <?php /** * Editing toolbar functions * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */use dokuwiki\Extension\Event; /** * Prepares and prints an JavaScript array with all toolbar buttons * * @emits TOOLBAR_DEFINE * @param string $varname Name of the JS variable to fill * @author Andreas Gohr <andi@splitbrain.org> */ function toolbar_JSdefines($varname){ global $lang; $menu = array(); $evt = new Event('TOOLBAR_DEFINE', $menu); if ($evt->advise_before()){ // build button array $menu = array_merge($menu, array( array( 'type' => 'format', 'title' => $lang['qb_bold'], 'icon' => 'bold.png', 'key' => 'b', 'open' => '**', 'close' => '**', 'block' => false ), array( 'type' => 'format', 'title' => $lang['qb_italic'], 'icon' => 'italic.png', 'key' => 'i', 'open' => '//', 'close' => '//', 'block' => false ), array( 'type' => 'format', 'title' => $lang['qb_underl'], 'icon' => 'underline.png', 'key' => 'u', 'open' => '__', 'close' => '__', 'block' => false ), array( 'type' => 'format', 'title' => $lang['qb_code'], 'icon' => 'mono.png', 'key' => 'm', 'open' => "''", 'close' => "''", 'block' => false ), array( 'type' => 'format', 'title' => $lang['qb_strike'], 'icon' => 'strike.png', 'key' => 'd', 'open' => '<del>', 'close' => '</del>', 'block' => false ), array( 'type' => 'autohead', 'title' => $lang['qb_hequal'], 'icon' => 'hequal.png', 'key' => '8', 'text' => $lang['qb_h'], 'mod' => 0, 'block' => true ), array( 'type' => 'autohead', 'title' => $lang['qb_hminus'], 'icon' => 'hminus.png', 'key' => '9', 'text' => $lang['qb_h'], 'mod' => 1, 'block' => true ), array( 'type' => 'autohead', 'title' => $lang['qb_hplus'], 'icon' => 'hplus.png', 'key' => '0', 'text' => $lang['qb_h'], 'mod' => -1, 'block' => true ), array( 'type' => 'picker', 'title' => $lang['qb_hs'], 'icon' => 'h.png', 'class' => 'pk_hl', 'list' => array( array( 'type' => 'format', 'title' => $lang['qb_h1'], 'icon' => 'h1.png', 'key' => '1', 'open' => '====== ', 'close' => ' ======\n', ), array( 'type' => 'format', 'title' => $lang['qb_h2'], 'icon' => 'h2.png', 'key' => '2', 'open' => '===== ', 'close' => ' =====\n', ), array( 'type' => 'format', 'title' => $lang['qb_h3'], 'icon' => 'h3.png', 'key' => '3', 'open' => '==== ', 'close' => ' ====\n', ), array( 'type' => 'format', 'title' => $lang['qb_h4'], 'icon' => 'h4.png', 'key' => '4', 'open' => '=== ', 'close' => ' ===\n', ), array( 'type' => 'format', 'title' => $lang['qb_h5'], 'icon' => 'h5.png', 'key' => '5', 'open' => '== ', 'close' => ' ==\n', ), ), 'block' => true ), array( 'type' => 'linkwiz', 'title' => $lang['qb_link'], 'icon' => 'link.png', 'key' => 'l', 'open' => '[[', 'close' => ']]', 'block' => false ), array( 'type' => 'format', 'title' => $lang['qb_extlink'], 'icon' => 'linkextern.png', 'open' => '[[', 'close' => ']]', 'sample' => 'http://example.com|'.$lang['qb_extlink'], 'block' => false ), array( 'type' => 'formatln', 'title' => $lang['qb_ol'], 'icon' => 'ol.png', 'open' => ' - ', 'close' => '', 'key' => '-', 'block' => true ), array( 'type' => 'formatln', 'title' => $lang['qb_ul'], 'icon' => 'ul.png', 'open' => ' * ', 'close' => '', 'key' => '.', 'block' => true ), array( 'type' => 'insert', 'title' => $lang['qb_hr'], 'icon' => 'hr.png', 'insert' => '\n----\n', 'block' => true ), array( 'type' => 'mediapopup', 'title' => $lang['qb_media'], 'icon' => 'image.png', 'url' => 'lib/exe/mediamanager.php?ns=', 'name' => 'mediaselect', 'options'=> 'width=750,height=500,left=20,top=20,scrollbars=yes,resizable=yes', 'block' => false ), array( 'type' => 'picker', 'title' => $lang['qb_smileys'], 'icon' => 'smiley.png', 'list' => getSmileys(), 'icobase'=> 'smileys', 'block' => false ), array( 'type' => 'picker', 'title' => $lang['qb_chars'], 'icon' => 'chars.png', 'list' => [ 'À', 'à', 'Á', 'á', 'Â', 'â', 'Ã', 'ã', 'Ä', 'ä', 'Ǎ', 'ǎ', 'Ă', 'ă', 'Å', 'å', 'Ā', 'ā', 'Ą', 'ą', 'Æ', 'æ', 'Ć', 'ć', 'Ç', 'ç', 'Č', 'č', 'Ĉ', 'ĉ', 'Ċ', 'ċ', 'Ð', 'đ', 'ð', 'Ď', 'ď', 'È', 'è', 'É', 'é', 'Ê', 'ê', 'Ë', 'ë', 'Ě', 'ě', 'Ē', 'ē', 'Ė', 'ė', 'Ę', 'ę', 'Ģ', 'ģ', 'Ĝ', 'ĝ', 'Ğ', 'ğ', 'Ġ', 'ġ', 'Ĥ', 'ĥ', 'Ì', 'ì', 'Í', 'í', 'Î', 'î', 'Ï', 'ï', 'Ǐ', 'ǐ', 'Ī', 'ī', 'İ', 'ı', 'Į', 'į', 'Ĵ', 'ĵ', 'Ķ', 'ķ', 'Ĺ', 'ĺ', 'Ļ', 'ļ', 'Ľ', 'ľ', 'Ł', 'ł', 'Ŀ', 'ŀ', 'Ń', 'ń', 'Ñ', 'ñ', 'Ņ', 'ņ', 'Ň', 'ň', 'Ò', 'ò', 'Ó', 'ó', 'Ô', 'ô', 'Õ', 'õ', 'Ö', 'ö', 'Ǒ', 'ǒ', 'Ō', 'ō', 'Ő', 'ő', 'Œ', 'œ', 'Ø', 'ø', 'Ŕ', 'ŕ', 'Ŗ', 'ŗ', 'Ř', 'ř', 'Ś', 'ś', 'Ş', 'ş', 'Š', 'š', 'Ŝ', 'ŝ', 'Ţ', 'ţ', 'Ť', 'ť', 'Ù', 'ù', 'Ú', 'ú', 'Û', 'û', 'Ü', 'ü', 'Ǔ', 'ǔ', 'Ŭ', 'ŭ', 'Ū', 'ū', 'Ů', 'ů', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'Ų', 'ų', 'Ű', 'ű', 'Ŵ', 'ŵ', 'Ý', 'ý', 'Ÿ', 'ÿ', 'Ŷ', 'ŷ', 'Ź', 'ź', 'Ž', 'ž', 'Ż', 'ż', 'Þ', 'þ', 'ß', 'Ħ', 'ħ', '¿', '¡', '¢', '£', '¤', '¥', '€', '¦', '§', 'ª', '¬', '¯', '°', '±', '÷', '‰', '¼', '½', '¾', '¹', '²', '³', 'µ', '¶', '†', '‡', '·', '•', 'º', '∀', '∂', '∃', 'Ə', 'ə', '∅', '∇', '∈', '∉', '∋', '∏', '∑', '‾', '−', '∗', '×', '⁄', '√', '∝', '∞', '∠', '∧', '∨', '∩', '∪', '∫', '∴', '∼', '≅', '≈', '≠', '≡', '≤', '≥', '⊂', '⊃', '⊄', '⊆', '⊇', '⊕', '⊗', '⊥', '⋅', '◊', '℘', 'ℑ', 'ℜ', 'ℵ', '♠', '♣', '♥', '♦', 'α', 'β', 'Γ', 'γ', 'Δ', 'δ', 'ε', 'ζ', 'η', 'Θ', 'θ', 'ι', 'κ', 'Λ', 'λ', 'μ', 'Ξ', 'ξ', 'Π', 'π', 'ρ', 'Σ', 'σ', 'Τ', 'τ', 'υ', 'Φ', 'φ', 'χ', 'Ψ', 'ψ', 'Ω', 'ω', '★', '☆', '☎', '☚', '☛', '☜', '☝', '☞', '☟', '☹', '☺', '✔', '✘', '„', '“', '”', '‚', '‘', '’', '«', '»', '‹', '›', '—', '–', '…', '←', '↑', '→', '↓', '↔', '⇐', '⇑', '⇒', '⇓', '⇔', '©', '™', '®', '′', '″', '[', ']', '{', '}', '~', '(', ')', '%', '§', '$', '#', '|', '@' ], 'block' => false ), array( 'type' => 'signature', 'title' => $lang['qb_sig'], 'icon' => 'sig.png', 'key' => 'y', 'block' => false ), )); } // end event TOOLBAR_DEFINE default action $evt->advise_after(); unset($evt); // use JSON to build the JavaScript array print "var $varname = ".json_encode($menu).";\n"; } /** * prepares the signature string as configured in the config * * @author Andreas Gohr <andi@splitbrain.org> */ function toolbar_signature(){ global $conf; global $INFO; /** @var Input $INPUT */ global $INPUT; $sig = $conf['signature']; $sig = dformat(null,$sig); $sig = str_replace('@USER@',$INPUT->server->str('REMOTE_USER'),$sig); $sig = str_replace('@NAME@',$INFO['userinfo']['name'],$sig); $sig = str_replace('@MAIL@',$INFO['userinfo']['mail'],$sig); $sig = str_replace('@DATE@',dformat(),$sig); $sig = str_replace('\\\\n','\\n',$sig); return json_encode($sig); } //Setup VIM: ex: et ts=4 : Subscriptions/SubscriberRegexBuilder.php 0000644 00000003442 15233462216 0014540 0 ustar 00 <?php namespace dokuwiki\Subscriptions; use Exception; class SubscriberRegexBuilder { /** * Construct a regular expression for parsing a subscription definition line * * @param string|array $user * @param string|array $style * @param string|array $data * * @return string complete regexp including delimiters * @throws Exception when no data is passed * @author Andreas Gohr <andi@splitbrain.org> * */ public function buildRegex($user = null, $style = null, $data = null) { // always work with arrays $user = (array)$user; $style = (array)$style; $data = (array)$data; // clean $user = array_filter(array_map('trim', $user)); $style = array_filter(array_map('trim', $style)); $data = array_filter(array_map('trim', $data)); // user names are encoded $user = array_map('auth_nameencode', $user); // quote $user = array_map('preg_quote_cb', $user); $style = array_map('preg_quote_cb', $style); $data = array_map('preg_quote_cb', $data); // join $user = join('|', $user); $style = join('|', $style); $data = join('|', $data); // any data at all? if ($user . $style . $data === '') { throw new Exception('no data passed'); } // replace empty values, set which ones are optional $sopt = ''; $dopt = ''; if ($user === '') { $user = '\S+'; } if ($style === '') { $style = '\S+'; $sopt = '?'; } if ($data === '') { $data = '\S+'; $dopt = '?'; } // assemble return "/^($user)(?:\\s+($style))$sopt(?:\\s+($data))$dopt$/"; } } Subscriptions/RegistrationSubscriptionSender.php 0000644 00000001654 15233462216 0016356 0 ustar 00 <?php namespace dokuwiki\Subscriptions; class RegistrationSubscriptionSender extends SubscriptionSender { /** * Send a notify mail on new registration * * @param string $login login name of the new user * @param string $fullname full name of the new user * @param string $email email address of the new user * * @return bool true if a mail was sent * @author Andreas Gohr <andi@splitbrain.org> * */ public function sendRegister($login, $fullname, $email) { global $conf; if (empty($conf['registernotify'])) { return false; } $trep = [ 'NEWUSER' => $login, 'NEWNAME' => $fullname, 'NEWEMAIL' => $email, ]; return $this->send( $conf['registernotify'], 'new_user', $login, 'registermail', $trep ); } } Subscriptions/SubscriberManager.php 0000644 00000020721 15233462216 0013530 0 ustar 00 <?php namespace dokuwiki\Subscriptions; use dokuwiki\Input\Input; use DokuWiki_Auth_Plugin; use Exception; class SubscriberManager { /** * Check if subscription system is enabled * * @return bool */ public function isenabled() { return actionOK('subscribe'); } /** * Adds a new subscription for the given page or namespace * * This will automatically overwrite any existent subscription for the given user on this * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces. * * @throws Exception when user or style is empty * * @param string $id The target page or namespace, specified by id; Namespaces * are identified by appending a colon. * @param string $user * @param string $style * @param string $data * * @return bool */ public function add($id, $user, $style, $data = '') { if (!$this->isenabled()) { return false; } // delete any existing subscription $this->remove($id, $user); $user = auth_nameencode(trim($user)); $style = trim($style); $data = trim($data); if (!$user) { throw new Exception('no subscription user given'); } if (!$style) { throw new Exception('no subscription style given'); } if (!$data) { $data = time(); } //always add current time for new subscriptions $line = "$user $style $data\n"; $file = $this->file($id); return io_saveFile($file, $line, true); } /** * Removes a subscription for the given page or namespace * * This removes all subscriptions matching the given criteria on the given page or * namespace. It will *not* modify any subscriptions that may exist in higher * namespaces. * * @param string $id The target object’s (namespace or page) id * @param string|array $user * @param string|array $style * @param string|array $data * * @return bool */ public function remove($id, $user = null, $style = null, $data = null) { if (!$this->isenabled()) { return false; } $file = $this->file($id); if (!file_exists($file)) { return true; } $regexBuilder = new SubscriberRegexBuilder(); $re = $regexBuilder->buildRegex($user, $style, $data); return io_deleteFromFile($file, $re, true); } /** * Get data for $INFO['subscribed'] * * $INFO['subscribed'] is either false if no subscription for the current page * and user is in effect. Else it contains an array of arrays with the fields * “target”, “style”, and optionally “data”. * * @author Adrian Lang <lang@cosmocode.de> * * @param string $id Page ID, defaults to global $ID * @param string $user User, defaults to $_SERVER['REMOTE_USER'] * * @return array|false */ public function userSubscription($id = '', $user = '') { if (!$this->isenabled()) { return false; } global $ID; /** @var Input $INPUT */ global $INPUT; if (!$id) { $id = $ID; } if (!$user) { $user = $INPUT->server->str('REMOTE_USER'); } if (empty($user)) { // not logged in return false; } $subs = $this->subscribers($id, $user); if (!count($subs)) { return false; } $result = []; foreach ($subs as $target => $info) { $result[] = [ 'target' => $target, 'style' => $info[$user][0], 'data' => $info[$user][1], ]; } return $result; } /** * Recursively search for matching subscriptions * * This function searches all relevant subscription files for a page or * namespace. * * @author Adrian Lang <lang@cosmocode.de> * * @param string $page The target object’s (namespace or page) id * @param string|array $user * @param string|array $style * @param string|array $data * * @return array */ public function subscribers($page, $user = null, $style = null, $data = null) { if (!$this->isenabled()) { return []; } // Construct list of files which may contain relevant subscriptions. $files = [':' => $this->file(':')]; do { $files[$page] = $this->file($page); $page = getNS(rtrim($page, ':')) . ':'; } while ($page !== ':'); $regexBuilder = new SubscriberRegexBuilder(); $re = $regexBuilder->buildRegex($user, $style, $data); // Handle files. $result = []; foreach ($files as $target => $file) { if (!file_exists($file)) { continue; } $lines = file($file); foreach ($lines as $line) { // fix old style subscription files if (strpos($line, ' ') === false) { $line = trim($line) . " every\n"; } // check for matching entries if (!preg_match($re, $line, $m)) { continue; } $u = rawurldecode($m[1]); // decode the user name if (!isset($result[$target])) { $result[$target] = []; } $result[$target][$u] = [$m[2], $m[3]]; // add to result } } return array_reverse($result); } /** * Default callback for COMMON_NOTIFY_ADDRESSLIST * * Aggregates all email addresses of user who have subscribed the given page with 'every' style * * @author Adrian Lang <lang@cosmocode.de> * @author Steven Danz <steven-danz@kc.rr.com> * * @todo move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead, * use an array for the addresses within it * * @param array &$data Containing the entries: * - $id (the page id), * - $self (whether the author should be notified, * - $addresslist (current email address list) * - $replacements (array of additional string substitutions, @KEY@ to be replaced by value) */ public function notifyAddresses(&$data) { if (!$this->isenabled()) { return; } /** @var DokuWiki_Auth_Plugin $auth */ global $auth; global $conf; /** @var \Input $INPUT */ global $INPUT; $id = $data['id']; $self = $data['self']; $addresslist = $data['addresslist']; $subscriptions = $this->subscribers($id, null, 'every'); $result = []; foreach ($subscriptions as $target => $users) { foreach ($users as $user => $info) { $userinfo = $auth->getUserData($user); if ($userinfo === false) { continue; } if (!$userinfo['mail']) { continue; } if (!$self && $user == $INPUT->server->str('REMOTE_USER')) { continue; } //skip our own changes $level = auth_aclcheck($id, $user, $userinfo['grps']); if ($level >= AUTH_READ) { if (strcasecmp($userinfo['mail'], $conf['notify']) != 0) { //skip user who get notified elsewhere $result[$user] = $userinfo['mail']; } } } } $data['addresslist'] = trim($addresslist . ',' . implode(',', $result), ','); } /** * Return the subscription meta file for the given ID * * @author Adrian Lang <lang@cosmocode.de> * * @param string $id The target page or namespace, specified by id; Namespaces * are identified by appending a colon. * * @return string */ protected function file($id) { $meta_fname = '.mlist'; if ((substr($id, -1, 1) === ':')) { $meta_froot = getNS($id); $meta_fname = '/' . $meta_fname; } else { $meta_froot = $id; } return metaFN((string)$meta_froot, $meta_fname); } } Subscriptions/MediaSubscriptionSender.php 0000644 00000002651 15233462216 0014721 0 ustar 00 <?php namespace dokuwiki\Subscriptions; class MediaSubscriptionSender extends SubscriptionSender { /** * Send the diff for some media change * * @fixme this should embed thumbnails of images in HTML version * * @param string $subscriber_mail The target mail address * @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, $current_rev = false) { global $conf; $file = mediaFN($id); list($mime, /* $ext */) = mimetype($id); $trep = [ 'MIME' => $mime, 'MEDIA' => ml($id, $current_rev?('rev='.$current_rev):'', true, '&', true), 'SIZE' => filesize_h(filesize($file)), ]; if ($rev && $conf['mediarevisions']) { $trep['OLD'] = ml($id, "rev=$rev", true, '&', true); } else { $trep['OLD'] = '---'; } $headers = ['Message-Id' => $this->getMessageID($id, @filemtime($file))]; if ($rev) { $headers['In-Reply-To'] = $this->getMessageID($id, $rev); } $this->send($subscriber_mail, 'upload', $id, $template, $trep, null, $headers); } } Subscriptions/BulkSubscriptionSender.php 0000644 00000020077 15233462216 0014601 0 ustar 00 <?php namespace dokuwiki\Subscriptions; use dokuwiki\ChangeLog\PageChangeLog; use dokuwiki\Input\Input; use DokuWiki_Auth_Plugin; class BulkSubscriptionSender extends SubscriptionSender { /** * Send digest and list subscriptions * * This sends mails to all subscribers that have a subscription for namespaces above * the given page if the needed $conf['subscribe_time'] has passed already. * * This function is called form lib/exe/indexer.php * * @param string $page * * @return int number of sent mails */ public function sendBulk($page) { $subscriberManager = new SubscriberManager(); if (!$subscriberManager->isenabled()) { return 0; } /** @var DokuWiki_Auth_Plugin $auth */ global $auth; global $conf; global $USERINFO; /** @var Input $INPUT */ global $INPUT; $count = 0; $subscriptions = $subscriberManager->subscribers($page, null, ['digest', 'list']); // remember current user info $olduinfo = $USERINFO; $olduser = $INPUT->server->str('REMOTE_USER'); foreach ($subscriptions as $target => $users) { if (!$this->lock($target)) { continue; } foreach ($users as $user => $info) { list($style, $lastupdate) = $info; $lastupdate = (int)$lastupdate; if ($lastupdate + $conf['subscribe_time'] > time()) { // Less than the configured time period passed since last // update. continue; } // Work as the user to make sure ACLs apply correctly $USERINFO = $auth->getUserData($user); $INPUT->server->set('REMOTE_USER', $user); if ($USERINFO === false) { continue; } if (!$USERINFO['mail']) { continue; } if (substr($target, -1, 1) === ':') { // subscription target is a namespace, get all changes within $changes = getRecentsSince($lastupdate, null, getNS($target)); } else { // single page subscription, check ACL ourselves if (auth_quickaclcheck($target) < AUTH_READ) { continue; } $meta = p_get_metadata($target); $changes = [$meta['last_change']]; } // Filter out pages only changed in small and own edits $change_ids = []; foreach ($changes as $rev) { $n = 0; while (!is_null($rev) && $rev['date'] >= $lastupdate && ($INPUT->server->str('REMOTE_USER') === $rev['user'] || $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)) { $pagelog = new PageChangeLog($rev['id']); $rev = $pagelog->getRevisions($n++, 1); $rev = (count($rev) > 0) ? $rev[0] : null; } if (!is_null($rev) && $rev['date'] >= $lastupdate) { // Some change was not a minor one and not by myself $change_ids[] = $rev['id']; } } // send it if ($style === 'digest') { foreach ($change_ids as $change_id) { $this->sendDigest( $USERINFO['mail'], $change_id, $lastupdate ); $count++; } } else { if ($style === 'list') { $this->sendList($USERINFO['mail'], $change_ids, $target); $count++; } } // TODO: Handle duplicate subscriptions. // Update notification time. $subscriberManager->add($target, $user, $style, time()); } $this->unlock($target); } // restore current user info $USERINFO = $olduinfo; $INPUT->server->set('REMOTE_USER', $olduser); return $count; } /** * Lock subscription info * * We don't use io_lock() her because we do not wait for the lock and use a larger stale time * * @param string $id The target page or namespace, specified by id; Namespaces * are identified by appending a colon. * * @return bool true, if you got a succesful lock * @author Adrian Lang <lang@cosmocode.de> */ protected function lock($id) { global $conf; $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock'; if (is_dir($lock) && time() - @filemtime($lock) > 60 * 5) { // looks like a stale lock - remove it @rmdir($lock); } // try creating the lock directory if (!@mkdir($lock, $conf['dmode'])) { return false; } if (!empty($conf['dperm'])) { chmod($lock, $conf['dperm']); } return true; } /** * Unlock subscription info * * @param string $id The target page or namespace, specified by id; Namespaces * are identified by appending a colon. * * @return bool * @author Adrian Lang <lang@cosmocode.de> */ protected function unlock($id) { global $conf; $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock'; return @rmdir($lock); } /** * Send a digest mail * * Sends a digest mail showing a bunch of changes of a single page. Basically the same as sendPageDiff() * but determines the last known revision first * * @param string $subscriber_mail The target mail address * @param string $id The ID * @param int $lastupdate Time of the last notification * * @return bool * @author Adrian Lang <lang@cosmocode.de> * */ protected function sendDigest($subscriber_mail, $id, $lastupdate) { $pagelog = new PageChangeLog($id); $n = 0; do { $rev = $pagelog->getRevisions($n++, 1); $rev = (count($rev) > 0) ? $rev[0] : null; } while (!is_null($rev) && $rev > $lastupdate); // TODO I'm not happy with the following line and passing $this->mailer around. Not sure how to solve it better $pageSubSender = new PageSubscriptionSender($this->mailer); return $pageSubSender->sendPageDiff( $subscriber_mail, 'subscr_digest', $id, $rev ); } /** * Send a list mail * * Sends a list mail showing a list of changed pages. * * @param string $subscriber_mail The target mail address * @param array $ids Array of ids * @param string $ns_id The id of the namespace * * @return bool true if a mail was sent * @author Adrian Lang <lang@cosmocode.de> * */ protected function sendList($subscriber_mail, $ids, $ns_id) { if (count($ids) === 0) { return false; } $tlist = ''; $hlist = '<ul>'; foreach ($ids as $id) { $link = wl($id, [], true); $tlist .= '* ' . $link . NL; $hlist .= '<li><a href="' . $link . '">' . hsc($id) . '</a></li>' . NL; } $hlist .= '</ul>'; $id = prettyprint_id($ns_id); $trep = [ 'DIFF' => rtrim($tlist), 'PAGE' => $id, 'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'), ]; $hrep = [ 'DIFF' => $hlist, ]; return $this->send( $subscriber_mail, 'subscribe_list', $ns_id, 'subscr_list', $trep, $hrep ); } } Subscriptions/PageSubscriptionSender.php 0000644 00000005107 15233462216 0014555 0 ustar 00 <?php namespace dokuwiki\Subscriptions; use Diff; use InlineDiffFormatter; use UnifiedDiffFormatter; class PageSubscriptionSender extends SubscriptionSender { /** * Send the diff for some page change * * @param string $subscriber_mail The target mail address * @param string $template Mail template ('subscr_digest', 'subscr_single', 'mailtext', ...) * @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 = '', $current_rev = null) { global $DIFF_INLINESTYLES; // prepare replacements (keys not set in hrep will be taken from trep) $trep = [ 'PAGE' => $id, 'NEWPAGE' => wl($id, $current_rev?('rev='.$current_rev):'', true, '&'), 'SUMMARY' => $summary, 'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'), ]; $hrep = []; if ($rev) { $subject = 'changed'; $trep['OLDPAGE'] = wl($id, "rev=$rev", true, '&'); $old_content = rawWiki($id, $rev); $new_content = rawWiki($id); $df = new Diff( explode("\n", $old_content), explode("\n", $new_content) ); $dformat = new UnifiedDiffFormatter(); $tdiff = $dformat->format($df); $DIFF_INLINESTYLES = true; $df = new Diff( explode("\n", $old_content), explode("\n", $new_content) ); $dformat = new InlineDiffFormatter(); $hdiff = $dformat->format($df); $hdiff = '<table>' . $hdiff . '</table>'; $DIFF_INLINESTYLES = false; } else { $subject = 'newpage'; $trep['OLDPAGE'] = '---'; $tdiff = rawWiki($id); $hdiff = nl2br(hsc($tdiff)); } $trep['DIFF'] = $tdiff; $hrep['DIFF'] = $hdiff; $headers = ['Message-Id' => $this->getMessageID($id)]; if ($rev) { $headers['In-Reply-To'] = $this->getMessageID($id, $rev); } return $this->send( $subscriber_mail, $subject, $id, $template, $trep, $hrep, $headers ); } } Subscriptions/SubscriptionSender.php 0000644 00000005465 15233462216 0013767 0 ustar 00 <?php namespace dokuwiki\Subscriptions; use Mailer; abstract class SubscriptionSender { protected $mailer; public function __construct(Mailer $mailer = null) { if ($mailer === null) { $mailer = new Mailer(); } $this->mailer = $mailer; } /** * Get a valid message id for a certain $id and revision (or the current revision) * * @param string $id The id of the page (or media file) the message id should be for * @param string $rev The revision of the page, set to the current revision of the page $id if not set * * @return string */ protected function getMessageID($id, $rev = null) { static $listid = null; if (is_null($listid)) { $server = parse_url(DOKU_URL, PHP_URL_HOST); $listid = join('.', array_reverse(explode('/', DOKU_BASE))) . $server; $listid = urlencode($listid); $listid = strtolower(trim($listid, '.')); } if (is_null($rev)) { $rev = @filemtime(wikiFN($id)); } return "<$id?rev=$rev@$listid>"; } /** * Helper function for sending a mail * * @param string $subscriber_mail The target mail address * @param string $subject The lang id of the mail subject (without the * prefix “mail_”) * @param string $context The context of this mail, eg. page or namespace id * @param string $template The name of the mail template * @param array $trep Predefined parameters used to parse the * template (in text format) * @param array $hrep Predefined parameters used to parse the * template (in HTML format), null to default to $trep * @param array $headers Additional mail headers in the form 'name' => 'value' * * @return bool * @author Adrian Lang <lang@cosmocode.de> * */ protected function send($subscriber_mail, $subject, $context, $template, $trep, $hrep = null, $headers = []) { global $lang; global $conf; $text = rawLocale($template); $subject = $lang['mail_' . $subject] . ' ' . $context; $mail = $this->mailer; $mail->bcc($subscriber_mail); $mail->subject($subject); $mail->setBody($text, $trep, $hrep); if (in_array($template, ['subscr_list', 'subscr_digest'])) { $mail->from($conf['mailfromnobody']); } if (isset($trep['SUBSCRIBE'])) { $mail->setHeader('List-Unsubscribe', '<' . $trep['SUBSCRIBE'] . '>', false); } foreach ($headers as $header => $value) { $mail->setHeader($header, $value); } return $mail->send(); } } Cache/CacheInstructions.php 0000644 00000002112 15233462216 0011710 0 ustar 00 <?php namespace dokuwiki\Cache; /** * Caching of parser instructions */ class CacheInstructions extends \dokuwiki\Cache\CacheParser { /** * @param string $id page id * @param string $file source file for cache */ public function __construct($id, $file) { parent::__construct($id, $file, 'i'); } /** * retrieve the cached data * * @param bool $clean true to clean line endings, false to leave line endings alone * @return array cache contents */ public function retrieveCache($clean = true) { $contents = io_readFile($this->cache, false); return !empty($contents) ? unserialize($contents) : array(); } /** * cache $instructions * * @param array $instructions the instruction to be cached * @return bool true on success, false otherwise */ public function storeCache($instructions) { if ($this->_nocache) { return false; } return io_saveFile($this->cache, serialize($instructions)); } } Cache/CacheRenderer.php 0000644 00000005457 15233462216 0010771 0 ustar 00 <?php namespace dokuwiki\Cache; /** * Caching of data of renderer */ class CacheRenderer extends CacheParser { /** * method contains cache use decision logic * * @return bool see useCache() */ public function makeDefaultCacheDecision() { global $conf; if (!parent::makeDefaultCacheDecision()) { return false; } if (!isset($this->page)) { return true; } // meta cache older than file it depends on? if ($this->_time < @filemtime(metaFN($this->page, '.meta'))) { return false; } // check current link existence is consistent with cache version // first check the purgefile // - if the cache is more recent than the purgefile we know no links can have been updated if ($this->_time >= @filemtime($conf['cachedir'] . '/purgefile')) { return true; } // for wiki pages, check metadata dependencies $metadata = p_get_metadata($this->page); if (!isset($metadata['relation']['references']) || empty($metadata['relation']['references'])) { return true; } foreach ($metadata['relation']['references'] as $id => $exists) { if ($exists != page_exists($id, '', false)) { return false; } } return true; } protected function addDependencies() { global $conf; // default renderer cache file 'age' is dependent on 'cachetime' setting, two special values: // -1 : do not cache (should not be overridden) // 0 : cache never expires (can be overridden) - no need to set depends['age'] if ($conf['cachetime'] == -1) { $this->_nocache = true; return; } elseif ($conf['cachetime'] > 0) { $this->depends['age'] = isset($this->depends['age']) ? min($this->depends['age'], $conf['cachetime']) : $conf['cachetime']; } // renderer cache file dependencies ... $files = array( DOKU_INC . 'inc/parser/' . $this->mode . '.php', // ... the renderer ); // page implies metadata and possibly some other dependencies if (isset($this->page)) { // for xhtml this will render the metadata if needed $valid = p_get_metadata($this->page, 'date valid'); if (!empty($valid['age'])) { $this->depends['age'] = isset($this->depends['age']) ? min($this->depends['age'], $valid['age']) : $valid['age']; } } $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files; parent::addDependencies(); } } Cache/CacheParser.php 0000644 00000003265 15233462216 0010452 0 ustar 00 <?php namespace dokuwiki\Cache; /** * Parser caching */ class CacheParser extends Cache { public $file = ''; // source file for cache public $mode = ''; // input mode (represents the processing the input file will undergo) public $page = ''; /** * * @param string $id page id * @param string $file source file for cache * @param string $mode input mode */ public function __construct($id, $file, $mode) { if ($id) { $this->page = $id; } $this->file = $file; $this->mode = $mode; $this->setEvent('PARSER_CACHE_USE'); parent::__construct($file . $_SERVER['HTTP_HOST'] . $_SERVER['SERVER_PORT'], '.' . $mode); } /** * method contains cache use decision logic * * @return bool see useCache() */ public function makeDefaultCacheDecision() { if (!file_exists($this->file)) { return false; } // source exists? return parent::makeDefaultCacheDecision(); } protected function addDependencies() { // parser cache file dependencies ... $files = array( $this->file, // ... source DOKU_INC . 'inc/parser/Parser.php', // ... parser DOKU_INC . 'inc/parser/handler.php', // ... handler ); $files = array_merge($files, getConfigFiles('main')); // ... wiki settings $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files; parent::addDependencies(); } } Cache/Cache.php 0000644 00000015302 15233462216 0007270 0 ustar 00 <?php namespace dokuwiki\Cache; use dokuwiki\Debug\PropertyDeprecationHelper; use dokuwiki\Extension\Event; /** * Generic handling of caching */ class Cache { use PropertyDeprecationHelper; public $key = ''; // primary identifier for this item public $ext = ''; // file ext for cache data, secondary identifier for this item public $cache = ''; // cache file name public $depends = array(); // array containing cache dependency information, // used by makeDefaultCacheDecision to determine cache validity // phpcs:disable /** * @deprecated since 2019-02-02 use the respective getters instead! */ protected $_event = ''; // event to be triggered during useCache protected $_time; protected $_nocache = false; // if set to true, cache will not be used or stored // phpcs:enable /** * @param string $key primary identifier * @param string $ext file extension */ public function __construct($key, $ext) { $this->key = $key; $this->ext = $ext; $this->cache = getCacheName($key, $ext); /** * @deprecated since 2019-02-02 use the respective getters instead! */ $this->deprecatePublicProperty('_event'); $this->deprecatePublicProperty('_time'); $this->deprecatePublicProperty('_nocache'); } public function getTime() { return $this->_time; } public function getEvent() { return $this->_event; } public function setEvent($event) { $this->_event = $event; } /** * public method to determine whether the cache can be used * * to assist in centralisation of event triggering and calculation of cache statistics, * don't override this function override makeDefaultCacheDecision() * * @param array $depends array of cache dependencies, support dependecies: * 'age' => max age of the cache in seconds * 'files' => cache must be younger than mtime of each file * (nb. dependency passes if file doesn't exist) * * @return bool true if cache can be used, false otherwise */ public function useCache($depends = array()) { $this->depends = $depends; $this->addDependencies(); if ($this->getEvent()) { return $this->stats( Event::createAndTrigger( $this->getEvent(), $this, array($this, 'makeDefaultCacheDecision') ) ); } return $this->stats($this->makeDefaultCacheDecision()); } /** * internal method containing cache use decision logic * * this function processes the following keys in the depends array * purge - force a purge on any non empty value * age - expire cache if older than age (seconds) * files - expire cache if any file in this array was updated more recently than the cache * * Note that this function needs to be public as it is used as callback for the event handler * * can be overridden * * @internal This method may only be called by the event handler! Call \dokuwiki\Cache\Cache::useCache instead! * * @return bool see useCache() */ public function makeDefaultCacheDecision() { if ($this->_nocache) { return false; } // caching turned off if (!empty($this->depends['purge'])) { return false; } // purge requested? if (!($this->_time = @filemtime($this->cache))) { return false; } // cache exists? // cache too old? if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) { return false; } if (!empty($this->depends['files'])) { foreach ($this->depends['files'] as $file) { if ($this->_time <= @filemtime($file)) { return false; } // cache older than files it depends on? } } return true; } /** * add dependencies to the depends array * * this method should only add dependencies, * it should not remove any existing dependencies and * it should only overwrite a dependency when the new value is more stringent than the old */ protected function addDependencies() { global $INPUT; if ($INPUT->has('purge')) { $this->depends['purge'] = true; } // purge requested } /** * retrieve the cached data * * @param bool $clean true to clean line endings, false to leave line endings alone * @return string cache contents */ public function retrieveCache($clean = true) { return io_readFile($this->cache, $clean); } /** * cache $data * * @param string $data the data to be cached * @return bool true on success, false otherwise */ public function storeCache($data) { if ($this->_nocache) { return false; } return io_saveFile($this->cache, $data); } /** * remove any cached data associated with this cache instance */ public function removeCache() { @unlink($this->cache); } /** * Record cache hits statistics. * (Only when debugging allowed, to reduce overhead.) * * @param bool $success result of this cache use attempt * @return bool pass-thru $success value */ protected function stats($success) { global $conf; static $stats = null; static $file; if (!$conf['allowdebug']) { return $success; } if (is_null($stats)) { $file = $conf['cachedir'] . '/cache_stats.txt'; $lines = explode("\n", io_readFile($file)); foreach ($lines as $line) { $i = strpos($line, ','); $stats[substr($line, 0, $i)] = $line; } } if (isset($stats[$this->ext])) { list($ext, $count, $hits) = explode(',', $stats[$this->ext]); } else { $ext = $this->ext; $count = 0; $hits = 0; } $count++; if ($success) { $hits++; } $stats[$this->ext] = "$ext,$count,$hits"; io_saveFile($file, join("\n", $stats)); return $success; } /** * @return bool */ public function isNoCache() { return $this->_nocache; } } compatibility.php 0000644 00000004317 15233462216 0010137 0 ustar 00 <?php /** * compatibility functions * * This file contains a few functions that might be missing from the PHP build */ if(!function_exists('ctype_space')) { /** * Check for whitespace character(s) * * @see ctype_space * @param string $text * @return bool */ function ctype_space($text) { if(!is_string($text)) return false; #FIXME original treats between -128 and 255 inclusive as ASCII chars if(trim($text) === '') return true; return false; } } if(!function_exists('ctype_digit')) { /** * Check for numeric character(s) * * @see ctype_digit * @param string $text * @return bool */ function ctype_digit($text) { if(!is_string($text)) return false; #FIXME original treats between -128 and 255 inclusive as ASCII chars if(preg_match('/^\d+$/', $text)) return true; return false; } } if(!function_exists('gzopen') && function_exists('gzopen64')) { /** * work around for PHP compiled against certain zlib versions #865 * * @link http://stackoverflow.com/questions/23417519/php-zlib-gzopen-not-exists * * @param string $filename * @param string $mode * @param int $use_include_path * @return mixed */ function gzopen($filename, $mode, $use_include_path = 0) { return gzopen64($filename, $mode, $use_include_path); } } if(!function_exists('gzseek') && function_exists('gzseek64')) { /** * work around for PHP compiled against certain zlib versions #865 * * @link http://stackoverflow.com/questions/23417519/php-zlib-gzopen-not-exists * * @param resource $zp * @param int $offset * @param int $whence * @return int */ function gzseek($zp, $offset, $whence = SEEK_SET) { return gzseek64($zp, $offset, $whence); } } if(!function_exists('gztell') && function_exists('gztell64')) { /** * work around for PHP compiled against certain zlib versions #865 * * @link http://stackoverflow.com/questions/23417519/php-zlib-gzopen-not-exists * * @param resource $zp * @return int */ function gztell($zp) { return gztell64($zp); } } httputils.php 0000644 00000024477 15233462216 0007337 0 ustar 00 <?php /** * Utilities for handling HTTP related tasks * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ define('HTTP_MULTIPART_BOUNDARY','D0KuW1K1B0uNDARY'); define('HTTP_HEADER_LF',"\r\n"); define('HTTP_CHUNK_SIZE',16*1024); /** * Checks and sets HTTP headers for conditional HTTP requests * * @author Simon Willison <swillison@gmail.com> * @link http://simonwillison.net/2003/Apr/23/conditionalGet/ * * @param int $timestamp lastmodified time of the cache file * @returns void or exits with previously header() commands executed */ function http_conditionalRequest($timestamp){ // A PHP implementation of conditional get, see // http://fishbowl.pastiche.org/2002/10/21/http_conditional_get_for_rss_hackers/ $last_modified = substr(gmdate('r', $timestamp), 0, -5).'GMT'; $etag = '"'.md5($last_modified).'"'; // Send the headers header("Last-Modified: $last_modified"); header("ETag: $etag"); // See if the client has provided the required headers if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])){ $if_modified_since = stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']); }else{ $if_modified_since = false; } if (isset($_SERVER['HTTP_IF_NONE_MATCH'])){ $if_none_match = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']); }else{ $if_none_match = false; } if (!$if_modified_since && !$if_none_match){ return; } // At least one of the headers is there - check them if ($if_none_match && $if_none_match != $etag) { return; // etag is there but doesn't match } if ($if_modified_since && $if_modified_since != $last_modified) { return; // if-modified-since is there but doesn't match } // Nothing has changed since their last request - serve a 304 and exit header('HTTP/1.0 304 Not Modified'); // don't produce output, even if compression is on @ob_end_clean(); exit; } /** * Let the webserver send the given file via x-sendfile method * * @author Chris Smith <chris@jalakai.co.uk> * * @param string $file absolute path of file to send * @returns void or exits with previous header() commands executed */ function http_sendfile($file) { global $conf; //use x-sendfile header to pass the delivery to compatible web servers if($conf['xsendfile'] == 1){ header("X-LIGHTTPD-send-file: $file"); ob_end_clean(); exit; }elseif($conf['xsendfile'] == 2){ header("X-Sendfile: $file"); ob_end_clean(); exit; }elseif($conf['xsendfile'] == 3){ // FS#2388 nginx just needs the relative path. $file = DOKU_REL.substr($file, strlen(fullpath(DOKU_INC)) + 1); header("X-Accel-Redirect: $file"); ob_end_clean(); exit; } } /** * Send file contents supporting rangeRequests * * This function exits the running script * * @param resource $fh - file handle for an already open file * @param int $size - size of the whole file * @param int $mime - MIME type of the file * * @author Andreas Gohr <andi@splitbrain.org> */ function http_rangeRequest($fh,$size,$mime){ $ranges = array(); $isrange = false; header('Accept-Ranges: bytes'); if(!isset($_SERVER['HTTP_RANGE'])){ // no range requested - send the whole file $ranges[] = array(0,$size,$size); }else{ $t = explode('=', $_SERVER['HTTP_RANGE']); if (!$t[0]=='bytes') { // we only understand byte ranges - send the whole file $ranges[] = array(0,$size,$size); }else{ $isrange = true; // handle multiple ranges $r = explode(',',$t[1]); foreach($r as $x){ $p = explode('-', $x); $start = (int)$p[0]; $end = (int)$p[1]; if (!$end) $end = $size - 1; if ($start > $end || $start > $size || $end > $size){ header('HTTP/1.1 416 Requested Range Not Satisfiable'); print 'Bad Range Request!'; exit; } $len = $end - $start + 1; $ranges[] = array($start,$end,$len); } } } $parts = count($ranges); // now send the type and length headers if(!$isrange){ header("Content-Type: $mime",true); }else{ header('HTTP/1.1 206 Partial Content'); if($parts == 1){ header("Content-Type: $mime",true); }else{ header('Content-Type: multipart/byteranges; boundary='.HTTP_MULTIPART_BOUNDARY,true); } } // send all ranges for($i=0; $i<$parts; $i++){ list($start,$end,$len) = $ranges[$i]; // multipart or normal headers if($parts > 1){ echo HTTP_HEADER_LF.'--'.HTTP_MULTIPART_BOUNDARY.HTTP_HEADER_LF; echo "Content-Type: $mime".HTTP_HEADER_LF; echo "Content-Range: bytes $start-$end/$size".HTTP_HEADER_LF; echo HTTP_HEADER_LF; }else{ header("Content-Length: $len"); if($isrange){ header("Content-Range: bytes $start-$end/$size"); } } // send file content fseek($fh,$start); //seek to start of range $chunk = ($len > HTTP_CHUNK_SIZE) ? HTTP_CHUNK_SIZE : $len; while (!feof($fh) && $chunk > 0) { @set_time_limit(30); // large files can take a lot of time print fread($fh, $chunk); flush(); $len -= $chunk; $chunk = ($len > HTTP_CHUNK_SIZE) ? HTTP_CHUNK_SIZE : $len; } } if($parts > 1){ echo HTTP_HEADER_LF.'--'.HTTP_MULTIPART_BOUNDARY.'--'.HTTP_HEADER_LF; } // everything should be done here, exit (or return if testing) if (defined('SIMPLE_TEST')) return; exit; } /** * Check for a gzipped version and create if necessary * * return true if there exists a gzip version of the uncompressed file * (samepath/samefilename.sameext.gz) created after the uncompressed file * * @author Chris Smith <chris.eureka@jalakai.co.uk> * * @param string $uncompressed_file * @return bool */ function http_gzip_valid($uncompressed_file) { if(!DOKU_HAS_GZIP) return false; $gzip = $uncompressed_file.'.gz'; if (filemtime($gzip) < filemtime($uncompressed_file)) { // filemtime returns false (0) if file doesn't exist return copy($uncompressed_file, 'compress.zlib://'.$gzip); } return true; } /** * Set HTTP headers and echo cachefile, if useable * * This function handles output of cacheable resource files. It ses the needed * HTTP headers. If a useable cache is present, it is passed to the web server * and the script is terminated. * * @param string $cache cache file name * @param bool $cache_ok if cache can be used */ function http_cached($cache, $cache_ok) { global $conf; // check cache age & handle conditional request // since the resource files are timestamped, we can use a long max age: 1 year header('Cache-Control: public, max-age=31536000'); header('Pragma: public'); if($cache_ok){ http_conditionalRequest(filemtime($cache)); if($conf['allowdebug']) header("X-CacheUsed: $cache"); // finally send output if ($conf['gzip_output'] && http_gzip_valid($cache)) { header('Vary: Accept-Encoding'); header('Content-Encoding: gzip'); readfile($cache.".gz"); } else { http_sendfile($cache); readfile($cache); } exit; } http_conditionalRequest(time()); } /** * Cache content and print it * * @param string $file file name * @param string $content */ function http_cached_finish($file, $content) { global $conf; // save cache file io_saveFile($file, $content); if(DOKU_HAS_GZIP) io_saveFile("$file.gz",$content); // finally send output if ($conf['gzip_output'] && DOKU_HAS_GZIP) { header('Vary: Accept-Encoding'); header('Content-Encoding: gzip'); print gzencode($content,9,FORCE_GZIP); } else { print $content; } } /** * Fetches raw, unparsed POST data * * @return string */ function http_get_raw_post_data() { static $postData = null; if ($postData === null) { $postData = file_get_contents('php://input'); } return $postData; } /** * Set the HTTP response status and takes care of the used PHP SAPI * * Inspired by CodeIgniter's set_status_header function * * @param int $code * @param string $text */ function http_status($code = 200, $text = '') { static $stati = array( 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported' ); if($text == '' && isset($stati[$code])) { $text = $stati[$code]; } $server_protocol = (isset($_SERVER['SERVER_PROTOCOL'])) ? $_SERVER['SERVER_PROTOCOL'] : false; if(substr(php_sapi_name(), 0, 3) == 'cgi' || defined('SIMPLE_TEST')) { header("Status: {$code} {$text}", true); } elseif($server_protocol == 'HTTP/1.1' OR $server_protocol == 'HTTP/1.0') { header($server_protocol." {$code} {$text}", true, $code); } else { header("HTTP/1.1 {$code} {$text}", true, $code); } } FeedParserFile.php 0000644 00000002426 15233462216 0010105 0 ustar 00 <?php namespace dokuwiki; use dokuwiki\HTTP\DokuHTTPClient; /** * Fetch an URL using our own HTTPClient * * Replaces SimplePie's own class */ class FeedParserFile extends \SimplePie_File { protected $http; /** @noinspection PhpMissingParentConstructorInspection */ /** * Inititializes the HTTPClient * * We ignore all given parameters - they are set in DokuHTTPClient * * @inheritdoc */ public function __construct( $url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false, $curl_options = array() ) { $this->http = new DokuHTTPClient(); $this->success = $this->http->sendRequest($url); $this->headers = $this->http->resp_headers; $this->body = $this->http->resp_body; $this->error = $this->http->error; $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_FSOCKOPEN; return $this->success; } /** @inheritdoc */ public function headers() { return $this->headers; } /** @inheritdoc */ public function body() { return $this->body; } /** @inheritdoc */ public function close() { return true; } } Utf8/Unicode.php 0000644 00000023712 15233462216 0007502 0 ustar 00 <?php namespace dokuwiki\Utf8; /** * Convert between UTF-8 and a list of Unicode Code Points */ class Unicode { /** * Takes an UTF-8 string and returns an array of ints representing the * Unicode characters. Astral planes are supported ie. the ints in the * output can be > 0xFFFF. Occurrances of the BOM are ignored. Surrogates * are not allowed. * * If $strict is set to true the function returns false if the input * string isn't a valid UTF-8 octet sequence and raises a PHP error at * level E_USER_WARNING * * Note: this function has been modified slightly in this library to * trigger errors on encountering bad bytes * * @author <hsivonen@iki.fi> * @author Harry Fuecks <hfuecks@gmail.com> * @see unicode_to_utf8 * @link http://hsivonen.iki.fi/php-utf8/ * @link http://sourceforge.net/projects/phputf8/ * @todo break into less complex chunks * @todo use exceptions instead of user errors * * @param string $str UTF-8 encoded string * @param boolean $strict Check for invalid sequences? * @return mixed array of unicode code points or false if UTF-8 invalid */ public static function fromUtf8($str, $strict = false) { $mState = 0; // cached expected number of octets after the current octet // until the beginning of the next UTF8 character sequence $mUcs4 = 0; // cached Unicode character $mBytes = 1; // cached expected number of octets in the current sequence $out = array(); $len = strlen($str); for ($i = 0; $i < $len; $i++) { $in = ord($str[$i]); if ($mState === 0) { // When mState is zero we expect either a US-ASCII character or a // multi-octet sequence. if (0 === (0x80 & $in)) { // US-ASCII, pass straight through. $out[] = $in; $mBytes = 1; } else if (0xC0 === (0xE0 & $in)) { // First octet of 2 octet sequence $mUcs4 = $in; $mUcs4 = ($mUcs4 & 0x1F) << 6; $mState = 1; $mBytes = 2; } else if (0xE0 === (0xF0 & $in)) { // First octet of 3 octet sequence $mUcs4 = $in; $mUcs4 = ($mUcs4 & 0x0F) << 12; $mState = 2; $mBytes = 3; } else if (0xF0 === (0xF8 & $in)) { // First octet of 4 octet sequence $mUcs4 = $in; $mUcs4 = ($mUcs4 & 0x07) << 18; $mState = 3; $mBytes = 4; } else if (0xF8 === (0xFC & $in)) { /* First octet of 5 octet sequence. * * This is illegal because the encoded codepoint must be either * (a) not the shortest form or * (b) outside the Unicode range of 0-0x10FFFF. * Rather than trying to resynchronize, we will carry on until the end * of the sequence and let the later error handling code catch it. */ $mUcs4 = $in; $mUcs4 = ($mUcs4 & 0x03) << 24; $mState = 4; $mBytes = 5; } else if (0xFC === (0xFE & $in)) { // First octet of 6 octet sequence, see comments for 5 octet sequence. $mUcs4 = $in; $mUcs4 = ($mUcs4 & 1) << 30; $mState = 5; $mBytes = 6; } elseif ($strict) { /* Current octet is neither in the US-ASCII range nor a legal first * octet of a multi-octet sequence. */ trigger_error( 'utf8_to_unicode: Illegal sequence identifier ' . 'in UTF-8 at byte ' . $i, E_USER_WARNING ); return false; } } else { // When mState is non-zero, we expect a continuation of the multi-octet // sequence if (0x80 === (0xC0 & $in)) { // Legal continuation. $shift = ($mState - 1) * 6; $tmp = $in; $tmp = ($tmp & 0x0000003F) << $shift; $mUcs4 |= $tmp; /** * End of the multi-octet sequence. mUcs4 now contains the final * Unicode codepoint to be output */ if (0 === --$mState) { /* * Check for illegal sequences and codepoints. */ // From Unicode 3.1, non-shortest form is illegal if (((2 === $mBytes) && ($mUcs4 < 0x0080)) || ((3 === $mBytes) && ($mUcs4 < 0x0800)) || ((4 === $mBytes) && ($mUcs4 < 0x10000)) || (4 < $mBytes) || // From Unicode 3.2, surrogate characters are illegal (($mUcs4 & 0xFFFFF800) === 0xD800) || // Codepoints outside the Unicode range are illegal ($mUcs4 > 0x10FFFF)) { if ($strict) { trigger_error( 'utf8_to_unicode: Illegal sequence or codepoint ' . 'in UTF-8 at byte ' . $i, E_USER_WARNING ); return false; } } if (0xFEFF !== $mUcs4) { // BOM is legal but we don't want to output it $out[] = $mUcs4; } //initialize UTF8 cache $mState = 0; $mUcs4 = 0; $mBytes = 1; } } elseif ($strict) { /** *((0xC0 & (*in) != 0x80) && (mState != 0)) * Incomplete multi-octet sequence. */ trigger_error( 'utf8_to_unicode: Incomplete multi-octet ' . ' sequence in UTF-8 at byte ' . $i, E_USER_WARNING ); return false; } } } return $out; } /** * Takes an array of ints representing the Unicode characters and returns * a UTF-8 string. Astral planes are supported ie. the ints in the * input can be > 0xFFFF. Occurrances of the BOM are ignored. Surrogates * are not allowed. * * If $strict is set to true the function returns false if the input * array contains ints that represent surrogates or are outside the * Unicode range and raises a PHP error at level E_USER_WARNING * * Note: this function has been modified slightly in this library to use * output buffering to concatenate the UTF-8 string (faster) as well as * reference the array by it's keys * * @param array $arr of unicode code points representing a string * @param boolean $strict Check for invalid sequences? * @return string|false UTF-8 string or false if array contains invalid code points * * @author <hsivonen@iki.fi> * @author Harry Fuecks <hfuecks@gmail.com> * @see utf8_to_unicode * @link http://hsivonen.iki.fi/php-utf8/ * @link http://sourceforge.net/projects/phputf8/ * @todo use exceptions instead of user errors */ public static function toUtf8($arr, $strict = false) { if (!is_array($arr)) return ''; ob_start(); foreach (array_keys($arr) as $k) { if (($arr[$k] >= 0) && ($arr[$k] <= 0x007f)) { # ASCII range (including control chars) echo chr($arr[$k]); } else if ($arr[$k] <= 0x07ff) { # 2 byte sequence echo chr(0xc0 | ($arr[$k] >> 6)); echo chr(0x80 | ($arr[$k] & 0x003f)); } else if ($arr[$k] == 0xFEFF) { # Byte order mark (skip) // nop -- zap the BOM } else if ($arr[$k] >= 0xD800 && $arr[$k] <= 0xDFFF) { # Test for illegal surrogates // found a surrogate if ($strict) { trigger_error( 'unicode_to_utf8: Illegal surrogate ' . 'at index: ' . $k . ', value: ' . $arr[$k], E_USER_WARNING ); return false; } } else if ($arr[$k] <= 0xffff) { # 3 byte sequence echo chr(0xe0 | ($arr[$k] >> 12)); echo chr(0x80 | (($arr[$k] >> 6) & 0x003f)); echo chr(0x80 | ($arr[$k] & 0x003f)); } else if ($arr[$k] <= 0x10ffff) { # 4 byte sequence echo chr(0xf0 | ($arr[$k] >> 18)); echo chr(0x80 | (($arr[$k] >> 12) & 0x3f)); echo chr(0x80 | (($arr[$k] >> 6) & 0x3f)); echo chr(0x80 | ($arr[$k] & 0x3f)); } elseif ($strict) { trigger_error( 'unicode_to_utf8: Codepoint out of Unicode range ' . 'at index: ' . $k . ', value: ' . $arr[$k], E_USER_WARNING ); // out of range return false; } } return ob_get_clean(); } } Utf8/PhpString.php 0000644 00000027615 15233462216 0010040 0 ustar 00 <?php namespace dokuwiki\Utf8; /** * UTF-8 aware equivalents to PHP's string functions */ class PhpString { /** * A locale independent basename() implementation * * works around a bug in PHP's basename() implementation * * @param string $path A path * @param string $suffix If the name component ends in suffix this will also be cut off * @return string * @link https://bugs.php.net/bug.php?id=37738 * * @see basename() */ public static function basename($path, $suffix = '') { $path = trim($path, '\\/'); $rpos = max(strrpos($path, '/'), strrpos($path, '\\')); if ($rpos) { $path = substr($path, $rpos + 1); } $suflen = strlen($suffix); if ($suflen && (substr($path, -$suflen) === $suffix)) { $path = substr($path, 0, -$suflen); } return $path; } /** * Unicode aware replacement for strlen() * * utf8_decode() converts characters that are not in ISO-8859-1 * to '?', which, for the purpose of counting, is alright - It's * even faster than mb_strlen. * * @param string $string * @return int * @see utf8_decode() * * @author <chernyshevsky at hotmail dot com> * @see strlen() */ public static function strlen($string) { if (function_exists('utf8_decode')) { return strlen(utf8_decode($string)); } if (UTF8_MBSTRING) { return mb_strlen($string, 'UTF-8'); } if (function_exists('iconv_strlen')) { return iconv_strlen($string, 'UTF-8'); } return strlen($string); } /** * UTF-8 aware alternative to substr * * Return part of a string given character offset (and optionally length) * * @param string $str * @param int $offset number of UTF-8 characters offset (from left) * @param int $length (optional) length in UTF-8 characters from offset * @return string * @author Harry Fuecks <hfuecks@gmail.com> * @author Chris Smith <chris@jalakai.co.uk> * */ public static function substr($str, $offset, $length = null) { if (UTF8_MBSTRING) { if ($length === null) { return mb_substr($str, $offset); } return mb_substr($str, $offset, $length); } /* * Notes: * * no mb string support, so we'll use pcre regex's with 'u' flag * pcre only supports repetitions of less than 65536, in order to accept up to MAXINT values for * offset and length, we'll repeat a group of 65535 characters when needed (ok, up to MAXINT-65536) * * substr documentation states false can be returned in some cases (e.g. offset > string length) * mb_substr never returns false, it will return an empty string instead. * * calculating the number of characters in the string is a relatively expensive operation, so * we only carry it out when necessary. It isn't necessary for +ve offsets and no specified length */ // cast parameters to appropriate types to avoid multiple notices/warnings $str = (string)$str; // generates E_NOTICE for PHP4 objects, but not PHP5 objects $offset = (int)$offset; if ($length !== null) $length = (int)$length; // handle trivial cases if ($length === 0) return ''; if ($offset < 0 && $length < 0 && $length < $offset) return ''; $offset_pattern = ''; $length_pattern = ''; // normalise -ve offsets (we could use a tail anchored pattern, but they are horribly slow!) if ($offset < 0) { $strlen = self::strlen($str); // see notes $offset = $strlen + $offset; if ($offset < 0) $offset = 0; } // establish a pattern for offset, a non-captured group equal in length to offset if ($offset > 0) { $Ox = (int)($offset / 65535); $Oy = $offset % 65535; if ($Ox) $offset_pattern = '(?:.{65535}){' . $Ox . '}'; $offset_pattern = '^(?:' . $offset_pattern . '.{' . $Oy . '})'; } else { $offset_pattern = '^'; // offset == 0; just anchor the pattern } // establish a pattern for length if ($length === null) { $length_pattern = '(.*)$'; // the rest of the string } else { if (!isset($strlen)) $strlen = self::strlen($str); // see notes if ($offset > $strlen) return ''; // another trivial case if ($length > 0) { // reduce any length that would go past the end of the string $length = min($strlen - $offset, $length); $Lx = (int)($length / 65535); $Ly = $length % 65535; // +ve length requires ... a captured group of length characters if ($Lx) $length_pattern = '(?:.{65535}){' . $Lx . '}'; $length_pattern = '(' . $length_pattern . '.{' . $Ly . '})'; } else if ($length < 0) { if ($length < ($offset - $strlen)) return ''; $Lx = (int)((-$length) / 65535); $Ly = (-$length) % 65535; // -ve length requires ... capture everything except a group of -length characters // anchored at the tail-end of the string if ($Lx) $length_pattern = '(?:.{65535}){' . $Lx . '}'; $length_pattern = '(.*)(?:' . $length_pattern . '.{' . $Ly . '})$'; } } if (!preg_match('#' . $offset_pattern . $length_pattern . '#us', $str, $match)) return ''; return $match[1]; } // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps /** * Unicode aware replacement for substr_replace() * * @param string $string input string * @param string $replacement the replacement * @param int $start the replacing will begin at the start'th offset into string. * @param int $length If given and is positive, it represents the length of the portion of string which is * to be replaced. If length is zero then this function will have the effect of inserting * replacement into string at the given start offset. * @return string * @see substr_replace() * * @author Andreas Gohr <andi@splitbrain.org> */ public static function substr_replace($string, $replacement, $start, $length = 0) { $ret = ''; if ($start > 0) $ret .= self::substr($string, 0, $start); $ret .= $replacement; $ret .= self::substr($string, $start + $length); return $ret; } // phpcs:enable PSR1.Methods.CamelCapsMethodName.NotCamelCaps /** * Unicode aware replacement for ltrim() * * @param string $str * @param string $charlist * @return string * @see ltrim() * * @author Andreas Gohr <andi@splitbrain.org> */ public static function ltrim($str, $charlist = '') { if ($charlist === '') return ltrim($str); //quote charlist for use in a characterclass $charlist = preg_replace('!([\\\\\\-\\]\\[/])!', '\\\${1}', $charlist); return preg_replace('/^[' . $charlist . ']+/u', '', $str); } /** * Unicode aware replacement for rtrim() * * @param string $str * @param string $charlist * @return string * @see rtrim() * * @author Andreas Gohr <andi@splitbrain.org> */ public static function rtrim($str, $charlist = '') { if ($charlist === '') return rtrim($str); //quote charlist for use in a characterclass $charlist = preg_replace('!([\\\\\\-\\]\\[/])!', '\\\${1}', $charlist); return preg_replace('/[' . $charlist . ']+$/u', '', $str); } /** * Unicode aware replacement for trim() * * @param string $str * @param string $charlist * @return string * @see trim() * * @author Andreas Gohr <andi@splitbrain.org> */ public static function trim($str, $charlist = '') { if ($charlist === '') return trim($str); return self::ltrim(self::rtrim($str, $charlist), $charlist); } /** * This is a unicode aware replacement for strtolower() * * Uses mb_string extension if available * * @param string $string * @return string * @see \dokuwiki\Utf8\PhpString::strtoupper() * * @author Leo Feyer <leo@typolight.org> * @see strtolower() */ public static function strtolower($string) { if (UTF8_MBSTRING) { if (class_exists('Normalizer', $autoload = false)) { return \Normalizer::normalize(mb_strtolower($string, 'utf-8')); } return (mb_strtolower($string, 'utf-8')); } return strtr($string, Table::upperCaseToLowerCase()); } /** * This is a unicode aware replacement for strtoupper() * * Uses mb_string extension if available * * @param string $string * @return string * @see \dokuwiki\Utf8\PhpString::strtoupper() * * @author Leo Feyer <leo@typolight.org> * @see strtoupper() */ public static function strtoupper($string) { if (UTF8_MBSTRING) return mb_strtoupper($string, 'utf-8'); return strtr($string, Table::lowerCaseToUpperCase()); } /** * UTF-8 aware alternative to ucfirst * Make a string's first character uppercase * * @param string $str * @return string with first character as upper case (if applicable) * @author Harry Fuecks * */ public static function ucfirst($str) { switch (self::strlen($str)) { case 0: return ''; case 1: return self::strtoupper($str); default: preg_match('/^(.{1})(.*)$/us', $str, $matches); return self::strtoupper($matches[1]) . $matches[2]; } } /** * UTF-8 aware alternative to ucwords * Uppercase the first character of each word in a string * * @param string $str * @return string with first char of each word uppercase * @author Harry Fuecks * @see http://php.net/ucwords * */ public static function ucwords($str) { // Note: [\x0c\x09\x0b\x0a\x0d\x20] matches; // form feeds, horizontal tabs, vertical tabs, linefeeds and carriage returns // This corresponds to the definition of a "word" defined at http://php.net/ucwords $pattern = '/(^|([\x0c\x09\x0b\x0a\x0d\x20]+))([^\x0c\x09\x0b\x0a\x0d\x20]{1})[^\x0c\x09\x0b\x0a\x0d\x20]*/u'; return preg_replace_callback( $pattern, function ($matches) { $leadingws = $matches[2]; $ucfirst = self::strtoupper($matches[3]); $ucword = self::substr_replace(ltrim($matches[0]), $ucfirst, 0, 1); return $leadingws . $ucword; }, $str ); } /** * This is an Unicode aware replacement for strpos * * @param string $haystack * @param string $needle * @param integer $offset * @return integer * @author Leo Feyer <leo@typolight.org> * @see strpos() * */ public static function strpos($haystack, $needle, $offset = 0) { $comp = 0; $length = null; while ($length === null || $length < $offset) { $pos = strpos($haystack, $needle, $offset + $comp); if ($pos === false) return false; $length = self::strlen(substr($haystack, 0, $pos)); if ($length < $offset) $comp = $pos - $length; } return $length; } } Utf8/Conversion.php 0000644 00000010544 15233462216 0010240 0 ustar 00 <?php namespace dokuwiki\Utf8; /** * Methods to convert from and to UTF-8 strings */ class Conversion { /** * Encodes UTF-8 characters to HTML entities * * @author Tom N Harris <tnharris@whoopdedo.org> * @author <vpribish at shopping dot com> * @link http://php.net/manual/en/function.utf8-decode.php * * @param string $str * @param bool $all Encode non-utf8 char to HTML as well * @return string */ public static function toHtml($str, $all = false) { $ret = ''; foreach (Unicode::fromUtf8($str) as $cp) { if ($cp < 0x80 && !$all) { $ret .= chr($cp); } elseif ($cp < 0x100) { $ret .= "&#$cp;"; } else { $ret .= '&#x' . dechex($cp) . ';'; } } return $ret; } /** * Decodes HTML entities to UTF-8 characters * * Convert any &#..; entity to a codepoint, * The entities flag defaults to only decoding numeric entities. * Pass HTML_ENTITIES and named entities, including & < etc. * are handled as well. Avoids the problem that would occur if you * had to decode "&#38;&amp;#38;" * * unhtmlspecialchars(\dokuwiki\Utf8\Conversion::fromHtml($s)) -> "&&" * \dokuwiki\Utf8\Conversion::fromHtml(unhtmlspecialchars($s)) -> "&&#38;" * what it should be -> "&&#38;" * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $str UTF-8 encoded string * @param boolean $entities decode name entities in addtition to numeric ones * @return string UTF-8 encoded string with numeric (and named) entities replaced. */ public static function fromHtml($str, $entities = false) { if (!$entities) { return preg_replace_callback( '/(&#([Xx])?([0-9A-Za-z]+);)/m', [__CLASS__, 'decodeNumericEntity'], $str ); } return preg_replace_callback( '/&(#)?([Xx])?([0-9A-Za-z]+);/m', [__CLASS__, 'decodeAnyEntity'], $str ); } /** * Decodes any HTML entity to it's correct UTF-8 char equivalent * * @param string $ent An entity * @return string */ protected static function decodeAnyEntity($ent) { // create the named entity lookup table static $table = null; if ($table === null) { $table = get_html_translation_table(HTML_ENTITIES); $table = array_flip($table); $table = array_map( static function ($c) { return Unicode::toUtf8(array(ord($c))); }, $table ); } if ($ent[1] === '#') { return self::decodeNumericEntity($ent); } if (array_key_exists($ent[0], $table)) { return $table[$ent[0]]; } return $ent[0]; } /** * Decodes numeric HTML entities to their correct UTF-8 characters * * @param $ent string A numeric entity * @return string|false */ protected static function decodeNumericEntity($ent) { switch ($ent[2]) { case 'X': case 'x': $cp = hexdec($ent[3]); break; default: $cp = intval($ent[3]); break; } return Unicode::toUtf8(array($cp)); } /** * UTF-8 to UTF-16BE conversion. * * Maybe really UCS-2 without mb_string due to utf8_to_unicode limits * * @param string $str * @param bool $bom * @return string */ public static function toUtf16be($str, $bom = false) { $out = $bom ? "\xFE\xFF" : ''; if (UTF8_MBSTRING) { return $out . mb_convert_encoding($str, 'UTF-16BE', 'UTF-8'); } $uni = Unicode::fromUtf8($str); foreach ($uni as $cp) { $out .= pack('n', $cp); } return $out; } /** * UTF-8 to UTF-16BE conversion. * * Maybe really UCS-2 without mb_string due to utf8_to_unicode limits * * @param string $str * @return false|string */ public static function fromUtf16be($str) { $uni = unpack('n*', $str); return Unicode::toUtf8($uni); } } Utf8/Asian.php 0000644 00000006310 15233462216 0007142 0 ustar 00 <?php namespace dokuwiki\Utf8; /** * Methods and constants to handle Asian "words" * * This uses a crude regexp to determine which parts of an Asian string should be treated as words. * This is necessary because in some Asian languages a single unicode char represents a whole idea * without spaces separating them. */ class Asian { /** * This defines a non-capturing group for the use in regular expressions to match any asian character that * needs to be treated as a word. Uses the Unicode-Ranges for Asian characters taken from * http://en.wikipedia.org/wiki/Unicode_block */ const REGEXP = '(?:' . '[\x{0E00}-\x{0E7F}]' . // Thai '|' . '[' . '\x{2E80}-\x{3040}' . // CJK -> Hangul '\x{309D}-\x{30A0}' . '\x{30FD}-\x{31EF}\x{3200}-\x{D7AF}' . '\x{F900}-\x{FAFF}' . // CJK Compatibility Ideographs '\x{FE30}-\x{FE4F}' . // CJK Compatibility Forms "\xF0\xA0\x80\x80-\xF0\xAA\x9B\x9F" . // CJK Extension B "\xF0\xAA\x9C\x80-\xF0\xAB\x9C\xBF" . // CJK Extension C "\xF0\xAB\x9D\x80-\xF0\xAB\xA0\x9F" . // CJK Extension D "\xF0\xAF\xA0\x80-\xF0\xAF\xAB\xBF" . // CJK Compatibility Supplement ']' . '|' . '[' . // Hiragana/Katakana (can be two characters) '\x{3042}\x{3044}\x{3046}\x{3048}' . '\x{304A}-\x{3062}\x{3064}-\x{3082}' . '\x{3084}\x{3086}\x{3088}-\x{308D}' . '\x{308F}-\x{3094}' . '\x{30A2}\x{30A4}\x{30A6}\x{30A8}' . '\x{30AA}-\x{30C2}\x{30C4}-\x{30E2}' . '\x{30E4}\x{30E6}\x{30E8}-\x{30ED}' . '\x{30EF}-\x{30F4}\x{30F7}-\x{30FA}' . '][' . '\x{3041}\x{3043}\x{3045}\x{3047}\x{3049}' . '\x{3063}\x{3083}\x{3085}\x{3087}\x{308E}\x{3095}-\x{309C}' . '\x{30A1}\x{30A3}\x{30A5}\x{30A7}\x{30A9}' . '\x{30C3}\x{30E3}\x{30E5}\x{30E7}\x{30EE}\x{30F5}\x{30F6}\x{30FB}\x{30FC}' . '\x{31F0}-\x{31FF}' . ']?' . ')'; /** * Check if the given term contains Asian word characters * * @param string $term * @return bool */ public static function isAsianWords($term) { return (bool)preg_match('/' . self::REGEXP . '/u', $term); } /** * Surround all Asian words in the given text with the given separator * * @param string $text Original text containing asian words * @param string $sep the separator to use * @return string Text with separated asian words */ public static function separateAsianWords($text, $sep = ' ') { // handle asian chars as single words (may fail on older PHP version) $asia = @preg_replace('/(' . self::REGEXP . ')/u', $sep . '\1' . $sep, $text); if (!is_null($asia)) $text = $asia; // recover from regexp falure return $text; } /** * Split the given text into separate parts * * Each part is either a non-asian string, or a single asian word * * @param string $term * @return string[] */ public static function splitAsianWords($term) { return preg_split('/(' . self::REGEXP . '+)/u', $term, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); } } Utf8/Table.php 0000644 00000004024 15233462216 0007136 0 ustar 00 <?php namespace dokuwiki\Utf8; /** * Provides static access to the UTF-8 conversion tables * * Lazy-Loads tables on first access */ class Table { /** * Get the upper to lower case conversion table * * @return array */ public static function upperCaseToLowerCase() { static $table = null; if ($table === null) $table = include __DIR__ . '/tables/case.php'; return $table; } /** * Get the lower to upper case conversion table * * @return array */ public static function lowerCaseToUpperCase() { static $table = null; if ($table === null) { $uclc = self::upperCaseToLowerCase(); $table = array_flip($uclc); } return $table; } /** * Get the lower case accent table * @return array */ public static function lowerAccents() { static $table = null; if ($table === null) { $table = include __DIR__ . '/tables/loweraccents.php'; } return $table; } /** * Get the lower case accent table * @return array */ public static function upperAccents() { static $table = null; if ($table === null) { $table = include __DIR__ . '/tables/upperaccents.php'; } return $table; } /** * Get the romanization table * @return array */ public static function romanization() { static $table = null; if ($table === null) { $table = include __DIR__ . '/tables/romanization.php'; } return $table; } /** * Get the special chars as a concatenated string * @return string */ public static function specialChars() { static $string = null; if ($string === null) { $table = include __DIR__ . '/tables/specials.php'; // FIXME should we cache this to file system? $string = Unicode::toUtf8($table); } return $string; } } Utf8/Clean.php 0000644 00000014272 15233462216 0007137 0 ustar 00 <?php namespace dokuwiki\Utf8; /** * Methods to assess and clean UTF-8 strings */ class Clean { /** * Checks if a string contains 7bit ASCII only * * @author Andreas Haerter <andreas.haerter@dev.mail-node.com> * * @param string $str * @return bool */ public static function isASCII($str) { return (preg_match('/(?:[^\x00-\x7F])/', $str) !== 1); } /** * Tries to detect if a string is in Unicode encoding * * @author <bmorel@ssi.fr> * @link http://php.net/manual/en/function.utf8-encode.php * * @param string $str * @return bool */ public static function isUtf8($str) { $len = strlen($str); for ($i = 0; $i < $len; $i++) { $b = ord($str[$i]); if ($b < 0x80) continue; # 0bbbbbbb elseif (($b & 0xE0) === 0xC0) $n = 1; # 110bbbbb elseif (($b & 0xF0) === 0xE0) $n = 2; # 1110bbbb elseif (($b & 0xF8) === 0xF0) $n = 3; # 11110bbb elseif (($b & 0xFC) === 0xF8) $n = 4; # 111110bb elseif (($b & 0xFE) === 0xFC) $n = 5; # 1111110b else return false; # Does not match any model for ($j = 0; $j < $n; $j++) { # n bytes matching 10bbbbbb follow ? if ((++$i === $len) || ((ord($str[$i]) & 0xC0) !== 0x80)) return false; } } return true; } /** * Strips all high byte chars * * Returns a pure ASCII7 string * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $str * @return string */ public static function strip($str) { $ascii = ''; $len = strlen($str); for ($i = 0; $i < $len; $i++) { if (ord($str[$i]) < 128) { $ascii .= $str[$i]; } } return $ascii; } /** * Removes special characters (nonalphanumeric) from a UTF-8 string * * This function adds the controlchars 0x00 to 0x19 to the array of * stripped chars (they are not included in $UTF8_SPECIAL_CHARS) * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $string The UTF8 string to strip of special chars * @param string $repl Replace special with this string * @param string $additional Additional chars to strip (used in regexp char class) * @return string */ public static function stripspecials($string, $repl = '', $additional = '') { static $specials = null; if ($specials === null) { $specials = preg_quote(Table::specialChars(), '/'); } return preg_replace('/[' . $additional . '\x00-\x19' . $specials . ']/u', $repl, $string); } /** * Replace bad bytes with an alternative character * * ASCII character is recommended for replacement char * * PCRE Pattern to locate bad bytes in a UTF-8 string * Comes from W3 FAQ: Multilingual Forms * Note: modified to include full ASCII range including control chars * * @author Harry Fuecks <hfuecks@gmail.com> * @see http://www.w3.org/International/questions/qa-forms-utf-8 * * @param string $str to search * @param string $replace to replace bad bytes with (defaults to '?') - use ASCII * @return string */ public static function replaceBadBytes($str, $replace = '') { $UTF8_BAD = '([\x00-\x7F]' . # ASCII (including control chars) '|[\xC2-\xDF][\x80-\xBF]' . # non-overlong 2-byte '|\xE0[\xA0-\xBF][\x80-\xBF]' . # excluding overlongs '|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}' . # straight 3-byte '|\xED[\x80-\x9F][\x80-\xBF]' . # excluding surrogates '|\xF0[\x90-\xBF][\x80-\xBF]{2}' . # planes 1-3 '|[\xF1-\xF3][\x80-\xBF]{3}' . # planes 4-15 '|\xF4[\x80-\x8F][\x80-\xBF]{2}' . # plane 16 '|(.{1}))'; # invalid byte ob_start(); while (preg_match('/' . $UTF8_BAD . '/S', $str, $matches)) { if (!isset($matches[2])) { echo $matches[0]; } else { echo $replace; } $str = substr($str, strlen($matches[0])); } return ob_get_clean(); } /** * Replace accented UTF-8 characters by unaccented ASCII-7 equivalents * * Use the optional parameter to just deaccent lower ($case = -1) or upper ($case = 1) * letters. Default is to deaccent both cases ($case = 0) * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $string * @param int $case * @return string */ public static function deaccent($string, $case = 0) { if ($case <= 0) { $string = strtr($string, Table::lowerAccents()); } if ($case >= 0) { $string = strtr($string, Table::upperAccents()); } return $string; } /** * Romanize a non-latin string * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $string * @return string */ public static function romanize($string) { if (self::isASCII($string)) return $string; //nothing to do return strtr($string, Table::romanization()); } /** * adjust a byte index into a utf8 string to a utf8 character boundary * * @author chris smith <chris@jalakai.co.uk> * * @param string $str utf8 character string * @param int $i byte index into $str * @param bool $next direction to search for boundary, false = up (current character) true = down (next character) * @return int byte index into $str now pointing to a utf8 character boundary */ public static function correctIdx($str, $i, $next = false) { if ($i <= 0) return 0; $limit = strlen($str); if ($i >= $limit) return $limit; if ($next) { while (($i < $limit) && ((ord($str[$i]) & 0xC0) === 0x80)) $i++; } else { while ($i && ((ord($str[$i]) & 0xC0) === 0x80)) $i--; } return $i; } } Utf8/tables/case.php 0000644 00000027773 15233462216 0010314 0 ustar 00 <?php /** * UTF-8 Case lookup table * * This lookuptable defines the lower case letters to their corresponding * upper case letter in UTF-8 * * @author Andreas Gohr <andi@splitbrain.org> */ return [ 'A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e', 'F' => 'f', 'G' => 'g', 'H' => 'h', 'I' => 'i', 'J' => 'j', 'K' => 'k', 'L' => 'l', 'M' => 'm', 'N' => 'n', 'O' => 'o', 'P' => 'p', 'Q' => 'q', 'R' => 'r', 'S' => 's', 'T' => 't', 'U' => 'u', 'V' => 'v', 'W' => 'w', 'X' => 'x', 'Y' => 'y', 'Z' => 'z', 'À' => 'à', 'Á' => 'á', 'Â' => 'â', 'Ã' => 'ã', 'Ä' => 'ä', 'Å' => 'å', 'Æ' => 'æ', 'Ç' => 'ç', 'È' => 'è', 'É' => 'é', 'Ê' => 'ê', 'Ë' => 'ë', 'Ì' => 'ì', 'Í' => 'í', 'Î' => 'î', 'Ï' => 'ï', 'Ð' => 'ð', 'Ñ' => 'ñ', 'Ò' => 'ò', 'Ó' => 'ó', 'Ô' => 'ô', 'Õ' => 'õ', 'Ö' => 'ö', 'Ø' => 'ø', 'Ù' => 'ù', 'Ú' => 'ú', 'Û' => 'û', 'Ü' => 'ü', 'Ý' => 'ý', 'Þ' => 'þ', 'Ā' => 'ā', 'Ă' => 'ă', 'Ą' => 'ą', 'Ć' => 'ć', 'Ĉ' => 'ĉ', 'Ċ' => 'ċ', 'Č' => 'č', 'Ď' => 'ď', 'Đ' => 'đ', 'Ē' => 'ē', 'Ĕ' => 'ĕ', 'Ė' => 'ė', 'Ę' => 'ę', 'Ě' => 'ě', 'Ĝ' => 'ĝ', 'Ğ' => 'ğ', 'Ġ' => 'ġ', 'Ģ' => 'ģ', 'Ĥ' => 'ĥ', 'Ħ' => 'ħ', 'Ĩ' => 'ĩ', 'Ī' => 'ī', 'Ĭ' => 'ĭ', 'Į' => 'į', 'IJ' => 'ij', 'Ĵ' => 'ĵ', 'Ķ' => 'ķ', 'Ĺ' => 'ĺ', 'Ļ' => 'ļ', 'Ľ' => 'ľ', 'Ŀ' => 'ŀ', 'Ł' => 'ł', 'Ń' => 'ń', 'Ņ' => 'ņ', 'Ň' => 'ň', 'Ŋ' => 'ŋ', 'Ō' => 'ō', 'Ŏ' => 'ŏ', 'Ő' => 'ő', 'Œ' => 'œ', 'Ŕ' => 'ŕ', 'Ŗ' => 'ŗ', 'Ř' => 'ř', 'Ś' => 'ś', 'Ŝ' => 'ŝ', 'Ş' => 'ş', 'Š' => 'š', 'Ţ' => 'ţ', 'Ť' => 'ť', 'Ŧ' => 'ŧ', 'Ũ' => 'ũ', 'Ū' => 'ū', 'Ŭ' => 'ŭ', 'Ů' => 'ů', 'Ű' => 'ű', 'Ų' => 'ų', 'Ŵ' => 'ŵ', 'Ŷ' => 'ŷ', 'Ÿ' => 'ÿ', 'Ź' => 'ź', 'Ż' => 'ż', 'Ž' => 'ž', 'Ɓ' => 'ɓ', 'Ƃ' => 'ƃ', 'Ƅ' => 'ƅ', 'Ɔ' => 'ɔ', 'Ƈ' => 'ƈ', 'Ɖ' => 'ɖ', 'Ɗ' => 'ɗ', 'Ƌ' => 'ƌ', 'Ǝ' => 'ǝ', 'Ə' => 'ə', 'Ɛ' => 'ɛ', 'Ƒ' => 'ƒ', 'Ɣ' => 'ɣ', 'Ɩ' => 'ɩ', 'Ɨ' => 'ɨ', 'Ƙ' => 'ƙ', 'Ɯ' => 'ɯ', 'Ɲ' => 'ɲ', 'Ɵ' => 'ɵ', 'Ơ' => 'ơ', 'Ƣ' => 'ƣ', 'Ƥ' => 'ƥ', 'Ʀ' => 'ʀ', 'Ƨ' => 'ƨ', 'Ʃ' => 'ʃ', 'Ƭ' => 'ƭ', 'Ʈ' => 'ʈ', 'Ư' => 'ư', 'Ʊ' => 'ʊ', 'Ʋ' => 'ʋ', 'Ƴ' => 'ƴ', 'Ƶ' => 'ƶ', 'Ʒ' => 'ʒ', 'Ƹ' => 'ƹ', 'Ƽ' => 'ƽ', 'Dž' => 'dž', 'Lj' => 'lj', 'Nj' => 'nj', 'Ǎ' => 'ǎ', 'Ǐ' => 'ǐ', 'Ǒ' => 'ǒ', 'Ǔ' => 'ǔ', 'Ǖ' => 'ǖ', 'Ǘ' => 'ǘ', 'Ǚ' => 'ǚ', 'Ǜ' => 'ǜ', 'Ǟ' => 'ǟ', 'Ǡ' => 'ǡ', 'Ǣ' => 'ǣ', 'Ǥ' => 'ǥ', 'Ǧ' => 'ǧ', 'Ǩ' => 'ǩ', 'Ǫ' => 'ǫ', 'Ǭ' => 'ǭ', 'Ǯ' => 'ǯ', 'Dz' => 'dz', 'Ǵ' => 'ǵ', 'Ƕ' => 'ƕ', 'Ƿ' => 'ƿ', 'Ǹ' => 'ǹ', 'Ǻ' => 'ǻ', 'Ǽ' => 'ǽ', 'Ǿ' => 'ǿ', 'Ȁ' => 'ȁ', 'Ȃ' => 'ȃ', 'Ȅ' => 'ȅ', 'Ȇ' => 'ȇ', 'Ȉ' => 'ȉ', 'Ȋ' => 'ȋ', 'Ȍ' => 'ȍ', 'Ȏ' => 'ȏ', 'Ȑ' => 'ȑ', 'Ȓ' => 'ȓ', 'Ȕ' => 'ȕ', 'Ȗ' => 'ȗ', 'Ș' => 'ș', 'Ț' => 'ț', 'Ȝ' => 'ȝ', 'Ȟ' => 'ȟ', 'Ƞ' => 'ƞ', 'Ȣ' => 'ȣ', 'Ȥ' => 'ȥ', 'Ȧ' => 'ȧ', 'Ȩ' => 'ȩ', 'Ȫ' => 'ȫ', 'Ȭ' => 'ȭ', 'Ȯ' => 'ȯ', 'Ȱ' => 'ȱ', 'Ȳ' => 'ȳ', 'Ά' => 'ά', 'Έ' => 'έ', 'Ή' => 'ή', 'Ί' => 'ί', 'Ό' => 'ό', 'Ύ' => 'ύ', 'Ώ' => 'ώ', 'Α' => 'α', 'Β' => 'β', 'Γ' => 'γ', 'Δ' => 'δ', 'Ε' => 'ε', 'Ζ' => 'ζ', 'Η' => 'η', 'Θ' => 'θ', 'Ι' => 'ι', 'Κ' => 'κ', 'Λ' => 'λ', 'Μ' => 'μ', 'Ν' => 'ν', 'Ξ' => 'ξ', 'Ο' => 'ο', 'Π' => 'π', 'Ρ' => 'ρ', 'Σ' => 'σ', 'Τ' => 'τ', 'Υ' => 'υ', 'Φ' => 'φ', 'Χ' => 'χ', 'Ψ' => 'ψ', 'Ω' => 'ω', 'Ϊ' => 'ϊ', 'Ϋ' => 'ϋ', 'Ϙ' => 'ϙ', 'Ϛ' => 'ϛ', 'Ϝ' => 'ϝ', 'Ϟ' => 'ϟ', 'Ϡ' => 'ϡ', 'Ϣ' => 'ϣ', 'Ϥ' => 'ϥ', 'Ϧ' => 'ϧ', 'Ϩ' => 'ϩ', 'Ϫ' => 'ϫ', 'Ϭ' => 'ϭ', 'Ϯ' => 'ϯ', 'Ѐ' => 'ѐ', 'Ё' => 'ё', 'Ђ' => 'ђ', 'Ѓ' => 'ѓ', 'Є' => 'є', 'Ѕ' => 'ѕ', 'І' => 'і', 'Ї' => 'ї', 'Ј' => 'ј', 'Љ' => 'љ', 'Њ' => 'њ', 'Ћ' => 'ћ', 'Ќ' => 'ќ', 'Ѝ' => 'ѝ', 'Ў' => 'ў', 'Џ' => 'џ', 'А' => 'а', 'Б' => 'б', 'В' => 'в', 'Г' => 'г', 'Д' => 'д', 'Е' => 'е', 'Ж' => 'ж', 'З' => 'з', 'И' => 'и', 'Й' => 'й', 'К' => 'к', 'Л' => 'л', 'М' => 'м', 'Н' => 'н', 'О' => 'о', 'П' => 'п', 'Р' => 'р', 'С' => 'с', 'Т' => 'т', 'У' => 'у', 'Ф' => 'ф', 'Х' => 'х', 'Ц' => 'ц', 'Ч' => 'ч', 'Ш' => 'ш', 'Щ' => 'щ', 'Ъ' => 'ъ', 'Ы' => 'ы', 'Ь' => 'ь', 'Э' => 'э', 'Ю' => 'ю', 'Я' => 'я', 'Ѡ' => 'ѡ', 'Ѣ' => 'ѣ', 'Ѥ' => 'ѥ', 'Ѧ' => 'ѧ', 'Ѩ' => 'ѩ', 'Ѫ' => 'ѫ', 'Ѭ' => 'ѭ', 'Ѯ' => 'ѯ', 'Ѱ' => 'ѱ', 'Ѳ' => 'ѳ', 'Ѵ' => 'ѵ', 'Ѷ' => 'ѷ', 'Ѹ' => 'ѹ', 'Ѻ' => 'ѻ', 'Ѽ' => 'ѽ', 'Ѿ' => 'ѿ', 'Ҁ' => 'ҁ', 'Ҋ' => 'ҋ', 'Ҍ' => 'ҍ', 'Ҏ' => 'ҏ', 'Ґ' => 'ґ', 'Ғ' => 'ғ', 'Ҕ' => 'ҕ', 'Җ' => 'җ', 'Ҙ' => 'ҙ', 'Қ' => 'қ', 'Ҝ' => 'ҝ', 'Ҟ' => 'ҟ', 'Ҡ' => 'ҡ', 'Ң' => 'ң', 'Ҥ' => 'ҥ', 'Ҧ' => 'ҧ', 'Ҩ' => 'ҩ', 'Ҫ' => 'ҫ', 'Ҭ' => 'ҭ', 'Ү' => 'ү', 'Ұ' => 'ұ', 'Ҳ' => 'ҳ', 'Ҵ' => 'ҵ', 'Ҷ' => 'ҷ', 'Ҹ' => 'ҹ', 'Һ' => 'һ', 'Ҽ' => 'ҽ', 'Ҿ' => 'ҿ', 'Ӂ' => 'ӂ', 'Ӄ' => 'ӄ', 'Ӆ' => 'ӆ', 'Ӈ' => 'ӈ', 'Ӊ' => 'ӊ', 'Ӌ' => 'ӌ', 'Ӎ' => 'ӎ', 'Ӑ' => 'ӑ', 'Ӓ' => 'ӓ', 'Ӕ' => 'ӕ', 'Ӗ' => 'ӗ', 'Ә' => 'ә', 'Ӛ' => 'ӛ', 'Ӝ' => 'ӝ', 'Ӟ' => 'ӟ', 'Ӡ' => 'ӡ', 'Ӣ' => 'ӣ', 'Ӥ' => 'ӥ', 'Ӧ' => 'ӧ', 'Ө' => 'ө', 'Ӫ' => 'ӫ', 'Ӭ' => 'ӭ', 'Ӯ' => 'ӯ', 'Ӱ' => 'ӱ', 'Ӳ' => 'ӳ', 'Ӵ' => 'ӵ', 'Ӹ' => 'ӹ', 'Ԁ' => 'ԁ', 'Ԃ' => 'ԃ', 'Ԅ' => 'ԅ', 'Ԇ' => 'ԇ', 'Ԉ' => 'ԉ', 'Ԋ' => 'ԋ', 'Ԍ' => 'ԍ', 'Ԏ' => 'ԏ', 'Ա' => 'ա', 'Բ' => 'բ', 'Գ' => 'գ', 'Դ' => 'դ', 'Ե' => 'ե', 'Զ' => 'զ', 'Է' => 'է', 'Ը' => 'ը', 'Թ' => 'թ', 'Ժ' => 'ժ', 'Ի' => 'ի', 'Լ' => 'լ', 'Խ' => 'խ', 'Ծ' => 'ծ', 'Կ' => 'կ', 'Հ' => 'հ', 'Ձ' => 'ձ', 'Ղ' => 'ղ', 'Ճ' => 'ճ', 'Մ' => 'մ', 'Յ' => 'յ', 'Ն' => 'ն', 'Շ' => 'շ', 'Ո' => 'ո', 'Չ' => 'չ', 'Պ' => 'պ', 'Ջ' => 'ջ', 'Ռ' => 'ռ', 'Ս' => 'ս', 'Վ' => 'վ', 'Տ' => 'տ', 'Ր' => 'ր', 'Ց' => 'ց', 'Ւ' => 'ւ', 'Փ' => 'փ', 'Ք' => 'ք', 'Օ' => 'օ', 'Ֆ' => 'ֆ', 'Ḁ' => 'ḁ', 'Ḃ' => 'ḃ', 'Ḅ' => 'ḅ', 'Ḇ' => 'ḇ', 'Ḉ' => 'ḉ', 'Ḋ' => 'ḋ', 'Ḍ' => 'ḍ', 'Ḏ' => 'ḏ', 'Ḑ' => 'ḑ', 'Ḓ' => 'ḓ', 'Ḕ' => 'ḕ', 'Ḗ' => 'ḗ', 'Ḙ' => 'ḙ', 'Ḛ' => 'ḛ', 'Ḝ' => 'ḝ', 'Ḟ' => 'ḟ', 'Ḡ' => 'ḡ', 'Ḣ' => 'ḣ', 'Ḥ' => 'ḥ', 'Ḧ' => 'ḧ', 'Ḩ' => 'ḩ', 'Ḫ' => 'ḫ', 'Ḭ' => 'ḭ', 'Ḯ' => 'ḯ', 'Ḱ' => 'ḱ', 'Ḳ' => 'ḳ', 'Ḵ' => 'ḵ', 'Ḷ' => 'ḷ', 'Ḹ' => 'ḹ', 'Ḻ' => 'ḻ', 'Ḽ' => 'ḽ', 'Ḿ' => 'ḿ', 'Ṁ' => 'ṁ', 'Ṃ' => 'ṃ', 'Ṅ' => 'ṅ', 'Ṇ' => 'ṇ', 'Ṉ' => 'ṉ', 'Ṋ' => 'ṋ', 'Ṍ' => 'ṍ', 'Ṏ' => 'ṏ', 'Ṑ' => 'ṑ', 'Ṓ' => 'ṓ', 'Ṕ' => 'ṕ', 'Ṗ' => 'ṗ', 'Ṙ' => 'ṙ', 'Ṛ' => 'ṛ', 'Ṝ' => 'ṝ', 'Ṟ' => 'ṟ', 'Ṡ' => 'ṡ', 'Ṣ' => 'ṣ', 'Ṥ' => 'ṥ', 'Ṧ' => 'ṧ', 'Ṩ' => 'ṩ', 'Ṫ' => 'ṫ', 'Ṭ' => 'ṭ', 'Ṯ' => 'ṯ', 'Ṱ' => 'ṱ', 'Ṳ' => 'ṳ', 'Ṵ' => 'ṵ', 'Ṷ' => 'ṷ', 'Ṹ' => 'ṹ', 'Ṻ' => 'ṻ', 'Ṽ' => 'ṽ', 'Ṿ' => 'ṿ', 'Ẁ' => 'ẁ', 'Ẃ' => 'ẃ', 'Ẅ' => 'ẅ', 'Ẇ' => 'ẇ', 'Ẉ' => 'ẉ', 'Ẋ' => 'ẋ', 'Ẍ' => 'ẍ', 'Ẏ' => 'ẏ', 'Ẑ' => 'ẑ', 'Ẓ' => 'ẓ', 'Ẕ' => 'ẕ', 'Ạ' => 'ạ', 'Ả' => 'ả', 'Ấ' => 'ấ', 'Ầ' => 'ầ', 'Ẩ' => 'ẩ', 'Ẫ' => 'ẫ', 'Ậ' => 'ậ', 'Ắ' => 'ắ', 'Ằ' => 'ằ', 'Ẳ' => 'ẳ', 'Ẵ' => 'ẵ', 'Ặ' => 'ặ', 'Ẹ' => 'ẹ', 'Ẻ' => 'ẻ', 'Ẽ' => 'ẽ', 'Ế' => 'ế', 'Ề' => 'ề', 'Ể' => 'ể', 'Ễ' => 'ễ', 'Ệ' => 'ệ', 'Ỉ' => 'ỉ', 'Ị' => 'ị', 'Ọ' => 'ọ', 'Ỏ' => 'ỏ', 'Ố' => 'ố', 'Ồ' => 'ồ', 'Ổ' => 'ổ', 'Ỗ' => 'ỗ', 'Ộ' => 'ộ', 'Ớ' => 'ớ', 'Ờ' => 'ờ', 'Ở' => 'ở', 'Ỡ' => 'ỡ', 'Ợ' => 'ợ', 'Ụ' => 'ụ', 'Ủ' => 'ủ', 'Ứ' => 'ứ', 'Ừ' => 'ừ', 'Ử' => 'ử', 'Ữ' => 'ữ', 'Ự' => 'ự', 'Ỳ' => 'ỳ', 'Ỵ' => 'ỵ', 'Ỷ' => 'ỷ', 'Ỹ' => 'ỹ', 'Ἀ' => 'ἀ', 'Ἁ' => 'ἁ', 'Ἂ' => 'ἂ', 'Ἃ' => 'ἃ', 'Ἄ' => 'ἄ', 'Ἅ' => 'ἅ', 'Ἆ' => 'ἆ', 'Ἇ' => 'ἇ', 'Ἐ' => 'ἐ', 'Ἑ' => 'ἑ', 'Ἒ' => 'ἒ', 'Ἓ' => 'ἓ', 'Ἔ' => 'ἔ', 'Ἕ' => 'ἕ', 'Ἡ' => 'ἡ', 'Ἢ' => 'ἢ', 'Ἣ' => 'ἣ', 'Ἤ' => 'ἤ', 'Ἥ' => 'ἥ', 'Ἦ' => 'ἦ', 'Ἧ' => 'ἧ', 'Ἰ' => 'ἰ', 'Ἱ' => 'ἱ', 'Ἲ' => 'ἲ', 'Ἳ' => 'ἳ', 'Ἴ' => 'ἴ', 'Ἵ' => 'ἵ', 'Ἶ' => 'ἶ', 'Ἷ' => 'ἷ', 'Ὀ' => 'ὀ', 'Ὁ' => 'ὁ', 'Ὂ' => 'ὂ', 'Ὃ' => 'ὃ', 'Ὄ' => 'ὄ', 'Ὅ' => 'ὅ', 'Ὑ' => 'ὑ', 'Ὓ' => 'ὓ', 'Ὕ' => 'ὕ', 'Ὗ' => 'ὗ', 'Ὡ' => 'ὡ', 'Ὢ' => 'ὢ', 'Ὣ' => 'ὣ', 'Ὤ' => 'ὤ', 'Ὥ' => 'ὥ', 'Ὦ' => 'ὦ', 'Ὧ' => 'ὧ', 'ᾈ' => 'ᾀ', 'ᾉ' => 'ᾁ', 'ᾊ' => 'ᾂ', 'ᾋ' => 'ᾃ', 'ᾌ' => 'ᾄ', 'ᾍ' => 'ᾅ', 'ᾎ' => 'ᾆ', 'ᾏ' => 'ᾇ', 'ᾘ' => 'ᾐ', 'ᾙ' => 'ᾑ', 'ᾚ' => 'ᾒ', 'ᾛ' => 'ᾓ', 'ᾜ' => 'ᾔ', 'ᾝ' => 'ᾕ', 'ᾞ' => 'ᾖ', 'ᾟ' => 'ᾗ', 'ᾩ' => 'ᾡ', 'ᾪ' => 'ᾢ', 'ᾫ' => 'ᾣ', 'ᾬ' => 'ᾤ', 'ᾭ' => 'ᾥ', 'ᾮ' => 'ᾦ', 'ᾯ' => 'ᾧ', 'Ᾰ' => 'ᾰ', 'Ᾱ' => 'ᾱ', 'Ὰ' => 'ὰ', 'ᾼ' => 'ᾳ', 'Ὲ' => 'ὲ', 'Ὴ' => 'ὴ', 'ῌ' => 'ῃ', 'Ῐ' => 'ῐ', 'Ῑ' => 'ῑ', 'Ὶ' => 'ὶ', 'Ῡ' => 'ῡ', 'Ὺ' => 'ὺ', 'Ῥ' => 'ῥ', 'Ὸ' => 'ὸ', 'Ὼ' => 'ὼ', 'ῼ' => 'ῳ', 'A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e', 'F' => 'f', 'G' => 'g', 'H' => 'h', 'I' => 'i', 'J' => 'j', 'K' => 'k', 'L' => 'l', 'M' => 'm', 'N' => 'n', 'O' => 'o', 'P' => 'p', 'Q' => 'q', 'R' => 'r', 'S' => 's', 'T' => 't', 'U' => 'u', 'V' => 'v', 'W' => 'w', 'X' => 'x', 'Y' => 'y', 'Z' => 'z', ]; Utf8/tables/loweraccents.php 0000644 00000004050 15233462216 0012051 0 ustar 00 <?php /** * UTF-8 lookup table for lower case accented letters * * This lookuptable defines replacements for accented characters from the ASCII-7 * range. This are lower case letters only. * * @author Andreas Gohr <andi@splitbrain.org> * @see \dokuwiki\Utf8\Clean::deaccent() */ return [ 'á' => 'a', 'à' => 'a', 'ă' => 'a', 'â' => 'a', 'å' => 'a', 'ä' => 'ae', 'ã' => 'a', 'ą' => 'a', 'ā' => 'a', 'æ' => 'ae', 'ḃ' => 'b', 'ć' => 'c', 'ĉ' => 'c', 'č' => 'c', 'ċ' => 'c', 'ç' => 'c', 'ď' => 'd', 'ḋ' => 'd', 'đ' => 'd', 'ð' => 'dh', 'é' => 'e', 'è' => 'e', 'ĕ' => 'e', 'ê' => 'e', 'ě' => 'e', 'ë' => 'e', 'ė' => 'e', 'ę' => 'e', 'ē' => 'e', 'ḟ' => 'f', 'ƒ' => 'f', 'ğ' => 'g', 'ĝ' => 'g', 'ġ' => 'g', 'ģ' => 'g', 'ĥ' => 'h', 'ħ' => 'h', 'í' => 'i', 'ì' => 'i', 'î' => 'i', 'ï' => 'i', 'ĩ' => 'i', 'į' => 'i', 'ī' => 'i', 'ĵ' => 'j', 'ķ' => 'k', 'ĺ' => 'l', 'ľ' => 'l', 'ļ' => 'l', 'ł' => 'l', 'ṁ' => 'm', 'ń' => 'n', 'ň' => 'n', 'ñ' => 'n', 'ņ' => 'n', 'ó' => 'o', 'ò' => 'o', 'ô' => 'o', 'ö' => 'oe', 'ő' => 'o', 'õ' => 'o', 'ø' => 'o', 'ō' => 'o', 'ơ' => 'o', 'ṗ' => 'p', 'ŕ' => 'r', 'ř' => 'r', 'ŗ' => 'r', 'ś' => 's', 'ŝ' => 's', 'š' => 's', 'ṡ' => 's', 'ş' => 's', 'ș' => 's', 'ß' => 'ss', 'ť' => 't', 'ṫ' => 't', 'ţ' => 't', 'ț' => 't', 'ŧ' => 't', 'ú' => 'u', 'ù' => 'u', 'ŭ' => 'u', 'û' => 'u', 'ů' => 'u', 'ü' => 'ue', 'ű' => 'u', 'ũ' => 'u', 'ų' => 'u', 'ū' => 'u', 'ư' => 'u', 'ẃ' => 'w', 'ẁ' => 'w', 'ŵ' => 'w', 'ẅ' => 'w', 'ý' => 'y', 'ỳ' => 'y', 'ŷ' => 'y', 'ÿ' => 'y', 'ź' => 'z', 'ž' => 'z', 'ż' => 'z', 'þ' => 'th', 'µ' => 'u', ]; Utf8/tables/specials.php 0000644 00000026501 15233462216 0011170 0 ustar 00 <?php /** * UTF-8 array of common special characters * * This array should contain all special characters (not a letter or digit) * defined in the various local charsets - it's not a complete list of non-alphanum * characters in UTF-8. It's not perfect but should match most cases of special * chars. * * The controlchars 0x00 to 0x19 are _not_ included in this array. The space 0x20 is! * These chars are _not_ in the array either: _ (0x5f), : 0x3a, . 0x2e, - 0x2d, * 0x2a * * @author Andreas Gohr <andi@splitbrain.org> * @see \dokuwiki\Utf8\Clean::stripspecials() */ return [ 0x1a, // 0x1b, // 0x1c, // 0x1d, // 0x1e, // 0x1f, // 0x20, // <space> 0x21, // ! 0x22, // " 0x23, // # 0x24, // $ 0x25, // % 0x26, // & 0x27, // ' 0x28, // ( 0x29, // ) 0x2b, // + 0x2c, // , 0x2f, // / 0x3b, // ; 0x3c, // < 0x3d, // = 0x3e, // > 0x3f, // ? 0x40, // @ 0x5b, // [ 0x5c, // \ 0x5d, // ] 0x5e, // ^ 0x60, // ` 0x7b, // { 0x7c, // | 0x7d, // } 0x7e, // ~ 0x7f, // 0x80, // 0x81, // 0x82, // 0x83, // 0x84, // 0x85, // 0x86, // 0x87, // 0x88, // 0x89, // 0x8a, // 0x8b, // 0x8c, // 0x8d, // 0x8e, // 0x8f, // 0x90, // 0x91, // 0x92, // 0x93, // 0x94, // 0x95, // 0x96, // 0x97, // 0x98, // 0x99, // 0x9a, // 0x9b, // 0x9c, // 0x9d, // 0x9e, // 0x9f, // 0xa0, // 0xa1, // ¡ 0xa2, // ¢ 0xa3, // £ 0xa4, // ¤ 0xa5, // ¥ 0xa6, // ¦ 0xa7, // § 0xa8, // ¨ 0xa9, // © 0xaa, // ª 0xab, // « 0xac, // ¬ 0xad, // 0xae, // ® 0xaf, // ¯ 0xb0, // ° 0xb1, // ± 0xb2, // ² 0xb3, // ³ 0xb4, // ´ 0xb5, // µ 0xb6, // ¶ 0xb7, // · 0xb8, // ¸ 0xb9, // ¹ 0xba, // º 0xbb, // » 0xbc, // ¼ 0xbd, // ½ 0xbe, // ¾ 0xbf, // ¿ 0xd7, // × 0xf7, // ÷ 0x2c7, // ˇ 0x2d8, // ˘ 0x2d9, // ˙ 0x2da, // ˚ 0x2db, // ˛ 0x2dc, // ˜ 0x2dd, // ˝ 0x300, // ̀ 0x301, // ́ 0x303, // ̃ 0x309, // ̉ 0x323, // ̣ 0x384, // ΄ 0x385, // ΅ 0x387, // · 0x5b0, // ְ 0x5b1, // ֱ 0x5b2, // ֲ 0x5b3, // ֳ 0x5b4, // ִ 0x5b5, // ֵ 0x5b6, // ֶ 0x5b7, // ַ 0x5b8, // ָ 0x5b9, // ֹ 0x5bb, // ֻ 0x5bc, // ּ 0x5bd, // ֽ 0x5be, // ־ 0x5bf, // ֿ 0x5c0, // ׀ 0x5c1, // ׁ 0x5c2, // ׂ 0x5c3, // ׃ 0x5f3, // ׳ 0x5f4, // ״ 0x60c, // ، 0x61b, // ؛ 0x61f, // ؟ 0x640, // ـ 0x64b, // ً 0x64c, // ٌ 0x64d, // ٍ 0x64e, // َ 0x64f, // ُ 0x650, // ِ 0x651, // ّ 0x652, // ْ 0x66a, // ٪ 0xe3f, // ฿ 0x200c, // 0x200d, // 0x200e, // 0x200f, // 0x2013, // – 0x2014, // — 0x2015, // ― 0x2017, // ‗ 0x2018, // ‘ 0x2019, // ’ 0x201a, // ‚ 0x201c, // “ 0x201d, // ” 0x201e, // „ 0x2020, // † 0x2021, // ‡ 0x2022, // • 0x2026, // … 0x2030, // ‰ 0x2032, // ′ 0x2033, // ″ 0x2039, // ‹ 0x203a, // › 0x2044, // ⁄ 0x20a7, // ₧ 0x20aa, // ₪ 0x20ab, // ₫ 0x20ac, // € 0x2116, // № 0x2118, // ℘ 0x2122, // ™ 0x2126, // Ω 0x2135, // ℵ 0x2190, // ← 0x2191, // ↑ 0x2192, // → 0x2193, // ↓ 0x2194, // ↔ 0x2195, // ↕ 0x21b5, // ↵ 0x21d0, // ⇐ 0x21d1, // ⇑ 0x21d2, // ⇒ 0x21d3, // ⇓ 0x21d4, // ⇔ 0x2200, // ∀ 0x2202, // ∂ 0x2203, // ∃ 0x2205, // ∅ 0x2206, // ∆ 0x2207, // ∇ 0x2208, // ∈ 0x2209, // ∉ 0x220b, // ∋ 0x220f, // ∏ 0x2211, // ∑ 0x2212, // − 0x2215, // ∕ 0x2217, // ∗ 0x2219, // ∙ 0x221a, // √ 0x221d, // ∝ 0x221e, // ∞ 0x2220, // ∠ 0x2227, // ∧ 0x2228, // ∨ 0x2229, // ∩ 0x222a, // ∪ 0x222b, // ∫ 0x2234, // ∴ 0x223c, // ∼ 0x2245, // ≅ 0x2248, // ≈ 0x2260, // ≠ 0x2261, // ≡ 0x2264, // ≤ 0x2265, // ≥ 0x2282, // ⊂ 0x2283, // ⊃ 0x2284, // ⊄ 0x2286, // ⊆ 0x2287, // ⊇ 0x2295, // ⊕ 0x2297, // ⊗ 0x22a5, // ⊥ 0x22c5, // ⋅ 0x2310, // ⌐ 0x2320, // ⌠ 0x2321, // ⌡ 0x2329, // 〈 0x232a, // 〉 0x2469, // ⑩ 0x2500, // ─ 0x2502, // │ 0x250c, // ┌ 0x2510, // ┐ 0x2514, // └ 0x2518, // ┘ 0x251c, // ├ 0x2524, // ┤ 0x252c, // ┬ 0x2534, // ┴ 0x253c, // ┼ 0x2550, // ═ 0x2551, // ║ 0x2552, // ╒ 0x2553, // ╓ 0x2554, // ╔ 0x2555, // ╕ 0x2556, // ╖ 0x2557, // ╗ 0x2558, // ╘ 0x2559, // ╙ 0x255a, // ╚ 0x255b, // ╛ 0x255c, // ╜ 0x255d, // ╝ 0x255e, // ╞ 0x255f, // ╟ 0x2560, // ╠ 0x2561, // ╡ 0x2562, // ╢ 0x2563, // ╣ 0x2564, // ╤ 0x2565, // ╥ 0x2566, // ╦ 0x2567, // ╧ 0x2568, // ╨ 0x2569, // ╩ 0x256a, // ╪ 0x256b, // ╫ 0x256c, // ╬ 0x2580, // ▀ 0x2584, // ▄ 0x2588, // █ 0x258c, // ▌ 0x2590, // ▐ 0x2591, // ░ 0x2592, // ▒ 0x2593, // ▓ 0x25a0, // ■ 0x25b2, // ▲ 0x25bc, // ▼ 0x25c6, // ◆ 0x25ca, // ◊ 0x25cf, // ● 0x25d7, // ◗ 0x2605, // ★ 0x260e, // ☎ 0x261b, // ☛ 0x261e, // ☞ 0x2660, // ♠ 0x2663, // ♣ 0x2665, // ♥ 0x2666, // ♦ 0x2701, // ✁ 0x2702, // ✂ 0x2703, // ✃ 0x2704, // ✄ 0x2706, // ✆ 0x2707, // ✇ 0x2708, // ✈ 0x2709, // ✉ 0x270c, // ✌ 0x270d, // ✍ 0x270e, // ✎ 0x270f, // ✏ 0x2710, // ✐ 0x2711, // ✑ 0x2712, // ✒ 0x2713, // ✓ 0x2714, // ✔ 0x2715, // ✕ 0x2716, // ✖ 0x2717, // ✗ 0x2718, // ✘ 0x2719, // ✙ 0x271a, // ✚ 0x271b, // ✛ 0x271c, // ✜ 0x271d, // ✝ 0x271e, // ✞ 0x271f, // ✟ 0x2720, // ✠ 0x2721, // ✡ 0x2722, // ✢ 0x2723, // ✣ 0x2724, // ✤ 0x2725, // ✥ 0x2726, // ✦ 0x2727, // ✧ 0x2729, // ✩ 0x272a, // ✪ 0x272b, // ✫ 0x272c, // ✬ 0x272d, // ✭ 0x272e, // ✮ 0x272f, // ✯ 0x2730, // ✰ 0x2731, // ✱ 0x2732, // ✲ 0x2733, // ✳ 0x2734, // ✴ 0x2735, // ✵ 0x2736, // ✶ 0x2737, // ✷ 0x2738, // ✸ 0x2739, // ✹ 0x273a, // ✺ 0x273b, // ✻ 0x273c, // ✼ 0x273d, // ✽ 0x273e, // ✾ 0x273f, // ✿ 0x2740, // ❀ 0x2741, // ❁ 0x2742, // ❂ 0x2743, // ❃ 0x2744, // ❄ 0x2745, // ❅ 0x2746, // ❆ 0x2747, // ❇ 0x2748, // ❈ 0x2749, // ❉ 0x274a, // ❊ 0x274b, // ❋ 0x274d, // ❍ 0x274f, // ❏ 0x2750, // ❐ 0x2751, // ❑ 0x2752, // ❒ 0x2756, // ❖ 0x2758, // ❘ 0x2759, // ❙ 0x275a, // ❚ 0x275b, // ❛ 0x275c, // ❜ 0x275d, // ❝ 0x275e, // ❞ 0x2761, // ❡ 0x2762, // ❢ 0x2763, // ❣ 0x2764, // ❤ 0x2765, // ❥ 0x2766, // ❦ 0x2767, // ❧ 0x277f, // ❿ 0x2789, // ➉ 0x2793, // ➓ 0x2794, // ➔ 0x2798, // ➘ 0x2799, // ➙ 0x279a, // ➚ 0x279b, // ➛ 0x279c, // ➜ 0x279d, // ➝ 0x279e, // ➞ 0x279f, // ➟ 0x27a0, // ➠ 0x27a1, // ➡ 0x27a2, // ➢ 0x27a3, // ➣ 0x27a4, // ➤ 0x27a5, // ➥ 0x27a6, // ➦ 0x27a7, // ➧ 0x27a8, // ➨ 0x27a9, // ➩ 0x27aa, // ➪ 0x27ab, // ➫ 0x27ac, // ➬ 0x27ad, // ➭ 0x27ae, // ➮ 0x27af, // ➯ 0x27b1, // ➱ 0x27b2, // ➲ 0x27b3, // ➳ 0x27b4, // ➴ 0x27b5, // ➵ 0x27b6, // ➶ 0x27b7, // ➷ 0x27b8, // ➸ 0x27b9, // ➹ 0x27ba, // ➺ 0x27bb, // ➻ 0x27bc, // ➼ 0x27bd, // ➽ 0x27be, // ➾ 0x3000, // 0x3001, // 、 0x3002, // 。 0x3003, // 〃 0x3008, // 〈 0x3009, // 〉 0x300a, // 《 0x300b, // 》 0x300c, // 「 0x300d, // 」 0x300e, // 『 0x300f, // 』 0x3010, // 【 0x3011, // 】 0x3012, // 〒 0x3014, // 〔 0x3015, // 〕 0x3016, // 〖 0x3017, // 〗 0x3018, // 〘 0x3019, // 〙 0x301a, // 〚 0x301b, // 〛 0x3036, // 〶 0xf6d9, // 0xf6da, // 0xf6db, // 0xf8d7, // 0xf8d8, // 0xf8d9, // 0xf8da, // 0xf8db, // 0xf8dc, // 0xf8dd, // 0xf8de, // 0xf8df, // 0xf8e0, // 0xf8e1, // 0xf8e2, // 0xf8e3, // 0xf8e4, // 0xf8e5, // 0xf8e6, // 0xf8e7, // 0xf8e8, // 0xf8e9, // 0xf8ea, // 0xf8eb, // 0xf8ec, // 0xf8ed, // 0xf8ee, // 0xf8ef, // 0xf8f0, // 0xf8f1, // 0xf8f2, // 0xf8f3, // 0xf8f4, // 0xf8f5, // 0xf8f6, // 0xf8f7, // 0xf8f8, // 0xf8f9, // 0xf8fa, // 0xf8fb, // 0xf8fc, // 0xf8fd, // 0xf8fe, // 0xfe7c, // ﹼ 0xfe7d, // ﹽ 0xff01, // ! 0xff02, // " 0xff03, // # 0xff04, // $ 0xff05, // % 0xff06, // & 0xff07, // ' 0xff08, // ( 0xff09, // ) 0xff09, // ) 0xff0a, // * 0xff0b, // + 0xff0c, // , 0xff0d, // - 0xff0e, // . 0xff0f, // / 0xff1a, // : 0xff1b, // ; 0xff1c, // < 0xff1d, // = 0xff1e, // > 0xff1f, // ? 0xff20, // @ 0xff3b, // [ 0xff3c, // \ 0xff3d, // ] 0xff3e, // ^ 0xff40, // ` 0xff5b, // { 0xff5c, // | 0xff5d, // } 0xff5e, // ~ 0xff5f, // ⦅ 0xff60, // ⦆ 0xff61, // 。 0xff62, // 「 0xff63, // 」 0xff64, // 、 0xff65, // ・ 0xffe0, // ¢ 0xffe1, // £ 0xffe2, // ¬ 0xffe3, //  ̄ 0xffe4, // ¦ 0xffe5, // ¥ 0xffe6, // ₩ 0xffe8, // │ 0xffe9, // ← 0xffea, // ↑ 0xffeb, // → 0xffec, // ↓ 0xffed, // ■ 0xffee, // ○ 0x1d6fc, // 𝛼 0x1d6fd, // 𝛽 0x1d6fe, // 𝛾 0x1d6ff, // 𝛿 0x1d700, // 𝜀 0x1d701, // 𝜁 0x1d702, // 𝜂 0x1d703, // 𝜃 0x1d704, // 𝜄 0x1d705, // 𝜅 0x1d706, // 𝜆 0x1d707, // 𝜇 0x1d708, // 𝜈 0x1d709, // 𝜉 0x1d70a, // 𝜊 0x1d70b, // 𝜋 0x1d70c, // 𝜌 0x1d70d, // 𝜍 0x1d70e, // 𝜎 0x1d70f, // 𝜏 0x1d710, // 𝜐 0x1d711, // 𝜑 0x1d712, // 𝜒 0x1d713, // 𝜓 0x1d714, // 𝜔 0x1d715, // 𝜕 0x1d716, // 𝜖 0x1d717, // 𝜗 0x1d718, // 𝜘 0x1d719, // 𝜙 0x1d71a, // 𝜚 0x1d71b, // 𝜛 0xc2a0, // 슠 0xe28087, // 0xe280af, // 0xe281a0, // 0xefbbbf, // ]; Utf8/tables/upperaccents.php 0000644 00000004005 15233462216 0012054 0 ustar 00 <?php /** * UTF-8 lookup table for upper case accented letters * * This lookuptable defines replacements for accented characters from the ASCII-7 * range. This are upper case letters only. * * @author Andreas Gohr <andi@splitbrain.org> * @see \dokuwiki\Utf8\Clean::deaccent() */ return [ 'Á' => 'A', 'À' => 'A', 'Ă' => 'A', 'Â' => 'A', 'Å' => 'A', 'Ä' => 'Ae', 'Ã' => 'A', 'Ą' => 'A', 'Ā' => 'A', 'Æ' => 'Ae', 'Ḃ' => 'B', 'Ć' => 'C', 'Ĉ' => 'C', 'Č' => 'C', 'Ċ' => 'C', 'Ç' => 'C', 'Ď' => 'D', 'Ḋ' => 'D', 'Đ' => 'D', 'Ð' => 'Dh', 'É' => 'E', 'È' => 'E', 'Ĕ' => 'E', 'Ê' => 'E', 'Ě' => 'E', 'Ë' => 'E', 'Ė' => 'E', 'Ę' => 'E', 'Ē' => 'E', 'Ḟ' => 'F', 'Ƒ' => 'F', 'Ğ' => 'G', 'Ĝ' => 'G', 'Ġ' => 'G', 'Ģ' => 'G', 'Ĥ' => 'H', 'Ħ' => 'H', 'Í' => 'I', 'Ì' => 'I', 'Î' => 'I', 'Ï' => 'I', 'Ĩ' => 'I', 'Į' => 'I', 'Ī' => 'I', 'Ĵ' => 'J', 'Ķ' => 'K', 'Ĺ' => 'L', 'Ľ' => 'L', 'Ļ' => 'L', 'Ł' => 'L', 'Ṁ' => 'M', 'Ń' => 'N', 'Ň' => 'N', 'Ñ' => 'N', 'Ņ' => 'N', 'Ó' => 'O', 'Ò' => 'O', 'Ô' => 'O', 'Ö' => 'Oe', 'Ő' => 'O', 'Õ' => 'O', 'Ø' => 'O', 'Ō' => 'O', 'Ơ' => 'O', 'Ṗ' => 'P', 'Ŕ' => 'R', 'Ř' => 'R', 'Ŗ' => 'R', 'Ś' => 'S', 'Ŝ' => 'S', 'Š' => 'S', 'Ṡ' => 'S', 'Ş' => 'S', 'Ș' => 'S', 'Ť' => 'T', 'Ṫ' => 'T', 'Ţ' => 'T', 'Ț' => 'T', 'Ŧ' => 'T', 'Ú' => 'U', 'Ù' => 'U', 'Ŭ' => 'U', 'Û' => 'U', 'Ů' => 'U', 'Ü' => 'Ue', 'Ű' => 'U', 'Ũ' => 'U', 'Ų' => 'U', 'Ū' => 'U', 'Ư' => 'U', 'Ẃ' => 'W', 'Ẁ' => 'W', 'Ŵ' => 'W', 'Ẅ' => 'W', 'Ý' => 'Y', 'Ỳ' => 'Y', 'Ŷ' => 'Y', 'Ÿ' => 'Y', 'Ź' => 'Z', 'Ž' => 'Z', 'Ż' => 'Z', 'Þ' => 'Th', ]; Utf8/tables/romanization.php 0000644 00000102751 15233462216 0012101 0 ustar 00 <?php /** * Romanization lookup table * * This lookup tables provides a way to transform strings written in a language * different from the ones based upon latin letters into plain ASCII. * * Please note: this is not a scientific transliteration table. It only works * oneway from nonlatin to ASCII and it works by simple character replacement * only. Specialities of each language are not supported. * * @todo some keys are used multiple times * @todo remove or integrate commented pairs * * @author Andreas Gohr <andi@splitbrain.org> * @author Vitaly Blokhin <vitinfo@vitn.com> * @author Bisqwit <bisqwit@iki.fi> * @author Arthit Suriyawongkul <arthit@gmail.com> * @author Denis Scheither <amorphis@uni-bremen.de> * @author Eivind Morland <eivind.morland@gmail.com> * @link http://www.uconv.com/translit.htm * @link http://kanjidict.stc.cx/hiragana.php?src=2 * @link http://www.translatum.gr/converter/greek-transliteration.htm * @link http://en.wikipedia.org/wiki/Royal_Thai_General_System_of_Transcription * @link http://www.btranslations.com/resources/romanization/korean.asp */ return [ // scandinavian - differs from what we do in deaccent 'å' => 'a', 'Å' => 'A', 'ä' => 'a', 'Ä' => 'A', 'ö' => 'o', 'Ö' => 'O', //russian cyrillic 'а' => 'a', 'А' => 'A', 'б' => 'b', 'Б' => 'B', 'в' => 'v', 'В' => 'V', 'г' => 'g', 'Г' => 'G', 'д' => 'd', 'Д' => 'D', 'е' => 'e', 'Е' => 'E', 'ё' => 'jo', 'Ё' => 'Jo', 'ж' => 'zh', 'Ж' => 'Zh', 'з' => 'z', 'З' => 'Z', 'и' => 'i', 'И' => 'I', 'й' => 'j', 'Й' => 'J', 'к' => 'k', 'К' => 'K', 'л' => 'l', 'Л' => 'L', 'м' => 'm', 'М' => 'M', 'н' => 'n', 'Н' => 'N', 'о' => 'o', 'О' => 'O', 'п' => 'p', 'П' => 'P', 'р' => 'r', 'Р' => 'R', 'с' => 's', 'С' => 'S', 'т' => 't', 'Т' => 'T', 'у' => 'u', 'У' => 'U', 'ф' => 'f', 'Ф' => 'F', 'х' => 'x', 'Х' => 'X', 'ц' => 'c', 'Ц' => 'C', 'ч' => 'ch', 'Ч' => 'Ch', 'ш' => 'sh', 'Ш' => 'Sh', 'щ' => 'sch', 'Щ' => 'Sch', 'ъ' => '', 'Ъ' => '', 'ы' => 'y', 'Ы' => 'Y', 'ь' => '', 'Ь' => '', 'э' => 'eh', 'Э' => 'Eh', 'ю' => 'ju', 'Ю' => 'Ju', 'я' => 'ja', 'Я' => 'Ja', // Ukrainian cyrillic 'Ґ' => 'Gh', 'ґ' => 'gh', 'Є' => 'Je', 'є' => 'je', 'І' => 'I', 'і' => 'i', 'Ї' => 'Ji', 'ї' => 'ji', // Georgian 'ა' => 'a', 'ბ' => 'b', 'გ' => 'g', 'დ' => 'd', 'ე' => 'e', 'ვ' => 'v', 'ზ' => 'z', 'თ' => 'th', 'ი' => 'i', 'კ' => 'p', 'ლ' => 'l', 'მ' => 'm', 'ნ' => 'n', 'ო' => 'o', 'პ' => 'p', 'ჟ' => 'zh', 'რ' => 'r', 'ს' => 's', 'ტ' => 't', 'უ' => 'u', 'ფ' => 'ph', 'ქ' => 'kh', 'ღ' => 'gh', 'ყ' => 'q', 'შ' => 'sh', 'ჩ' => 'ch', 'ც' => 'c', 'ძ' => 'dh', 'წ' => 'w', 'ჭ' => 'j', 'ხ' => 'x', 'ჯ' => 'jh', 'ჰ' => 'xh', //Sanskrit 'अ' => 'a', 'आ' => 'ah', 'इ' => 'i', 'ई' => 'ih', 'उ' => 'u', 'ऊ' => 'uh', 'ऋ' => 'ry', 'ॠ' => 'ryh', 'ऌ' => 'ly', 'ॡ' => 'lyh', 'ए' => 'e', 'ऐ' => 'ay', 'ओ' => 'o', 'औ' => 'aw', 'अं' => 'amh', 'अः' => 'aq', 'क' => 'k', 'ख' => 'kh', 'ग' => 'g', 'घ' => 'gh', 'ङ' => 'nh', 'च' => 'c', 'छ' => 'ch', 'ज' => 'j', 'झ' => 'jh', 'ञ' => 'ny', 'ट' => 'tq', 'ठ' => 'tqh', 'ड' => 'dq', 'ढ' => 'dqh', 'ण' => 'nq', 'त' => 't', 'थ' => 'th', 'द' => 'd', 'ध' => 'dh', 'न' => 'n', 'प' => 'p', 'फ' => 'ph', 'ब' => 'b', 'भ' => 'bh', 'म' => 'm', 'य' => 'z', 'र' => 'r', 'ल' => 'l', 'व' => 'v', 'श' => 'sh', 'ष' => 'sqh', 'स' => 's', 'ह' => 'x', //Sanskrit diacritics 'Ā' => 'A', 'Ī' => 'I', 'Ū' => 'U', 'Ṛ' => 'R', 'Ṝ' => 'R', 'Ṅ' => 'N', 'Ñ' => 'N', 'Ṭ' => 'T', 'Ḍ' => 'D', 'Ṇ' => 'N', 'Ś' => 'S', 'Ṣ' => 'S', 'Ṁ' => 'M', 'Ṃ' => 'M', 'Ḥ' => 'H', 'Ḷ' => 'L', 'Ḹ' => 'L', 'ā' => 'a', 'ī' => 'i', 'ū' => 'u', 'ṛ' => 'r', 'ṝ' => 'r', 'ṅ' => 'n', 'ñ' => 'n', 'ṭ' => 't', 'ḍ' => 'd', 'ṇ' => 'n', 'ś' => 's', 'ṣ' => 's', 'ṁ' => 'm', 'ṃ' => 'm', 'ḥ' => 'h', 'ḷ' => 'l', 'ḹ' => 'l', //Hebrew 'א' => 'a', 'ב' => 'b', 'ג' => 'g', 'ד' => 'd', 'ה' => 'h', 'ו' => 'v', 'ז' => 'z', 'ח' => 'kh', 'ט' => 'th', 'י' => 'y', 'ך' => 'h', 'כ' => 'k', 'ל' => 'l', 'ם' => 'm', 'מ' => 'm', 'ן' => 'n', 'נ' => 'n', 'ס' => 's', 'ע' => 'ah', 'ף' => 'f', 'פ' => 'p', 'ץ' => 'c', 'צ' => 'c', 'ק' => 'q', 'ר' => 'r', 'ש' => 'sh', 'ת' => 't', //Arabic 'ا' => 'a', 'ب' => 'b', 'ت' => 't', 'ث' => 'th', 'ج' => 'g', 'ح' => 'xh', 'خ' => 'x', 'د' => 'd', 'ذ' => 'dh', 'ر' => 'r', 'ز' => 'z', 'س' => 's', 'ش' => 'sh', 'ص' => 's\'', 'ض' => 'd\'', 'ط' => 't\'', 'ظ' => 'z\'', 'ع' => 'y', 'غ' => 'gh', 'ف' => 'f', 'ق' => 'q', 'ك' => 'k', 'ل' => 'l', 'م' => 'm', 'ن' => 'n', 'ه' => 'x\'', 'و' => 'u', 'ي' => 'i', // Japanese characters (last update: 2008-05-09) // Japanese hiragana // 3 character syllables, っ doubles the consonant after 'っちゃ' => 'ccha', 'っちぇ' => 'cche', 'っちょ' => 'ccho', 'っちゅ' => 'cchu', 'っびゃ' => 'bbya', 'っびぇ' => 'bbye', 'っびぃ' => 'bbyi', 'っびょ' => 'bbyo', 'っびゅ' => 'bbyu', 'っぴゃ' => 'ppya', 'っぴぇ' => 'ppye', 'っぴぃ' => 'ppyi', 'っぴょ' => 'ppyo', 'っぴゅ' => 'ppyu', 'っちゃ' => 'ccha', 'っちぇ' => 'cche', 'っち' => 'cchi', 'っちょ' => 'ccho', 'っちゅ' => 'cchu', // 'っひゃ'=>'hya', // 'っひぇ'=>'hye', // 'っひぃ'=>'hyi', // 'っひょ'=>'hyo', // 'っひゅ'=>'hyu', 'っきゃ' => 'kkya', 'っきぇ' => 'kkye', 'っきぃ' => 'kkyi', 'っきょ' => 'kkyo', 'っきゅ' => 'kkyu', 'っぎゃ' => 'ggya', 'っぎぇ' => 'ggye', 'っぎぃ' => 'ggyi', 'っぎょ' => 'ggyo', 'っぎゅ' => 'ggyu', 'っみゃ' => 'mmya', 'っみぇ' => 'mmye', 'っみぃ' => 'mmyi', 'っみょ' => 'mmyo', 'っみゅ' => 'mmyu', 'っにゃ' => 'nnya', 'っにぇ' => 'nnye', 'っにぃ' => 'nnyi', 'っにょ' => 'nnyo', 'っにゅ' => 'nnyu', 'っりゃ' => 'rrya', 'っりぇ' => 'rrye', 'っりぃ' => 'rryi', 'っりょ' => 'rryo', 'っりゅ' => 'rryu', 'っしゃ' => 'ssha', 'っしぇ' => 'sshe', 'っし' => 'sshi', 'っしょ' => 'ssho', 'っしゅ' => 'sshu', // seperate hiragana 'n' ('n' + 'i' != 'ni', normally we would write "kon'nichi wa" but the // apostrophe would be converted to _ anyway) 'んあ' => 'n_a', 'んえ' => 'n_e', 'んい' => 'n_i', 'んお' => 'n_o', 'んう' => 'n_u', 'んや' => 'n_ya', 'んよ' => 'n_yo', 'んゆ' => 'n_yu', // 2 character syllables - normal 'ふぁ' => 'fa', 'ふぇ' => 'fe', 'ふぃ' => 'fi', 'ふぉ' => 'fo', 'ちゃ' => 'cha', 'ちぇ' => 'che', 'ち' => 'chi', 'ちょ' => 'cho', 'ちゅ' => 'chu', 'ひゃ' => 'hya', 'ひぇ' => 'hye', 'ひぃ' => 'hyi', 'ひょ' => 'hyo', 'ひゅ' => 'hyu', 'びゃ' => 'bya', 'びぇ' => 'bye', 'びぃ' => 'byi', 'びょ' => 'byo', 'びゅ' => 'byu', 'ぴゃ' => 'pya', 'ぴぇ' => 'pye', 'ぴぃ' => 'pyi', 'ぴょ' => 'pyo', 'ぴゅ' => 'pyu', 'きゃ' => 'kya', 'きぇ' => 'kye', 'きぃ' => 'kyi', 'きょ' => 'kyo', 'きゅ' => 'kyu', 'ぎゃ' => 'gya', 'ぎぇ' => 'gye', 'ぎぃ' => 'gyi', 'ぎょ' => 'gyo', 'ぎゅ' => 'gyu', 'みゃ' => 'mya', 'みぇ' => 'mye', 'みぃ' => 'myi', 'みょ' => 'myo', 'みゅ' => 'myu', 'にゃ' => 'nya', 'にぇ' => 'nye', 'にぃ' => 'nyi', 'にょ' => 'nyo', 'にゅ' => 'nyu', 'りゃ' => 'rya', 'りぇ' => 'rye', 'りぃ' => 'ryi', 'りょ' => 'ryo', 'りゅ' => 'ryu', 'しゃ' => 'sha', 'しぇ' => 'she', 'し' => 'shi', 'しょ' => 'sho', 'しゅ' => 'shu', 'じゃ' => 'ja', 'じぇ' => 'je', 'じょ' => 'jo', 'じゅ' => 'ju', 'うぇ' => 'we', 'うぃ' => 'wi', 'いぇ' => 'ye', // 2 character syllables, っ doubles the consonant after 'っば' => 'bba', 'っべ' => 'bbe', 'っび' => 'bbi', 'っぼ' => 'bbo', 'っぶ' => 'bbu', 'っぱ' => 'ppa', 'っぺ' => 'ppe', 'っぴ' => 'ppi', 'っぽ' => 'ppo', 'っぷ' => 'ppu', 'った' => 'tta', 'って' => 'tte', 'っち' => 'cchi', 'っと' => 'tto', 'っつ' => 'ttsu', 'っだ' => 'dda', 'っで' => 'dde', 'っぢ' => 'ddi', 'っど' => 'ddo', 'っづ' => 'ddu', 'っが' => 'gga', 'っげ' => 'gge', 'っぎ' => 'ggi', 'っご' => 'ggo', 'っぐ' => 'ggu', 'っか' => 'kka', 'っけ' => 'kke', 'っき' => 'kki', 'っこ' => 'kko', 'っく' => 'kku', 'っま' => 'mma', 'っめ' => 'mme', 'っみ' => 'mmi', 'っも' => 'mmo', 'っむ' => 'mmu', 'っな' => 'nna', 'っね' => 'nne', 'っに' => 'nni', 'っの' => 'nno', 'っぬ' => 'nnu', 'っら' => 'rra', 'っれ' => 'rre', 'っり' => 'rri', 'っろ' => 'rro', 'っる' => 'rru', 'っさ' => 'ssa', 'っせ' => 'sse', 'っし' => 'sshi', 'っそ' => 'sso', 'っす' => 'ssu', 'っざ' => 'zza', 'っぜ' => 'zze', 'っじ' => 'jji', 'っぞ' => 'zzo', 'っず' => 'zzu', // 1 character syllabels 'あ' => 'a', 'え' => 'e', 'い' => 'i', 'お' => 'o', 'う' => 'u', 'ん' => 'n', 'は' => 'ha', 'へ' => 'he', 'ひ' => 'hi', 'ほ' => 'ho', 'ふ' => 'fu', 'ば' => 'ba', 'べ' => 'be', 'び' => 'bi', 'ぼ' => 'bo', 'ぶ' => 'bu', 'ぱ' => 'pa', 'ぺ' => 'pe', 'ぴ' => 'pi', 'ぽ' => 'po', 'ぷ' => 'pu', 'た' => 'ta', 'て' => 'te', 'ち' => 'chi', 'と' => 'to', 'つ' => 'tsu', 'だ' => 'da', 'で' => 'de', 'ぢ' => 'di', 'ど' => 'do', 'づ' => 'du', 'が' => 'ga', 'げ' => 'ge', 'ぎ' => 'gi', 'ご' => 'go', 'ぐ' => 'gu', 'か' => 'ka', 'け' => 'ke', 'き' => 'ki', 'こ' => 'ko', 'く' => 'ku', 'ま' => 'ma', 'め' => 'me', 'み' => 'mi', 'も' => 'mo', 'む' => 'mu', 'な' => 'na', 'ね' => 'ne', 'に' => 'ni', 'の' => 'no', 'ぬ' => 'nu', 'ら' => 'ra', 'れ' => 're', 'り' => 'ri', 'ろ' => 'ro', 'る' => 'ru', 'さ' => 'sa', 'せ' => 'se', 'し' => 'shi', 'そ' => 'so', 'す' => 'su', 'わ' => 'wa', 'を' => 'wo', 'ざ' => 'za', 'ぜ' => 'ze', 'じ' => 'ji', 'ぞ' => 'zo', 'ず' => 'zu', 'や' => 'ya', 'よ' => 'yo', 'ゆ' => 'yu', // old characters 'ゑ' => 'we', 'ゐ' => 'wi', // convert what's left (probably only kicks in when something's missing above) // 'ぁ'=>'a','ぇ'=>'e','ぃ'=>'i','ぉ'=>'o','ぅ'=>'u', // 'ゃ'=>'ya','ょ'=>'yo','ゅ'=>'yu', // never seen one of those (disabled for the moment) // 'ヴぁ'=>'va','ヴぇ'=>'ve','ヴぃ'=>'vi','ヴぉ'=>'vo','ヴ'=>'vu', // 'でゃ'=>'dha','でぇ'=>'dhe','でぃ'=>'dhi','でょ'=>'dho','でゅ'=>'dhu', // 'どぁ'=>'dwa','どぇ'=>'dwe','どぃ'=>'dwi','どぉ'=>'dwo','どぅ'=>'dwu', // 'ぢゃ'=>'dya','ぢぇ'=>'dye','ぢぃ'=>'dyi','ぢょ'=>'dyo','ぢゅ'=>'dyu', // 'ふぁ'=>'fwa','ふぇ'=>'fwe','ふぃ'=>'fwi','ふぉ'=>'fwo','ふぅ'=>'fwu', // 'ふゃ'=>'fya','ふぇ'=>'fye','ふぃ'=>'fyi','ふょ'=>'fyo','ふゅ'=>'fyu', // 'すぁ'=>'swa','すぇ'=>'swe','すぃ'=>'swi','すぉ'=>'swo','すぅ'=>'swu', // 'てゃ'=>'tha','てぇ'=>'the','てぃ'=>'thi','てょ'=>'tho','てゅ'=>'thu', // 'つゃ'=>'tsa','つぇ'=>'tse','つぃ'=>'tsi','つょ'=>'tso','つ'=>'tsu', // 'とぁ'=>'twa','とぇ'=>'twe','とぃ'=>'twi','とぉ'=>'two','とぅ'=>'twu', // 'ヴゃ'=>'vya','ヴぇ'=>'vye','ヴぃ'=>'vyi','ヴょ'=>'vyo','ヴゅ'=>'vyu', // 'うぁ'=>'wha','うぇ'=>'whe','うぃ'=>'whi','うぉ'=>'who','うぅ'=>'whu', // 'じゃ'=>'zha','じぇ'=>'zhe','じぃ'=>'zhi','じょ'=>'zho','じゅ'=>'zhu', // 'じゃ'=>'zya','じぇ'=>'zye','じぃ'=>'zyi','じょ'=>'zyo','じゅ'=>'zyu', // 'spare' characters from other romanization systems // 'だ'=>'da','で'=>'de','ぢ'=>'di','ど'=>'do','づ'=>'du', // 'ら'=>'la','れ'=>'le','り'=>'li','ろ'=>'lo','る'=>'lu', // 'さ'=>'sa','せ'=>'se','し'=>'si','そ'=>'so','す'=>'su', // 'ちゃ'=>'cya','ちぇ'=>'cye','ちぃ'=>'cyi','ちょ'=>'cyo','ちゅ'=>'cyu', //'じゃ'=>'jya','じぇ'=>'jye','じぃ'=>'jyi','じょ'=>'jyo','じゅ'=>'jyu', //'りゃ'=>'lya','りぇ'=>'lye','りぃ'=>'lyi','りょ'=>'lyo','りゅ'=>'lyu', //'しゃ'=>'sya','しぇ'=>'sye','しぃ'=>'syi','しょ'=>'syo','しゅ'=>'syu', //'ちゃ'=>'tya','ちぇ'=>'tye','ちぃ'=>'tyi','ちょ'=>'tyo','ちゅ'=>'tyu', //'し'=>'ci',,い'=>'yi','ぢ'=>'dzi', //'っじゃ'=>'jja','っじぇ'=>'jje','っじ'=>'jji','っじょ'=>'jjo','っじゅ'=>'jju', // Japanese katakana // 4 character syllables: ッ doubles the consonant after, ー doubles the vowel before // (usualy written with macron, but we don't want that in our URLs) 'ッビャー' => 'bbyaa', 'ッビェー' => 'bbyee', 'ッビィー' => 'bbyii', 'ッビョー' => 'bbyoo', 'ッビュー' => 'bbyuu', 'ッピャー' => 'ppyaa', 'ッピェー' => 'ppyee', 'ッピィー' => 'ppyii', 'ッピョー' => 'ppyoo', 'ッピュー' => 'ppyuu', 'ッキャー' => 'kkyaa', 'ッキェー' => 'kkyee', 'ッキィー' => 'kkyii', 'ッキョー' => 'kkyoo', 'ッキュー' => 'kkyuu', 'ッギャー' => 'ggyaa', 'ッギェー' => 'ggyee', 'ッギィー' => 'ggyii', 'ッギョー' => 'ggyoo', 'ッギュー' => 'ggyuu', 'ッミャー' => 'mmyaa', 'ッミェー' => 'mmyee', 'ッミィー' => 'mmyii', 'ッミョー' => 'mmyoo', 'ッミュー' => 'mmyuu', 'ッニャー' => 'nnyaa', 'ッニェー' => 'nnyee', 'ッニィー' => 'nnyii', 'ッニョー' => 'nnyoo', 'ッニュー' => 'nnyuu', 'ッリャー' => 'rryaa', 'ッリェー' => 'rryee', 'ッリィー' => 'rryii', 'ッリョー' => 'rryoo', 'ッリュー' => 'rryuu', 'ッシャー' => 'sshaa', 'ッシェー' => 'sshee', 'ッシー' => 'sshii', 'ッショー' => 'sshoo', 'ッシュー' => 'sshuu', 'ッチャー' => 'cchaa', 'ッチェー' => 'cchee', 'ッチー' => 'cchii', 'ッチョー' => 'cchoo', 'ッチュー' => 'cchuu', 'ッティー' => 'ttii', 'ッヂィー' => 'ddii', // 3 character syllables - doubled vowels 'ファー' => 'faa', 'フェー' => 'fee', 'フィー' => 'fii', 'フォー' => 'foo', 'フャー' => 'fyaa', 'フェー' => 'fyee', 'フィー' => 'fyii', 'フョー' => 'fyoo', 'フュー' => 'fyuu', 'ヒャー' => 'hyaa', 'ヒェー' => 'hyee', 'ヒィー' => 'hyii', 'ヒョー' => 'hyoo', 'ヒュー' => 'hyuu', 'ビャー' => 'byaa', 'ビェー' => 'byee', 'ビィー' => 'byii', 'ビョー' => 'byoo', 'ビュー' => 'byuu', 'ピャー' => 'pyaa', 'ピェー' => 'pyee', 'ピィー' => 'pyii', 'ピョー' => 'pyoo', 'ピュー' => 'pyuu', 'キャー' => 'kyaa', 'キェー' => 'kyee', 'キィー' => 'kyii', 'キョー' => 'kyoo', 'キュー' => 'kyuu', 'ギャー' => 'gyaa', 'ギェー' => 'gyee', 'ギィー' => 'gyii', 'ギョー' => 'gyoo', 'ギュー' => 'gyuu', 'ミャー' => 'myaa', 'ミェー' => 'myee', 'ミィー' => 'myii', 'ミョー' => 'myoo', 'ミュー' => 'myuu', 'ニャー' => 'nyaa', 'ニェー' => 'nyee', 'ニィー' => 'nyii', 'ニョー' => 'nyoo', 'ニュー' => 'nyuu', 'リャー' => 'ryaa', 'リェー' => 'ryee', 'リィー' => 'ryii', 'リョー' => 'ryoo', 'リュー' => 'ryuu', 'シャー' => 'shaa', 'シェー' => 'shee', 'シー' => 'shii', 'ショー' => 'shoo', 'シュー' => 'shuu', 'ジャー' => 'jaa', 'ジェー' => 'jee', 'ジー' => 'jii', 'ジョー' => 'joo', 'ジュー' => 'juu', 'スァー' => 'swaa', 'スェー' => 'swee', 'スィー' => 'swii', 'スォー' => 'swoo', 'スゥー' => 'swuu', 'デァー' => 'daa', 'デェー' => 'dee', 'ディー' => 'dii', 'デォー' => 'doo', 'デゥー' => 'duu', 'チャー' => 'chaa', 'チェー' => 'chee', 'チー' => 'chii', 'チョー' => 'choo', 'チュー' => 'chuu', 'ヂャー' => 'dyaa', 'ヂェー' => 'dyee', 'ヂィー' => 'dyii', 'ヂョー' => 'dyoo', 'ヂュー' => 'dyuu', 'ツャー' => 'tsaa', 'ツェー' => 'tsee', 'ツィー' => 'tsii', 'ツョー' => 'tsoo', 'ツー' => 'tsuu', 'トァー' => 'twaa', 'トェー' => 'twee', 'トィー' => 'twii', 'トォー' => 'twoo', 'トゥー' => 'twuu', 'ドァー' => 'dwaa', 'ドェー' => 'dwee', 'ドィー' => 'dwii', 'ドォー' => 'dwoo', 'ドゥー' => 'dwuu', 'ウァー' => 'whaa', 'ウェー' => 'whee', 'ウィー' => 'whii', 'ウォー' => 'whoo', 'ウゥー' => 'whuu', 'ヴャー' => 'vyaa', 'ヴェー' => 'vyee', 'ヴィー' => 'vyii', 'ヴョー' => 'vyoo', 'ヴュー' => 'vyuu', 'ヴァー' => 'vaa', 'ヴェー' => 'vee', 'ヴィー' => 'vii', 'ヴォー' => 'voo', 'ヴー' => 'vuu', 'ウェー' => 'wee', 'ウィー' => 'wii', 'イェー' => 'yee', 'ティー' => 'tii', 'ヂィー' => 'dii', // 3 character syllables - doubled consonants 'ッビャ' => 'bbya', 'ッビェ' => 'bbye', 'ッビィ' => 'bbyi', 'ッビョ' => 'bbyo', 'ッビュ' => 'bbyu', 'ッピャ' => 'ppya', 'ッピェ' => 'ppye', 'ッピィ' => 'ppyi', 'ッピョ' => 'ppyo', 'ッピュ' => 'ppyu', 'ッキャ' => 'kkya', 'ッキェ' => 'kkye', 'ッキィ' => 'kkyi', 'ッキョ' => 'kkyo', 'ッキュ' => 'kkyu', 'ッギャ' => 'ggya', 'ッギェ' => 'ggye', 'ッギィ' => 'ggyi', 'ッギョ' => 'ggyo', 'ッギュ' => 'ggyu', 'ッミャ' => 'mmya', 'ッミェ' => 'mmye', 'ッミィ' => 'mmyi', 'ッミョ' => 'mmyo', 'ッミュ' => 'mmyu', 'ッニャ' => 'nnya', 'ッニェ' => 'nnye', 'ッニィ' => 'nnyi', 'ッニョ' => 'nnyo', 'ッニュ' => 'nnyu', 'ッリャ' => 'rrya', 'ッリェ' => 'rrye', 'ッリィ' => 'rryi', 'ッリョ' => 'rryo', 'ッリュ' => 'rryu', 'ッシャ' => 'ssha', 'ッシェ' => 'sshe', 'ッシ' => 'sshi', 'ッショ' => 'ssho', 'ッシュ' => 'sshu', 'ッチャ' => 'ccha', 'ッチェ' => 'cche', 'ッチ' => 'cchi', 'ッチョ' => 'ccho', 'ッチュ' => 'cchu', 'ッティ' => 'tti', 'ッヂィ' => 'ddi', // 3 character syllables - doubled vowel and consonants 'ッバー' => 'bbaa', 'ッベー' => 'bbee', 'ッビー' => 'bbii', 'ッボー' => 'bboo', 'ッブー' => 'bbuu', 'ッパー' => 'ppaa', 'ッペー' => 'ppee', 'ッピー' => 'ppii', 'ッポー' => 'ppoo', 'ップー' => 'ppuu', 'ッケー' => 'kkee', 'ッキー' => 'kkii', 'ッコー' => 'kkoo', 'ックー' => 'kkuu', 'ッカー' => 'kkaa', 'ッガー' => 'ggaa', 'ッゲー' => 'ggee', 'ッギー' => 'ggii', 'ッゴー' => 'ggoo', 'ッグー' => 'gguu', 'ッマー' => 'maa', 'ッメー' => 'mee', 'ッミー' => 'mii', 'ッモー' => 'moo', 'ッムー' => 'muu', 'ッナー' => 'nnaa', 'ッネー' => 'nnee', 'ッニー' => 'nnii', 'ッノー' => 'nnoo', 'ッヌー' => 'nnuu', 'ッラー' => 'rraa', 'ッレー' => 'rree', 'ッリー' => 'rrii', 'ッロー' => 'rroo', 'ッルー' => 'rruu', 'ッサー' => 'ssaa', 'ッセー' => 'ssee', 'ッシー' => 'sshii', 'ッソー' => 'ssoo', 'ッスー' => 'ssuu', 'ッザー' => 'zzaa', 'ッゼー' => 'zzee', 'ッジー' => 'jjii', 'ッゾー' => 'zzoo', 'ッズー' => 'zzuu', 'ッター' => 'ttaa', 'ッテー' => 'ttee', 'ッチー' => 'chii', 'ットー' => 'ttoo', 'ッツー' => 'ttsuu', 'ッダー' => 'ddaa', 'ッデー' => 'ddee', 'ッヂー' => 'ddii', 'ッドー' => 'ddoo', 'ッヅー' => 'dduu', // 2 character syllables - normal 'ファ' => 'fa', 'フェ' => 'fe', 'フィ' => 'fi', 'フォ' => 'fo', 'フゥ' => 'fu', // 'フャ'=>'fya', // 'フェ'=>'fye', // 'フィ'=>'fyi', // 'フョ'=>'fyo', // 'フュ'=>'fyu', 'フャ' => 'fa', 'フェ' => 'fe', 'フィ' => 'fi', 'フョ' => 'fo', 'フュ' => 'fu', 'ヒャ' => 'hya', 'ヒェ' => 'hye', 'ヒィ' => 'hyi', 'ヒョ' => 'hyo', 'ヒュ' => 'hyu', 'ビャ' => 'bya', 'ビェ' => 'bye', 'ビィ' => 'byi', 'ビョ' => 'byo', 'ビュ' => 'byu', 'ピャ' => 'pya', 'ピェ' => 'pye', 'ピィ' => 'pyi', 'ピョ' => 'pyo', 'ピュ' => 'pyu', 'キャ' => 'kya', 'キェ' => 'kye', 'キィ' => 'kyi', 'キョ' => 'kyo', 'キュ' => 'kyu', 'ギャ' => 'gya', 'ギェ' => 'gye', 'ギィ' => 'gyi', 'ギョ' => 'gyo', 'ギュ' => 'gyu', 'ミャ' => 'mya', 'ミェ' => 'mye', 'ミィ' => 'myi', 'ミョ' => 'myo', 'ミュ' => 'myu', 'ニャ' => 'nya', 'ニェ' => 'nye', 'ニィ' => 'nyi', 'ニョ' => 'nyo', 'ニュ' => 'nyu', 'リャ' => 'rya', 'リェ' => 'rye', 'リィ' => 'ryi', 'リョ' => 'ryo', 'リュ' => 'ryu', 'シャ' => 'sha', 'シェ' => 'she', 'ショ' => 'sho', 'シュ' => 'shu', 'ジャ' => 'ja', 'ジェ' => 'je', 'ジョ' => 'jo', 'ジュ' => 'ju', 'スァ' => 'swa', 'スェ' => 'swe', 'スィ' => 'swi', 'スォ' => 'swo', 'スゥ' => 'swu', 'デァ' => 'da', 'デェ' => 'de', 'ディ' => 'di', 'デォ' => 'do', 'デゥ' => 'du', 'チャ' => 'cha', 'チェ' => 'che', 'チ' => 'chi', 'チョ' => 'cho', 'チュ' => 'chu', // 'ヂャ'=>'dya', // 'ヂェ'=>'dye', // 'ヂィ'=>'dyi', // 'ヂョ'=>'dyo', // 'ヂュ'=>'dyu', 'ツャ' => 'tsa', 'ツェ' => 'tse', 'ツィ' => 'tsi', 'ツョ' => 'tso', 'ツ' => 'tsu', 'トァ' => 'twa', 'トェ' => 'twe', 'トィ' => 'twi', 'トォ' => 'two', 'トゥ' => 'twu', 'ドァ' => 'dwa', 'ドェ' => 'dwe', 'ドィ' => 'dwi', 'ドォ' => 'dwo', 'ドゥ' => 'dwu', 'ウァ' => 'wha', 'ウェ' => 'whe', 'ウィ' => 'whi', 'ウォ' => 'who', 'ウゥ' => 'whu', 'ヴャ' => 'vya', 'ヴェ' => 'vye', 'ヴィ' => 'vyi', 'ヴョ' => 'vyo', 'ヴュ' => 'vyu', 'ヴァ' => 'va', 'ヴェ' => 've', 'ヴィ' => 'vi', 'ヴォ' => 'vo', 'ヴ' => 'vu', 'ウェ' => 'we', 'ウィ' => 'wi', 'イェ' => 'ye', 'ティ' => 'ti', 'ヂィ' => 'di', // 2 character syllables - doubled vocal 'アー' => 'aa', 'エー' => 'ee', 'イー' => 'ii', 'オー' => 'oo', 'ウー' => 'uu', 'ダー' => 'daa', 'デー' => 'dee', 'ヂー' => 'dii', 'ドー' => 'doo', 'ヅー' => 'duu', 'ハー' => 'haa', 'ヘー' => 'hee', 'ヒー' => 'hii', 'ホー' => 'hoo', 'フー' => 'fuu', 'バー' => 'baa', 'ベー' => 'bee', 'ビー' => 'bii', 'ボー' => 'boo', 'ブー' => 'buu', 'パー' => 'paa', 'ペー' => 'pee', 'ピー' => 'pii', 'ポー' => 'poo', 'プー' => 'puu', 'ケー' => 'kee', 'キー' => 'kii', 'コー' => 'koo', 'クー' => 'kuu', 'カー' => 'kaa', 'ガー' => 'gaa', 'ゲー' => 'gee', 'ギー' => 'gii', 'ゴー' => 'goo', 'グー' => 'guu', 'マー' => 'maa', 'メー' => 'mee', 'ミー' => 'mii', 'モー' => 'moo', 'ムー' => 'muu', 'ナー' => 'naa', 'ネー' => 'nee', 'ニー' => 'nii', 'ノー' => 'noo', 'ヌー' => 'nuu', 'ラー' => 'raa', 'レー' => 'ree', 'リー' => 'rii', 'ロー' => 'roo', 'ルー' => 'ruu', 'サー' => 'saa', 'セー' => 'see', 'シー' => 'shii', 'ソー' => 'soo', 'スー' => 'suu', 'ザー' => 'zaa', 'ゼー' => 'zee', 'ジー' => 'jii', 'ゾー' => 'zoo', 'ズー' => 'zuu', 'ター' => 'taa', 'テー' => 'tee', 'チー' => 'chii', 'トー' => 'too', 'ツー' => 'tsuu', 'ワー' => 'waa', 'ヲー' => 'woo', 'ヤー' => 'yaa', 'ヨー' => 'yoo', 'ユー' => 'yuu', 'ヵー' => 'kaa', 'ヶー' => 'kee', // old characters 'ヱー' => 'wee', 'ヰー' => 'wii', // seperate katakana 'n' 'ンア' => 'n_a', 'ンエ' => 'n_e', 'ンイ' => 'n_i', 'ンオ' => 'n_o', 'ンウ' => 'n_u', 'ンヤ' => 'n_ya', 'ンヨ' => 'n_yo', 'ンユ' => 'n_yu', // 2 character syllables - doubled consonants 'ッバ' => 'bba', 'ッベ' => 'bbe', 'ッビ' => 'bbi', 'ッボ' => 'bbo', 'ッブ' => 'bbu', 'ッパ' => 'ppa', 'ッペ' => 'ppe', 'ッピ' => 'ppi', 'ッポ' => 'ppo', 'ップ' => 'ppu', 'ッケ' => 'kke', 'ッキ' => 'kki', 'ッコ' => 'kko', 'ック' => 'kku', 'ッカ' => 'kka', 'ッガ' => 'gga', 'ッゲ' => 'gge', 'ッギ' => 'ggi', 'ッゴ' => 'ggo', 'ッグ' => 'ggu', 'ッマ' => 'ma', 'ッメ' => 'me', 'ッミ' => 'mi', 'ッモ' => 'mo', 'ッム' => 'mu', 'ッナ' => 'nna', 'ッネ' => 'nne', 'ッニ' => 'nni', 'ッノ' => 'nno', 'ッヌ' => 'nnu', 'ッラ' => 'rra', 'ッレ' => 'rre', 'ッリ' => 'rri', 'ッロ' => 'rro', 'ッル' => 'rru', 'ッサ' => 'ssa', 'ッセ' => 'sse', 'ッシ' => 'sshi', 'ッソ' => 'sso', 'ッス' => 'ssu', 'ッザ' => 'zza', 'ッゼ' => 'zze', 'ッジ' => 'jji', 'ッゾ' => 'zzo', 'ッズ' => 'zzu', 'ッタ' => 'tta', 'ッテ' => 'tte', 'ッチ' => 'cchi', 'ット' => 'tto', 'ッツ' => 'ttsu', 'ッダ' => 'dda', 'ッデ' => 'dde', 'ッヂ' => 'ddi', 'ッド' => 'ddo', 'ッヅ' => 'ddu', // 1 character syllables 'ア' => 'a', 'エ' => 'e', 'イ' => 'i', 'オ' => 'o', 'ウ' => 'u', 'ン' => 'n', 'ハ' => 'ha', 'ヘ' => 'he', 'ヒ' => 'hi', 'ホ' => 'ho', 'フ' => 'fu', 'バ' => 'ba', 'ベ' => 'be', 'ビ' => 'bi', 'ボ' => 'bo', 'ブ' => 'bu', 'パ' => 'pa', 'ペ' => 'pe', 'ピ' => 'pi', 'ポ' => 'po', 'プ' => 'pu', 'ケ' => 'ke', 'キ' => 'ki', 'コ' => 'ko', 'ク' => 'ku', 'カ' => 'ka', 'ガ' => 'ga', 'ゲ' => 'ge', 'ギ' => 'gi', 'ゴ' => 'go', 'グ' => 'gu', 'マ' => 'ma', 'メ' => 'me', 'ミ' => 'mi', 'モ' => 'mo', 'ム' => 'mu', 'ナ' => 'na', 'ネ' => 'ne', 'ニ' => 'ni', 'ノ' => 'no', 'ヌ' => 'nu', 'ラ' => 'ra', 'レ' => 're', 'リ' => 'ri', 'ロ' => 'ro', 'ル' => 'ru', 'サ' => 'sa', 'セ' => 'se', 'シ' => 'shi', 'ソ' => 'so', 'ス' => 'su', 'ザ' => 'za', 'ゼ' => 'ze', 'ジ' => 'ji', 'ゾ' => 'zo', 'ズ' => 'zu', 'タ' => 'ta', 'テ' => 'te', 'チ' => 'chi', 'ト' => 'to', 'ツ' => 'tsu', 'ダ' => 'da', 'デ' => 'de', 'ヂ' => 'di', 'ド' => 'do', 'ヅ' => 'du', 'ワ' => 'wa', 'ヲ' => 'wo', 'ヤ' => 'ya', 'ヨ' => 'yo', 'ユ' => 'yu', 'ヵ' => 'ka', 'ヶ' => 'ke', // old characters 'ヱ' => 'we', 'ヰ' => 'wi', // convert what's left (probably only kicks in when something's missing above) 'ァ' => 'a', 'ェ' => 'e', 'ィ' => 'i', 'ォ' => 'o', 'ゥ' => 'u', 'ャ' => 'ya', 'ョ' => 'yo', 'ュ' => 'yu', // special characters '・' => '_', '、' => '_', 'ー' => '_', // when used with hiragana (seldom), this character would not be converted otherwise // 'ラ'=>'la', // 'レ'=>'le', // 'リ'=>'li', // 'ロ'=>'lo', // 'ル'=>'lu', // 'チャ'=>'cya', // 'チェ'=>'cye', // 'チィ'=>'cyi', // 'チョ'=>'cyo', // 'チュ'=>'cyu', // 'デャ'=>'dha', // 'デェ'=>'dhe', // 'ディ'=>'dhi', // 'デョ'=>'dho', // 'デュ'=>'dhu', // 'リャ'=>'lya', // 'リェ'=>'lye', // 'リィ'=>'lyi', // 'リョ'=>'lyo', // 'リュ'=>'lyu', // 'テャ'=>'tha', // 'テェ'=>'the', // 'ティ'=>'thi', // 'テョ'=>'tho', // 'テュ'=>'thu', // 'ファ'=>'fwa', // 'フェ'=>'fwe', // 'フィ'=>'fwi', // 'フォ'=>'fwo', // 'フゥ'=>'fwu', // 'チャ'=>'tya', // 'チェ'=>'tye', // 'チィ'=>'tyi', // 'チョ'=>'tyo', // 'チュ'=>'tyu', // 'ジャ'=>'jya', // 'ジェ'=>'jye', // 'ジィ'=>'jyi', // 'ジョ'=>'jyo', // 'ジュ'=>'jyu', // 'ジャ'=>'zha', // 'ジェ'=>'zhe', // 'ジィ'=>'zhi', // 'ジョ'=>'zho', // 'ジュ'=>'zhu', // 'ジャ'=>'zya', // 'ジェ'=>'zye', // 'ジィ'=>'zyi', // 'ジョ'=>'zyo', // 'ジュ'=>'zyu', // 'シャ'=>'sya', // 'シェ'=>'sye', // 'シィ'=>'syi', // 'ショ'=>'syo', // 'シュ'=>'syu', // 'シ'=>'ci', // 'フ'=>'hu', // 'シ'=>'si', // 'チ'=>'ti', // 'ツ'=>'tu', // 'イ'=>'yi', // 'ヂ'=>'dzi', // "Greeklish" 'Γ' => 'G', 'Δ' => 'E', 'Θ' => 'Th', 'Λ' => 'L', 'Ξ' => 'X', 'Π' => 'P', 'Σ' => 'S', 'Φ' => 'F', 'Ψ' => 'Ps', 'γ' => 'g', 'δ' => 'e', 'θ' => 'th', 'λ' => 'l', 'ξ' => 'x', 'π' => 'p', 'σ' => 's', 'φ' => 'f', 'ψ' => 'ps', // Thai 'ก' => 'k', 'ข' => 'kh', 'ฃ' => 'kh', 'ค' => 'kh', 'ฅ' => 'kh', 'ฆ' => 'kh', 'ง' => 'ng', 'จ' => 'ch', 'ฉ' => 'ch', 'ช' => 'ch', 'ซ' => 's', 'ฌ' => 'ch', 'ญ' => 'y', 'ฎ' => 'd', 'ฏ' => 't', 'ฐ' => 'th', 'ฑ' => 'd', 'ฒ' => 'th', 'ณ' => 'n', 'ด' => 'd', 'ต' => 't', 'ถ' => 'th', 'ท' => 'th', 'ธ' => 'th', 'น' => 'n', 'บ' => 'b', 'ป' => 'p', 'ผ' => 'ph', 'ฝ' => 'f', 'พ' => 'ph', 'ฟ' => 'f', 'ภ' => 'ph', 'ม' => 'm', 'ย' => 'y', 'ร' => 'r', 'ฤ' => 'rue', 'ฤๅ' => 'rue', 'ล' => 'l', 'ฦ' => 'lue', 'ฦๅ' => 'lue', 'ว' => 'w', 'ศ' => 's', 'ษ' => 's', 'ส' => 's', 'ห' => 'h', 'ฬ' => 'l', 'ฮ' => 'h', 'ะ' => 'a', 'ั' => 'a', 'รร' => 'a', 'า' => 'a', 'ๅ' => 'a', 'ำ' => 'am', 'ํา' => 'am', 'ิ' => 'i', 'ี' => 'i', 'ึ' => 'ue', 'ี' => 'ue', 'ุ' => 'u', 'ู' => 'u', 'เ' => 'e', 'แ' => 'ae', 'โ' => 'o', 'อ' => 'o', 'ียะ' => 'ia', 'ีย' => 'ia', 'ือะ' => 'uea', 'ือ' => 'uea', 'ัวะ' => 'ua', 'ัว' => 'ua', 'ใ' => 'ai', 'ไ' => 'ai', 'ัย' => 'ai', 'าย' => 'ai', 'าว' => 'ao', 'ุย' => 'ui', 'อย' => 'oi', 'ือย' => 'ueai', 'วย' => 'uai', 'ิว' => 'io', '็ว' => 'eo', 'ียว' => 'iao', '่' => '', '้' => '', '๊' => '', '๋' => '', '็' => '', '์' => '', '๎' => '', 'ํ' => '', 'ฺ' => '', 'ๆ' => '2', '๏' => 'o', 'ฯ' => '-', '๚' => '-', '๛' => '-', '๐' => '0', '๑' => '1', '๒' => '2', '๓' => '3', '๔' => '4', '๕' => '5', '๖' => '6', '๗' => '7', '๘' => '8', '๙' => '9', // Korean 'ㄱ' => 'k', 'ㅋ' => 'kh', 'ㄲ' => 'kk', 'ㄷ' => 't', 'ㅌ' => 'th', 'ㄸ' => 'tt', 'ㅂ' => 'p', 'ㅍ' => 'ph', 'ㅃ' => 'pp', 'ㅈ' => 'c', 'ㅊ' => 'ch', 'ㅉ' => 'cc', 'ㅅ' => 's', 'ㅆ' => 'ss', 'ㅎ' => 'h', 'ㅇ' => 'ng', 'ㄴ' => 'n', 'ㄹ' => 'l', 'ㅁ' => 'm', 'ㅏ' => 'a', 'ㅓ' => 'e', 'ㅗ' => 'o', 'ㅜ' => 'wu', 'ㅡ' => 'u', 'ㅣ' => 'i', 'ㅐ' => 'ay', 'ㅔ' => 'ey', 'ㅚ' => 'oy', 'ㅘ' => 'wa', 'ㅝ' => 'we', 'ㅟ' => 'wi', 'ㅙ' => 'way', 'ㅞ' => 'wey', 'ㅢ' => 'uy', 'ㅑ' => 'ya', 'ㅕ' => 'ye', 'ㅛ' => 'oy', 'ㅠ' => 'yu', 'ㅒ' => 'yay', 'ㅖ' => 'yey', ]; Extension/Event.php 0000644 00000014105 15233462216 0010317 0 ustar 00 <?php // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps namespace dokuwiki\Extension; /** * The Action plugin event */ class Event { /** @var string READONLY event name, objects must register against this name to see the event */ public $name = ''; /** @var mixed|null READWRITE data relevant to the event, no standardised format, refer to event docs */ public $data = null; /** * @var mixed|null READWRITE the results of the event action, only relevant in "_AFTER" advise * event handlers may modify this if they are preventing the default action * to provide the after event handlers with event results */ public $result = null; /** @var bool READONLY if true, event handlers can prevent the events default action */ public $canPreventDefault = true; /** @var bool whether or not to carry out the default action associated with the event */ protected $runDefault = true; /** @var bool whether or not to continue propagating the event to other handlers */ protected $mayContinue = true; /** * event constructor * * @param string $name * @param mixed $data */ public function __construct($name, &$data) { $this->name = $name; $this->data =& $data; } /** * @return string */ public function __toString() { return $this->name; } /** * advise all registered BEFORE handlers of this event * * if these methods are used by functions outside of this object, they must * properly handle correct processing of any default action and issue an * advise_after() signal. e.g. * $evt = new dokuwiki\Plugin\Doku_Event(name, data); * if ($evt->advise_before(canPreventDefault) { * // default action code block * } * $evt->advise_after(); * unset($evt); * * @param bool $enablePreventDefault * @return bool results of processing the event, usually $this->runDefault */ public function advise_before($enablePreventDefault = true) { global $EVENT_HANDLER; $this->canPreventDefault = $enablePreventDefault; if ($EVENT_HANDLER !== null) { $EVENT_HANDLER->process_event($this, 'BEFORE'); } else { dbglog($this->name . ':BEFORE event triggered before event system was initialized'); } return (!$enablePreventDefault || $this->runDefault); } /** * advise all registered AFTER handlers of this event * * @param bool $enablePreventDefault * @see advise_before() for details */ public function advise_after() { global $EVENT_HANDLER; $this->mayContinue = true; if ($EVENT_HANDLER !== null) { $EVENT_HANDLER->process_event($this, 'AFTER'); } else { dbglog($this->name . ':AFTER event triggered before event system was initialized'); } } /** * trigger * * - advise all registered (<event>_BEFORE) handlers that this event is about to take place * - carry out the default action using $this->data based on $enablePrevent and * $this->_default, all of which may have been modified by the event handlers. * - advise all registered (<event>_AFTER) handlers that the event has taken place * * @param null|callable $action * @param bool $enablePrevent * @return mixed $event->results * the value set by any <event>_before or <event> handlers if the default action is prevented * or the results of the default action (as modified by <event>_after handlers) * or NULL no action took place and no handler modified the value */ public function trigger($action = null, $enablePrevent = true) { if (!is_callable($action)) { $enablePrevent = false; if ($action !== null) { trigger_error( 'The default action of ' . $this . ' is not null but also not callable. Maybe the method is not public?', E_USER_WARNING ); } } if ($this->advise_before($enablePrevent) && is_callable($action)) { $this->result = call_user_func_array($action, [&$this->data]); } $this->advise_after(); return $this->result; } /** * stopPropagation * * stop any further processing of the event by event handlers * this function does not prevent the default action taking place */ public function stopPropagation() { $this->mayContinue = false; } /** * may the event propagate to the next handler? * * @return bool */ public function mayPropagate() { return $this->mayContinue; } /** * preventDefault * * prevent the default action taking place */ public function preventDefault() { $this->runDefault = false; } /** * should the default action be executed? * * @return bool */ public function mayRunDefault() { return $this->runDefault; } /** * Convenience method to trigger an event * * Creates, triggers and destroys an event in one go * * @param string $name name for the event * @param mixed $data event data * @param callable $action (optional, default=NULL) default action, a php callback function * @param bool $canPreventDefault (optional, default=true) can hooks prevent the default action * * @return mixed the event results value after all event processing is complete * by default this is the return value of the default action however * it can be set or modified by event handler hooks */ static public function createAndTrigger($name, &$data, $action = null, $canPreventDefault = true) { $evt = new Event($name, $data); return $evt->trigger($action, $canPreventDefault); } } Extension/SyntaxPlugin.php 0000644 00000010111 15233462216 0011674 0 ustar 00 <?php namespace dokuwiki\Extension; use Doku_Handler; use Doku_Renderer; /** * Syntax Plugin Prototype * * All DokuWiki plugins to extend the parser/rendering mechanism * need to inherit from this class * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ abstract class SyntaxPlugin extends \dokuwiki\Parsing\ParserMode\Plugin { use PluginTrait; protected $allowedModesSetup = false; /** * Syntax Type * * Needs to return one of the mode types defined in $PARSER_MODES in Parser.php * * @return string */ abstract public function getType(); /** * Allowed Mode Types * * Defines the mode types for other dokuwiki markup that maybe nested within the * plugin's own markup. Needs to return an array of one or more of the mode types * defined in $PARSER_MODES in Parser.php * * @return array */ public function getAllowedTypes() { return array(); } /** * Paragraph Type * * Defines how this syntax is handled regarding paragraphs. This is important * for correct XHTML nesting. Should return one of the following: * * 'normal' - The plugin can be used inside paragraphs * 'block' - Open paragraphs need to be closed before plugin output * 'stack' - Special case. Plugin wraps other paragraphs. * * @see Doku_Handler_Block * * @return string */ public function getPType() { return 'normal'; } /** * Handler to prepare matched data for the rendering process * * This function can only pass data to render() via its return value - render() * may be not be run during the object's current life. * * Usually you should only need the $match param. * * @param string $match The text matched by the patterns * @param int $state The lexer state for the match * @param int $pos The character position of the matched text * @param Doku_Handler $handler The Doku_Handler object * @return bool|array Return an array with all data you want to use in render, false don't add an instruction */ abstract public function handle($match, $state, $pos, Doku_Handler $handler); /** * Handles the actual output creation. * * The function must not assume any other of the classes methods have been run * during the object's current life. The only reliable data it receives are its * parameters. * * The function should always check for the given output format and return false * when a format isn't supported. * * $renderer contains a reference to the renderer object which is * currently handling the rendering. You need to use it for writing * the output. How this is done depends on the renderer used (specified * by $format * * The contents of the $data array depends on what the handler() function above * created * * @param string $format output format being rendered * @param Doku_Renderer $renderer the current renderer object * @param array $data data created by handler() * @return boolean rendered correctly? (however, returned value is not used at the moment) */ abstract public function render($format, Doku_Renderer $renderer, $data); /** * There should be no need to override this function * * @param string $mode * @return bool */ public function accepts($mode) { if (!$this->allowedModesSetup) { global $PARSER_MODES; $allowedModeTypes = $this->getAllowedTypes(); foreach ($allowedModeTypes as $mt) { $this->allowedModes = array_merge($this->allowedModes, $PARSER_MODES[$mt]); } $idx = array_search(substr(get_class($this), 7), (array)$this->allowedModes); if ($idx !== false) { unset($this->allowedModes[$idx]); } $this->allowedModesSetup = true; } return parent::accepts($mode); } } Extension/Plugin.php 0000644 00000000300 15233462216 0010464 0 ustar 00 <?php namespace dokuwiki\Extension; /** * DokuWiki Base Plugin * * Most plugin types inherit from this class */ abstract class Plugin implements PluginInterface { use PluginTrait; } Extension/AdminPlugin.php 0000644 00000005723 15233462216 0011453 0 ustar 00 <?php namespace dokuwiki\Extension; /** * Admin Plugin Prototype * * Implements an admin interface in a plugin * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Christopher Smith <chris@jalakai.co.uk> */ abstract class AdminPlugin extends Plugin { /** * Return the text that is displayed at the main admin menu * (Default localized language string 'menu' is returned, override this function for setting another name) * * @param string $language language code * @return string menu string */ public function getMenuText($language) { $menutext = $this->getLang('menu'); if (!$menutext) { $info = $this->getInfo(); $menutext = $info['name'] . ' ...'; } return $menutext; } /** * Return the path to the icon being displayed in the main admin menu. * By default it tries to find an 'admin.svg' file in the plugin directory. * (Override this function for setting another image) * * Important: you have to return a single path, monochrome SVG icon! It has to be * under 2 Kilobytes! * * We recommend icons from https://materialdesignicons.com/ or to use a matching * style. * * @return string full path to the icon file */ public function getMenuIcon() { $plugin = $this->getPluginName(); return DOKU_PLUGIN . $plugin . '/admin.svg'; } /** * Determine position in list in admin window * Lower values are sorted up * * @return int */ public function getMenuSort() { return 1000; } /** * Carry out required processing */ public function handle() { // some plugins might not need this } /** * Output html of the admin page */ abstract public function html(); /** * Checks if access should be granted to this admin plugin * * @return bool true if the current user may access this admin plugin */ public function isAccessibleByCurrentUser() { $data = []; $data['instance'] = $this; $data['hasAccess'] = false; $event = new Event('ADMINPLUGIN_ACCESS_CHECK', $data); if($event->advise_before()) { if ($this->forAdminOnly()) { $data['hasAccess'] = auth_isadmin(); } else { $data['hasAccess'] = auth_ismanager(); } } $event->advise_after(); return $data['hasAccess']; } /** * Return true for access only by admins (config:superuser) or false if managers are allowed as well * * @return bool */ public function forAdminOnly() { return true; } /** * Return array with ToC items. Items can be created with the html_mktocitem() * * @see html_mktocitem() * @see tpl_toc() * * @return array */ public function getTOC() { return array(); } } Extension/ActionPlugin.php 0000644 00000000736 15233462216 0011637 0 ustar 00 <?php namespace dokuwiki\Extension; /** * Action Plugin Prototype * * Handles action hooks within a plugin * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Christopher Smith <chris@jalakai.co.uk> */ abstract class ActionPlugin extends Plugin { /** * Registers a callback function for a given event * * @param \Doku_Event_Handler $controller */ abstract public function register(\Doku_Event_Handler $controller); } Extension/PluginTrait.php 0000644 00000016056 15233462216 0011507 0 ustar 00 <?php namespace dokuwiki\Extension; /** * Provides standard DokuWiki plugin behaviour */ trait PluginTrait { protected $localised = false; // set to true by setupLocale() after loading language dependent strings protected $lang = array(); // array to hold language dependent strings, best accessed via ->getLang() protected $configloaded = false; // set to true by loadConfig() after loading plugin configuration variables protected $conf = array(); // array to hold plugin settings, best accessed via ->getConf() /** * @see PluginInterface::getInfo() */ public function getInfo() { $parts = explode('_', get_class($this)); $info = DOKU_PLUGIN . '/' . $parts[2] . '/plugin.info.txt'; if (file_exists($info)) return confToHash($info); msg( 'getInfo() not implemented in ' . get_class($this) . ' and ' . $info . ' not found.<br />' . 'Verify you\'re running the latest version of the plugin. If the problem persists, send a ' . 'bug report to the author of the ' . $parts[2] . ' plugin.', -1 ); return array( 'date' => '0000-00-00', 'name' => $parts[2] . ' plugin', ); } /** * @see PluginInterface::isSingleton() */ public function isSingleton() { return true; } /** * @see PluginInterface::loadHelper() */ public function loadHelper($name, $msg = true) { $obj = plugin_load('helper', $name); if (is_null($obj) && $msg) msg("Helper plugin $name is not available or invalid.", -1); return $obj; } // region introspection methods /** * @see PluginInterface::getPluginType() */ public function getPluginType() { list($t) = explode('_', get_class($this), 2); return $t; } /** * @see PluginInterface::getPluginName() */ public function getPluginName() { list(/* $t */, /* $p */, $n) = explode('_', get_class($this), 4); return $n; } /** * @see PluginInterface::getPluginComponent() */ public function getPluginComponent() { list(/* $t */, /* $p */, /* $n */, $c) = explode('_', get_class($this), 4); return (isset($c) ? $c : ''); } // endregion // region localization methods /** * @see PluginInterface::getLang() */ public function getLang($id) { if (!$this->localised) $this->setupLocale(); return (isset($this->lang[$id]) ? $this->lang[$id] : ''); } /** * @see PluginInterface::locale_xhtml() */ public function locale_xhtml($id) { return p_cached_output($this->localFN($id)); } /** * @see PluginInterface::localFN() */ public function localFN($id, $ext = 'txt') { global $conf; $plugin = $this->getPluginName(); $file = DOKU_CONF . 'plugin_lang/' . $plugin . '/' . $conf['lang'] . '/' . $id . '.' . $ext; if (!file_exists($file)) { $file = DOKU_PLUGIN . $plugin . '/lang/' . $conf['lang'] . '/' . $id . '.' . $ext; if (!file_exists($file)) { //fall back to english $file = DOKU_PLUGIN . $plugin . '/lang/en/' . $id . '.' . $ext; } } return $file; } /** * @see PluginInterface::setupLocale() */ public function setupLocale() { if ($this->localised) return; global $conf, $config_cascade; // definitely don't invoke "global $lang" $path = DOKU_PLUGIN . $this->getPluginName() . '/lang/'; $lang = array(); // don't include once, in case several plugin components require the same language file @include($path . 'en/lang.php'); foreach ($config_cascade['lang']['plugin'] as $config_file) { if (file_exists($config_file . $this->getPluginName() . '/en/lang.php')) { include($config_file . $this->getPluginName() . '/en/lang.php'); } } if ($conf['lang'] != 'en') { @include($path . $conf['lang'] . '/lang.php'); foreach ($config_cascade['lang']['plugin'] as $config_file) { if (file_exists($config_file . $this->getPluginName() . '/' . $conf['lang'] . '/lang.php')) { include($config_file . $this->getPluginName() . '/' . $conf['lang'] . '/lang.php'); } } } $this->lang = $lang; $this->localised = true; } // endregion // region configuration methods /** * @see PluginInterface::getConf() */ public function getConf($setting, $notset = false) { if (!$this->configloaded) { $this->loadConfig(); } if (isset($this->conf[$setting])) { return $this->conf[$setting]; } else { return $notset; } } /** * @see PluginInterface::loadConfig() */ public function loadConfig() { global $conf; $defaults = $this->readDefaultSettings(); $plugin = $this->getPluginName(); foreach ($defaults as $key => $value) { if (isset($conf['plugin'][$plugin][$key])) continue; $conf['plugin'][$plugin][$key] = $value; } $this->configloaded = true; $this->conf =& $conf['plugin'][$plugin]; } /** * read the plugin's default configuration settings from conf/default.php * this function is automatically called through getConf() * * @return array setting => value */ protected function readDefaultSettings() { $path = DOKU_PLUGIN . $this->getPluginName() . '/conf/'; $conf = array(); if (file_exists($path . 'default.php')) { include($path . 'default.php'); } return $conf; } // endregion // region output methods /** * @see PluginInterface::email() */ public function email($email, $name = '', $class = '', $more = '') { if (!$email) return $name; $email = obfuscate($email); if (!$name) $name = $email; $class = "class='" . ($class ? $class : 'mail') . "'"; return "<a href='mailto:$email' $class title='$email' $more>$name</a>"; } /** * @see PluginInterface::external_link() */ public function external_link($link, $title = '', $class = '', $target = '', $more = '') { global $conf; $link = htmlentities($link); if (!$title) $title = $link; if (!$target) $target = $conf['target']['extern']; if ($conf['relnofollow']) $more .= ' rel="nofollow"'; if ($class) $class = " class='$class'"; if ($target) $target = " target='$target'"; if ($more) $more = " " . trim($more); return "<a href='$link'$class$target$more>$title</a>"; } /** * @see PluginInterface::render_text() */ public function render_text($text, $format = 'xhtml') { return p_render($format, p_get_instructions($text), $info); } // endregion } Extension/RemotePlugin.php 0000644 00000006526 15233462216 0011660 0 ustar 00 <?php namespace dokuwiki\Extension; use dokuwiki\Remote\Api; use ReflectionException; use ReflectionMethod; /** * Remote Plugin prototype * * Add functionality to the remote API in a plugin */ abstract class RemotePlugin extends Plugin { private $api; /** * Constructor */ public function __construct() { $this->api = new Api(); } /** * Get all available methods with remote access. * * By default it exports all public methods of a remote plugin. Methods beginning * with an underscore are skipped. * * @return array Information about all provided methods. {@see dokuwiki\Remote\RemoteAPI}. * @throws ReflectionException */ public function _getMethods() { $result = array(); $reflection = new \ReflectionClass($this); foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { // skip parent methods, only methods further down are exported $declaredin = $method->getDeclaringClass()->name; if ($declaredin === 'dokuwiki\Extension\Plugin' || $declaredin === 'dokuwiki\Extension\RemotePlugin') { continue; } $method_name = $method->name; if (strpos($method_name, '_') === 0) { continue; } // strip asterisks $doc = $method->getDocComment(); $doc = preg_replace( array('/^[ \t]*\/\*+[ \t]*/m', '/[ \t]*\*+[ \t]*/m', '/\*+\/\s*$/m', '/\s*\/\s*$/m'), array('', '', '', ''), $doc ); // prepare data $data = array(); $data['name'] = $method_name; $data['public'] = 0; $data['doc'] = $doc; $data['args'] = array(); // get parameter type from doc block type hint foreach ($method->getParameters() as $parameter) { $name = $parameter->name; $type = 'string'; // we default to string if (preg_match('/^@param[ \t]+([\w|\[\]]+)[ \t]\$' . $name . '/m', $doc, $m)) { $type = $this->cleanTypeHint($m[1]); } $data['args'][] = $type; } // get return type from doc block type hint if (preg_match('/^@return[ \t]+([\w|\[\]]+)/m', $doc, $m)) { $data['return'] = $this->cleanTypeHint($m[1]); } else { $data['return'] = 'string'; } // add to result $result[$method_name] = $data; } return $result; } /** * Matches the given type hint against the valid options for the remote API * * @param string $hint * @return string */ protected function cleanTypeHint($hint) { $types = explode('|', $hint); foreach ($types as $t) { if (substr($t, -2) === '[]') { return 'array'; } if ($t === 'boolean') { return 'bool'; } if (in_array($t, array('array', 'string', 'int', 'double', 'bool', 'null', 'date', 'file'))) { return $t; } } return 'string'; } /** * @return Api */ protected function getApi() { return $this->api; } } Extension/AuthPlugin.php 0000644 00000036345 15233462216 0011330 0 ustar 00 <?php namespace dokuwiki\Extension; /** * Auth Plugin Prototype * * allows to authenticate users in a plugin * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Chris Smith <chris@jalakai.co.uk> * @author Jan Schumann <js@jschumann-it.com> */ abstract class AuthPlugin extends Plugin { public $success = true; /** * Possible things an auth backend module may be able to * do. The things a backend can do need to be set to true * in the constructor. */ protected $cando = array( 'addUser' => false, // can Users be created? 'delUser' => false, // can Users be deleted? 'modLogin' => false, // can login names be changed? 'modPass' => false, // can passwords be changed? 'modName' => false, // can real names be changed? 'modMail' => false, // can emails be changed? 'modGroups' => false, // can groups be changed? 'getUsers' => false, // can a (filtered) list of users be retrieved? 'getUserCount' => false, // can the number of users be retrieved? 'getGroups' => false, // can a list of available groups be retrieved? 'external' => false, // does the module do external auth checking? 'logout' => true, // can the user logout again? (eg. not possible with HTTP auth) ); /** * Constructor. * * Carry out sanity checks to ensure the object is * able to operate. Set capabilities in $this->cando * array here * * For future compatibility, sub classes should always include a call * to parent::__constructor() in their constructors! * * Set $this->success to false if checks fail * * @author Christopher Smith <chris@jalakai.co.uk> */ public function __construct() { // the base class constructor does nothing, derived class // constructors do the real work } /** * Available Capabilities. [ DO NOT OVERRIDE ] * * For introspection/debugging * * @author Christopher Smith <chris@jalakai.co.uk> * @return array */ public function getCapabilities() { return array_keys($this->cando); } /** * Capability check. [ DO NOT OVERRIDE ] * * Checks the capabilities set in the $this->cando array and * some pseudo capabilities (shortcutting access to multiple * ones) * * ususal capabilities start with lowercase letter * shortcut capabilities start with uppercase letter * * @author Andreas Gohr <andi@splitbrain.org> * @param string $cap the capability to check * @return bool */ public function canDo($cap) { switch ($cap) { case 'Profile': // can at least one of the user's properties be changed? return ($this->cando['modPass'] || $this->cando['modName'] || $this->cando['modMail']); break; case 'UserMod': // can at least anything be changed? return ($this->cando['modPass'] || $this->cando['modName'] || $this->cando['modMail'] || $this->cando['modLogin'] || $this->cando['modGroups'] || $this->cando['modMail']); break; default: // print a helping message for developers if (!isset($this->cando[$cap])) { msg("Check for unknown capability '$cap' - Do you use an outdated Plugin?", -1); } return $this->cando[$cap]; } } /** * Trigger the AUTH_USERDATA_CHANGE event and call the modification function. [ DO NOT OVERRIDE ] * * You should use this function instead of calling createUser, modifyUser or * deleteUsers directly. The event handlers can prevent the modification, for * example for enforcing a user name schema. * * @author Gabriel Birke <birke@d-scribe.de> * @param string $type Modification type ('create', 'modify', 'delete') * @param array $params Parameters for the createUser, modifyUser or deleteUsers method. * The content of this array depends on the modification type * @return bool|null|int Result from the modification function or false if an event handler has canceled the action */ public function triggerUserMod($type, $params) { $validTypes = array( 'create' => 'createUser', 'modify' => 'modifyUser', 'delete' => 'deleteUsers', ); if (empty($validTypes[$type])) { return false; } $result = false; $eventdata = array('type' => $type, 'params' => $params, 'modification_result' => null); $evt = new Event('AUTH_USER_CHANGE', $eventdata); if ($evt->advise_before(true)) { $result = call_user_func_array(array($this, $validTypes[$type]), $evt->data['params']); $evt->data['modification_result'] = $result; } $evt->advise_after(); unset($evt); return $result; } /** * Log off the current user [ OPTIONAL ] * * Is run in addition to the ususal logoff method. Should * only be needed when trustExternal is implemented. * * @see auth_logoff() * @author Andreas Gohr <andi@splitbrain.org> */ public function logOff() { } /** * Do all authentication [ OPTIONAL ] * * Set $this->cando['external'] = true when implemented * * If this function is implemented it will be used to * authenticate a user - all other DokuWiki internals * will not be used for authenticating (except this * function returns null, in which case, DokuWiki will * still run auth_login as a fallback, which may call * checkPass()). If this function is not returning null, * implementing checkPass() is not needed here anymore. * * The function can be used to authenticate against third * party cookies or Apache auth mechanisms and replaces * the auth_login() function * * The function will be called with or without a set * username. If the Username is given it was called * from the login form and the given credentials might * need to be checked. If no username was given it * the function needs to check if the user is logged in * by other means (cookie, environment). * * The function needs to set some globals needed by * DokuWiki like auth_login() does. * * @see auth_login() * @author Andreas Gohr <andi@splitbrain.org> * * @param string $user Username * @param string $pass Cleartext Password * @param bool $sticky Cookie should not expire * @return bool true on successful auth, * null on unknown result (fallback to checkPass) */ public function trustExternal($user, $pass, $sticky = false) { /* some example: global $USERINFO; global $conf; $sticky ? $sticky = true : $sticky = false; //sanity check // do the checking here // set the globals if authed $USERINFO['name'] = 'FIXME'; $USERINFO['mail'] = 'FIXME'; $USERINFO['grps'] = array('FIXME'); $_SERVER['REMOTE_USER'] = $user; $_SESSION[DOKU_COOKIE]['auth']['user'] = $user; $_SESSION[DOKU_COOKIE]['auth']['pass'] = $pass; $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO; return true; */ } /** * Check user+password [ MUST BE OVERRIDDEN ] * * Checks if the given user exists and the given * plaintext password is correct * * May be ommited if trustExternal is used. * * @author Andreas Gohr <andi@splitbrain.org> * @param string $user the user name * @param string $pass the clear text password * @return bool */ public function checkPass($user, $pass) { msg("no valid authorisation system in use", -1); return false; } /** * Return user info [ MUST BE OVERRIDDEN ] * * Returns info about the given user needs to contain * at least these fields: * * name string full name of the user * mail string email address of the user * grps array list of groups the user is in * * @author Andreas Gohr <andi@splitbrain.org> * @param string $user the user name * @param bool $requireGroups whether or not the returned data must include groups * @return false|array containing user data or false */ public function getUserData($user, $requireGroups = true) { if (!$this->cando['external']) msg("no valid authorisation system in use", -1); return false; } /** * Create a new User [implement only where required/possible] * * Returns false if the user already exists, null when an error * occurred and true if everything went well. * * The new user HAS TO be added to the default group by this * function! * * Set addUser capability when implemented * * @author Andreas Gohr <andi@splitbrain.org> * @param string $user * @param string $pass * @param string $name * @param string $mail * @param null|array $grps * @return bool|null */ public function createUser($user, $pass, $name, $mail, $grps = null) { msg("authorisation method does not allow creation of new users", -1); return null; } /** * Modify user data [implement only where required/possible] * * Set the mod* capabilities according to the implemented features * * @author Chris Smith <chris@jalakai.co.uk> * @param string $user nick of the user to be changed * @param array $changes array of field/value pairs to be changed (password will be clear text) * @return bool */ public function modifyUser($user, $changes) { msg("authorisation method does not allow modifying of user data", -1); return false; } /** * Delete one or more users [implement only where required/possible] * * Set delUser capability when implemented * * @author Chris Smith <chris@jalakai.co.uk> * @param array $users * @return int number of users deleted */ public function deleteUsers($users) { msg("authorisation method does not allow deleting of users", -1); return 0; } /** * Return a count of the number of user which meet $filter criteria * [should be implemented whenever retrieveUsers is implemented] * * Set getUserCount capability when implemented * * @author Chris Smith <chris@jalakai.co.uk> * @param array $filter array of field/pattern pairs, empty array for no filter * @return int */ public function getUserCount($filter = array()) { msg("authorisation method does not provide user counts", -1); return 0; } /** * Bulk retrieval of user data [implement only where required/possible] * * Set getUsers capability when implemented * * @author Chris Smith <chris@jalakai.co.uk> * @param int $start index of first user to be returned * @param int $limit max number of users to be returned, 0 for unlimited * @param array $filter array of field/pattern pairs, null for no filter * @return array list of userinfo (refer getUserData for internal userinfo details) */ public function retrieveUsers($start = 0, $limit = 0, $filter = null) { msg("authorisation method does not support mass retrieval of user data", -1); return array(); } /** * Define a group [implement only where required/possible] * * Set addGroup capability when implemented * * @author Chris Smith <chris@jalakai.co.uk> * @param string $group * @return bool */ public function addGroup($group) { msg("authorisation method does not support independent group creation", -1); return false; } /** * Retrieve groups [implement only where required/possible] * * Set getGroups capability when implemented * * @author Chris Smith <chris@jalakai.co.uk> * @param int $start * @param int $limit * @return array */ public function retrieveGroups($start = 0, $limit = 0) { msg("authorisation method does not support group list retrieval", -1); return array(); } /** * Return case sensitivity of the backend [OPTIONAL] * * When your backend is caseinsensitive (eg. you can login with USER and * user) then you need to overwrite this method and return false * * @return bool */ public function isCaseSensitive() { return true; } /** * Sanitize a given username [OPTIONAL] * * This function is applied to any user name that is given to * the backend and should also be applied to any user name within * the backend before returning it somewhere. * * This should be used to enforce username restrictions. * * @author Andreas Gohr <andi@splitbrain.org> * @param string $user username * @return string the cleaned username */ public function cleanUser($user) { return $user; } /** * Sanitize a given groupname [OPTIONAL] * * This function is applied to any groupname that is given to * the backend and should also be applied to any groupname within * the backend before returning it somewhere. * * This should be used to enforce groupname restrictions. * * Groupnames are to be passed without a leading '@' here. * * @author Andreas Gohr <andi@splitbrain.org> * @param string $group groupname * @return string the cleaned groupname */ public function cleanGroup($group) { return $group; } /** * Check Session Cache validity [implement only where required/possible] * * DokuWiki caches user info in the user's session for the timespan defined * in $conf['auth_security_timeout']. * * This makes sure slow authentication backends do not slow down DokuWiki. * This also means that changes to the user database will not be reflected * on currently logged in users. * * To accommodate for this, the user manager plugin will touch a reference * file whenever a change is submitted. This function compares the filetime * of this reference file with the time stored in the session. * * This reference file mechanism does not reflect changes done directly in * the backend's database through other means than the user manager plugin. * * Fast backends might want to return always false, to force rechecks on * each page load. Others might want to use their own checking here. If * unsure, do not override. * * @param string $user - The username * @author Andreas Gohr <andi@splitbrain.org> * @return bool */ public function useSessionCache($user) { global $conf; return ($_SESSION[DOKU_COOKIE]['auth']['time'] >= @filemtime($conf['cachedir'] . '/sessionpurge')); } } Extension/PluginController.php 0000644 00000032277 15233462216 0012552 0 ustar 00 <?php namespace dokuwiki\Extension; /** * Class to encapsulate access to dokuwiki plugins * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Christopher Smith <chris@jalakai.co.uk> */ class PluginController { /** @var array the types of plugins DokuWiki supports */ const PLUGIN_TYPES = ['auth', 'admin', 'syntax', 'action', 'renderer', 'helper', 'remote', 'cli']; protected $listByType = []; /** @var array all installed plugins and their enabled state [plugin=>enabled] */ protected $masterList = []; protected $pluginCascade = ['default' => [], 'local' => [], 'protected' => []]; protected $lastLocalConfigFile = ''; /** * Populates the master list of plugins */ public function __construct() { $this->loadConfig(); $this->populateMasterList(); } /** * Returns a list of available plugins of given type * * @param $type string, plugin_type name; * the type of plugin to return, * use empty string for all types * @param $all bool; * false to only return enabled plugins, * true to return both enabled and disabled plugins * * @return array of * - plugin names when $type = '' * - or plugin component names when a $type is given * * @author Andreas Gohr <andi@splitbrain.org> */ public function getList($type = '', $all = false) { // request the complete list if (!$type) { return $all ? array_keys($this->masterList) : array_keys(array_filter($this->masterList)); } if (!isset($this->listByType[$type]['enabled'])) { $this->listByType[$type]['enabled'] = $this->getListByType($type, true); } if ($all && !isset($this->listByType[$type]['disabled'])) { $this->listByType[$type]['disabled'] = $this->getListByType($type, false); } return $all ? array_merge($this->listByType[$type]['enabled'], $this->listByType[$type]['disabled']) : $this->listByType[$type]['enabled']; } /** * Loads the given plugin and creates an object of it * * @param $type string type of plugin to load * @param $name string name of the plugin to load * @param $new bool true to return a new instance of the plugin, false to use an already loaded instance * @param $disabled bool true to load even disabled plugins * @return PluginInterface|null the plugin object or null on failure * @author Andreas Gohr <andi@splitbrain.org> * */ public function load($type, $name, $new = false, $disabled = false) { //we keep all loaded plugins available in global scope for reuse global $DOKU_PLUGINS; list($plugin, /* $component */) = $this->splitName($name); // check if disabled if (!$disabled && !$this->isEnabled($plugin)) { return null; } $class = $type . '_plugin_' . $name; //plugin already loaded? if (!empty($DOKU_PLUGINS[$type][$name])) { if ($new || !$DOKU_PLUGINS[$type][$name]->isSingleton()) { return class_exists($class, true) ? new $class : null; } return $DOKU_PLUGINS[$type][$name]; } //construct class and instantiate if (!class_exists($class, true)) { # the plugin might be in the wrong directory $inf = confToHash(DOKU_PLUGIN . "$plugin/plugin.info.txt"); if ($inf['base'] && $inf['base'] != $plugin) { msg( sprintf( "Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.", hsc($plugin), hsc( $inf['base'] ) ), -1 ); } elseif (preg_match('/^' . DOKU_PLUGIN_NAME_REGEX . '$/', $plugin) !== 1) { msg( sprintf( "Plugin name '%s' is not a valid plugin name, only the characters a-z and 0-9 are allowed. " . 'Maybe the plugin has been installed in the wrong directory?', hsc($plugin) ), -1 ); } return null; } $DOKU_PLUGINS[$type][$name] = new $class; return $DOKU_PLUGINS[$type][$name]; } /** * Whether plugin is disabled * * @param string $plugin name of plugin * @return bool true disabled, false enabled * @deprecated in favor of the more sensible isEnabled where the return value matches the enabled state */ public function isDisabled($plugin) { dbg_deprecated('isEnabled()'); return !$this->isEnabled($plugin); } /** * Check whether plugin is disabled * * @param string $plugin name of plugin * @return bool true enabled, false disabled */ public function isEnabled($plugin) { return !empty($this->masterList[$plugin]); } /** * Disable the plugin * * @param string $plugin name of plugin * @return bool true saving succeed, false saving failed */ public function disable($plugin) { if (array_key_exists($plugin, $this->pluginCascade['protected'])) return false; $this->masterList[$plugin] = 0; return $this->saveList(); } /** * Enable the plugin * * @param string $plugin name of plugin * @return bool true saving succeed, false saving failed */ public function enable($plugin) { if (array_key_exists($plugin, $this->pluginCascade['protected'])) return false; $this->masterList[$plugin] = 1; return $this->saveList(); } /** * Returns cascade of the config files * * @return array with arrays of plugin configs */ public function getCascade() { return $this->pluginCascade; } /** * Read all installed plugins and their current enabled state */ protected function populateMasterList() { if ($dh = @opendir(DOKU_PLUGIN)) { $all_plugins = array(); while (false !== ($plugin = readdir($dh))) { if ($plugin[0] === '.') continue; // skip hidden entries if (is_file(DOKU_PLUGIN . $plugin)) continue; // skip files, we're only interested in directories if (array_key_exists($plugin, $this->masterList) && $this->masterList[$plugin] == 0) { $all_plugins[$plugin] = 0; } elseif (array_key_exists($plugin, $this->masterList) && $this->masterList[$plugin] == 1) { $all_plugins[$plugin] = 1; } else { $all_plugins[$plugin] = 1; } } $this->masterList = $all_plugins; if (!file_exists($this->lastLocalConfigFile)) { $this->saveList(true); } } } /** * Includes the plugin config $files * and returns the entries of the $plugins array set in these files * * @param array $files list of files to include, latter overrides previous * @return array with entries of the $plugins arrays of the included files */ protected function checkRequire($files) { $plugins = array(); foreach ($files as $file) { if (file_exists($file)) { include_once($file); } } return $plugins; } /** * Save the current list of plugins * * @param bool $forceSave ; * false to save only when config changed * true to always save * @return bool true saving succeed, false saving failed */ protected function saveList($forceSave = false) { global $conf; if (empty($this->masterList)) return false; // Rebuild list of local settings $local_plugins = $this->rebuildLocal(); if ($local_plugins != $this->pluginCascade['local'] || $forceSave) { $file = $this->lastLocalConfigFile; $out = "<?php\n/*\n * Local plugin enable/disable settings\n" . " * Auto-generated through plugin/extension manager\n *\n" . " * NOTE: Plugins will not be added to this file unless there " . "is a need to override a default setting. Plugins are\n" . " * enabled by default.\n */\n"; foreach ($local_plugins as $plugin => $value) { $out .= "\$plugins['$plugin'] = $value;\n"; } // backup current file (remove any existing backup) if (file_exists($file)) { $backup = $file . '.bak'; if (file_exists($backup)) @unlink($backup); if (!@copy($file, $backup)) return false; if (!empty($conf['fperm'])) chmod($backup, $conf['fperm']); } //check if can open for writing, else restore return io_saveFile($file, $out); } return false; } /** * Rebuild the set of local plugins * * @return array array of plugins to be saved in end($config_cascade['plugins']['local']) */ protected function rebuildLocal() { //assign to local variable to avoid overwriting $backup = $this->masterList; //Can't do anything about protected one so rule them out completely $local_default = array_diff_key($backup, $this->pluginCascade['protected']); //Diff between local+default and default //gives us the ones we need to check and save $diffed_ones = array_diff_key($local_default, $this->pluginCascade['default']); //The ones which we are sure of (list of 0s not in default) $sure_plugins = array_filter($diffed_ones, array($this, 'negate')); //the ones in need of diff $conflicts = array_diff_key($local_default, $diffed_ones); //The final list return array_merge($sure_plugins, array_diff_assoc($conflicts, $this->pluginCascade['default'])); } /** * Build the list of plugins and cascade * */ protected function loadConfig() { global $config_cascade; foreach (array('default', 'protected') as $type) { if (array_key_exists($type, $config_cascade['plugins'])) { $this->pluginCascade[$type] = $this->checkRequire($config_cascade['plugins'][$type]); } } $local = $config_cascade['plugins']['local']; $this->lastLocalConfigFile = array_pop($local); $this->pluginCascade['local'] = $this->checkRequire(array($this->lastLocalConfigFile)); if (is_array($local)) { $this->pluginCascade['default'] = array_merge( $this->pluginCascade['default'], $this->checkRequire($local) ); } $this->masterList = array_merge( $this->pluginCascade['default'], $this->pluginCascade['local'], $this->pluginCascade['protected'] ); } /** * Returns a list of available plugin components of given type * * @param string $type plugin_type name; the type of plugin to return, * @param bool $enabled true to return enabled plugins, * false to return disabled plugins * @return array of plugin components of requested type */ protected function getListByType($type, $enabled) { $master_list = $enabled ? array_keys(array_filter($this->masterList)) : array_keys(array_filter($this->masterList, array($this, 'negate'))); $plugins = array(); foreach ($master_list as $plugin) { if (file_exists(DOKU_PLUGIN . "$plugin/$type.php")) { $plugins[] = $plugin; continue; } $typedir = DOKU_PLUGIN . "$plugin/$type/"; if (is_dir($typedir)) { if ($dp = opendir($typedir)) { while (false !== ($component = readdir($dp))) { if (strpos($component, '.') === 0 || strtolower(substr($component, -4)) !== '.php') continue; if (is_file($typedir . $component)) { $plugins[] = $plugin . '_' . substr($component, 0, -4); } } closedir($dp); } } }//foreach return $plugins; } /** * Split name in a plugin name and a component name * * @param string $name * @return array with * - plugin name * - and component name when available, otherwise empty string */ protected function splitName($name) { if (!isset($this->masterList[$name])) { return explode('_', $name, 2); } return array($name, ''); } /** * Returns inverse boolean value of the input * * @param mixed $input * @return bool inversed boolean value of input */ protected function negate($input) { return !(bool)$input; } } Extension/EventHandler.php 0000644 00000006276 15233462216 0011627 0 ustar 00 <?php // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps namespace dokuwiki\Extension; /** * Controls the registration and execution of all events, */ class EventHandler { // public properties: none // private properties protected $hooks = array(); // array of events and their registered handlers /** * event_handler * * constructor, loads all action plugins and calls their register() method giving them * an opportunity to register any hooks they require */ public function __construct() { // load action plugins /** @var ActionPlugin $plugin */ $plugin = null; $pluginlist = plugin_list('action'); foreach ($pluginlist as $plugin_name) { $plugin = plugin_load('action', $plugin_name); if ($plugin !== null) $plugin->register($this); } } /** * register_hook * * register a hook for an event * * @param string $event string name used by the event, (incl '_before' or '_after' for triggers) * @param string $advise * @param object $obj object in whose scope method is to be executed, * if NULL, method is assumed to be a globally available function * @param string $method event handler function * @param mixed $param data passed to the event handler * @param int $seq sequence number for ordering hook execution (ascending) */ public function register_hook($event, $advise, $obj, $method, $param = null, $seq = 0) { $seq = (int)$seq; $doSort = !isset($this->hooks[$event . '_' . $advise][$seq]); $this->hooks[$event . '_' . $advise][$seq][] = array($obj, $method, $param); if ($doSort) { ksort($this->hooks[$event . '_' . $advise]); } } /** * process the before/after event * * @param Event $event * @param string $advise BEFORE or AFTER */ public function process_event($event, $advise = '') { $evt_name = $event->name . ($advise ? '_' . $advise : '_BEFORE'); if (!empty($this->hooks[$evt_name])) { foreach ($this->hooks[$evt_name] as $sequenced_hooks) { foreach ($sequenced_hooks as $hook) { list($obj, $method, $param) = $hook; if ($obj === null) { $method($event, $param); } else { $obj->$method($event, $param); } if (!$event->mayPropagate()) return; } } } } /** * Check if an event has any registered handlers * * When $advise is empty, both BEFORE and AFTER events will be considered, * otherwise only the given advisory is checked * * @param string $name Name of the event * @param string $advise BEFORE, AFTER or empty * @return bool */ public function hasHandlerForEvent($name, $advise = '') { if ($advise) { return isset($this->hooks[$name . '_' . $advise]); } return isset($this->hooks[$name . '_BEFORE']) || isset($this->hooks[$name . '_AFTER']); } } Extension/CLIPlugin.php 0000644 00000000361 15233462216 0011023 0 ustar 00 <?php namespace dokuwiki\Extension; /** * CLI plugin prototype * * Provides DokuWiki plugin functionality on top of php-cli */ abstract class CLIPlugin extends \splitbrain\phpcli\CLI implements PluginInterface { use PluginTrait; } Extension/PluginInterface.php 0000644 00000011536 15233462216 0012322 0 ustar 00 <?php namespace dokuwiki\Extension; /** * DokuWiki Plugin Interface * * Defines the public contract all DokuWiki plugins will adhere to. The actual code * to do so is defined in dokuwiki\Extension\PluginTrait * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Christopher Smith <chris@jalakai.co.uk> */ interface PluginInterface { /** * General Info * * Needs to return a associative array with the following values: * * base - the plugin's base name (eg. the directory it needs to be installed in) * author - Author of the plugin * email - Email address to contact the author * date - Last modified date of the plugin in YYYY-MM-DD format * name - Name of the plugin * desc - Short description of the plugin (Text only) * url - Website with more information on the plugin (eg. syntax description) */ public function getInfo(); /** * The type of the plugin inferred from the class name * * @return string plugin type */ public function getPluginType(); /** * The name of the plugin inferred from the class name * * @return string plugin name */ public function getPluginName(); /** * The component part of the plugin inferred from the class name * * @return string component name */ public function getPluginComponent(); /** * Access plugin language strings * * to try to minimise unnecessary loading of the strings when the plugin doesn't require them * e.g. when info plugin is querying plugins for information about themselves. * * @param string $id id of the string to be retrieved * @return string in appropriate language or english if not available */ public function getLang($id); /** * retrieve a language dependent file and pass to xhtml renderer for display * plugin equivalent of p_locale_xhtml() * * @param string $id id of language dependent wiki page * @return string parsed contents of the wiki page in xhtml format */ public function locale_xhtml($id); /** * Prepends appropriate path for a language dependent filename * plugin equivalent of localFN() * * @param string $id id of localization file * @param string $ext The file extension (usually txt) * @return string wiki text */ public function localFN($id, $ext = 'txt'); /** * Reads all the plugins language dependent strings into $this->lang * this function is automatically called by getLang() * * @todo this could be made protected and be moved to the trait only */ public function setupLocale(); /** * use this function to access plugin configuration variables * * @param string $setting the setting to access * @param mixed $notset what to return if the setting is not available * @return mixed */ public function getConf($setting, $notset = false); /** * merges the plugin's default settings with any local settings * this function is automatically called through getConf() * * @todo this could be made protected and be moved to the trait only */ public function loadConfig(); /** * Loads a given helper plugin (if enabled) * * @author Esther Brunner <wikidesign@gmail.com> * * @param string $name name of plugin to load * @param bool $msg if a message should be displayed in case the plugin is not available * @return PluginInterface|null helper plugin object */ public function loadHelper($name, $msg = true); /** * email * standardised function to generate an email link according to obfuscation settings * * @param string $email * @param string $name * @param string $class * @param string $more * @return string html */ public function email($email, $name = '', $class = '', $more = ''); /** * external_link * standardised function to generate an external link according to conf settings * * @param string $link * @param string $title * @param string $class * @param string $target * @param string $more * @return string */ public function external_link($link, $title = '', $class = '', $target = '', $more = ''); /** * output text string through the parser, allows dokuwiki markup to be used * very ineffecient for small pieces of data - try not to use * * @param string $text wiki markup to parse * @param string $format output format * @return null|string */ public function render_text($text, $format = 'xhtml'); /** * Allow the plugin to prevent DokuWiki from reusing an instance * * @return bool false if the plugin has to be instantiated */ public function isSingleton(); } indexer.php 0000644 00000024746 15233462216 0006734 0 ustar 00 <?php /** * Functions to create the fulltext search index * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> * @author Tom N Harris <tnharris@whoopdedo.org> */ use dokuwiki\Extension\Event; use dokuwiki\Search\Indexer; // Version tag used to force rebuild on upgrade define('INDEXER_VERSION', 8); // set the minimum token length to use in the index (note, this doesn't apply to numeric tokens) if (!defined('IDX_MINWORDLENGTH')) define('IDX_MINWORDLENGTH',2); /** * Version of the indexer taking into consideration the external tokenizer. * The indexer is only compatible with data written by the same version. * * @triggers INDEXER_VERSION_GET * Plugins that modify what gets indexed should hook this event and * add their version info to the event data like so: * $data[$plugin_name] = $plugin_version; * * @author Tom N Harris <tnharris@whoopdedo.org> * @author Michael Hamann <michael@content-space.de> * * @return int|string */ function idx_get_version(){ static $indexer_version = null; if ($indexer_version == null) { $version = INDEXER_VERSION; // DokuWiki version is included for the convenience of plugins $data = array('dokuwiki'=>$version); Event::createAndTrigger('INDEXER_VERSION_GET', $data, null, false); unset($data['dokuwiki']); // this needs to be first ksort($data); foreach ($data as $plugin=>$vers) $version .= '+'.$plugin.'='.$vers; $indexer_version = $version; } return $indexer_version; } /** * Measure the length of a string. * Differs from strlen in handling of asian characters. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $w * @return int */ function wordlen($w){ $l = strlen($w); // If left alone, all chinese "words" will get put into w3.idx // So the "length" of a "word" is faked if(preg_match_all('/[\xE2-\xEF]/',$w,$leadbytes)) { foreach($leadbytes[0] as $b) $l += ord($b) - 0xE1; } return $l; } /** * Create an instance of the indexer. * * @return Indexer an Indexer * * @author Tom N Harris <tnharris@whoopdedo.org> */ function idx_get_indexer() { static $Indexer; if (!isset($Indexer)) { $Indexer = new Indexer(); } return $Indexer; } /** * Returns words that will be ignored. * * @return array list of stop words * * @author Tom N Harris <tnharris@whoopdedo.org> */ function & idx_get_stopwords() { static $stopwords = null; if (is_null($stopwords)) { global $conf; $swfile = DOKU_INC.'inc/lang/'.$conf['lang'].'/stopwords.txt'; if(file_exists($swfile)){ $stopwords = file($swfile, FILE_IGNORE_NEW_LINES); }else{ $stopwords = array(); } } return $stopwords; } /** * Adds/updates the search index for the given page * * Locking is handled internally. * * @param string $page name of the page to index * @param boolean $verbose print status messages * @param boolean $force force reindexing even when the index is up to date * @return string|boolean the function completed successfully * * @author Tom N Harris <tnharris@whoopdedo.org> */ function idx_addPage($page, $verbose=false, $force=false) { $idxtag = metaFN($page,'.indexed'); // check if page was deleted but is still in the index if (!page_exists($page)) { if (!file_exists($idxtag)) { if ($verbose) print("Indexer: $page does not exist, ignoring".DOKU_LF); return false; } $Indexer = idx_get_indexer(); $result = $Indexer->deletePage($page); if ($result === "locked") { if ($verbose) print("Indexer: locked".DOKU_LF); return false; } @unlink($idxtag); return $result; } // check if indexing needed if(!$force && file_exists($idxtag)){ if(trim(io_readFile($idxtag)) == idx_get_version()){ $last = @filemtime($idxtag); if($last > @filemtime(wikiFN($page))){ if ($verbose) print("Indexer: index for $page up to date".DOKU_LF); return false; } } } $indexenabled = p_get_metadata($page, 'internal index', METADATA_RENDER_UNLIMITED); if ($indexenabled === false) { $result = false; if (file_exists($idxtag)) { $Indexer = idx_get_indexer(); $result = $Indexer->deletePage($page); if ($result === "locked") { if ($verbose) print("Indexer: locked".DOKU_LF); return false; } @unlink($idxtag); } if ($verbose) print("Indexer: index disabled for $page".DOKU_LF); return $result; } $Indexer = idx_get_indexer(); $pid = $Indexer->getPID($page); if ($pid === false) { if ($verbose) print("Indexer: getting the PID failed for $page".DOKU_LF); return false; } $body = ''; $metadata = array(); $metadata['title'] = p_get_metadata($page, 'title', METADATA_RENDER_UNLIMITED); if (($references = p_get_metadata($page, 'relation references', METADATA_RENDER_UNLIMITED)) !== null) $metadata['relation_references'] = array_keys($references); else $metadata['relation_references'] = array(); if (($media = p_get_metadata($page, 'relation media', METADATA_RENDER_UNLIMITED)) !== null) $metadata['relation_media'] = array_keys($media); else $metadata['relation_media'] = array(); $data = compact('page', 'body', 'metadata', 'pid'); $evt = new Event('INDEXER_PAGE_ADD', $data); if ($evt->advise_before()) $data['body'] = $data['body'] . " " . rawWiki($page); $evt->advise_after(); unset($evt); extract($data); $result = $Indexer->addPageWords($page, $body); if ($result === "locked") { if ($verbose) print("Indexer: locked".DOKU_LF); return false; } if ($result) { $result = $Indexer->addMetaKeys($page, $metadata); if ($result === "locked") { if ($verbose) print("Indexer: locked".DOKU_LF); return false; } } if ($result) io_saveFile(metaFN($page,'.indexed'), idx_get_version()); if ($verbose) { print("Indexer: finished".DOKU_LF); return true; } return $result; } /** * Find tokens in the fulltext index * * Takes an array of words and will return a list of matching * pages for each one. * * Important: No ACL checking is done here! All results are * returned, regardless of permissions * * @param array $words list of words to search for * @return array list of pages found, associated with the search terms */ function idx_lookup(&$words) { $Indexer = idx_get_indexer(); return $Indexer->lookup($words); } /** * Split a string into tokens * * @param string $string * @param bool $wc * * @return array */ function idx_tokenizer($string, $wc=false) { $Indexer = idx_get_indexer(); return $Indexer->tokenizer($string, $wc); } /* For compatibility */ /** * Read the list of words in an index (if it exists). * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $idx * @param string $suffix * @return array */ function idx_getIndex($idx, $suffix) { global $conf; $fn = $conf['indexdir'].'/'.$idx.$suffix.'.idx'; if (!file_exists($fn)) return array(); return file($fn); } /** * 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 */ function idx_listIndexLengths() { global $conf; // testing what we have to do, create a cache file or not. if ($conf['readdircache'] == 0) { $docache = false; } else { clearstatcache(); if (file_exists($conf['indexdir'].'/lengths.idx') && (time() < @filemtime($conf['indexdir'].'/lengths.idx') + $conf['readdircache'])) { if ( ($lengths = @file($conf['indexdir'].'/lengths.idx', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)) !== false ) { $idx = array(); foreach ($lengths as $length) { $idx[] = (int)$length; } return $idx; } } $docache = true; } if ($conf['readdircache'] == 0 || $docache) { $dir = @opendir($conf['indexdir']); if ($dir === false) return array(); $idx = array(); while (($f = readdir($dir)) !== false) { if (substr($f, 0, 1) == 'i' && substr($f, -4) == '.idx') { $i = substr($f, 1, -4); if (is_numeric($i)) $idx[] = (int)$i; } } closedir($dir); sort($idx); // save this in a file if ($docache) { $handle = @fopen($conf['indexdir'].'/lengths.idx', 'w'); @fwrite($handle, implode("\n", $idx)); @fclose($handle); } return $idx; } return array(); } /** * 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 */ function idx_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; } /** * Clean a name of a key for use as a file name. * * Romanizes non-latin characters, then strips away anything that's * not a letter, number, or underscore. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $name * @return string */ function idx_cleanName($name) { $name = \dokuwiki\Utf8\Clean::romanize(trim((string)$name)); $name = preg_replace('#[ \./\\:-]+#', '_', $name); $name = preg_replace('/[^A-Za-z0-9_]/', '', $name); return strtolower($name); } //Setup VIM: ex: et ts=4 : pageutils.php 0000644 00000052232 15233462216 0007262 0 ustar 00 <?php /** * Utilities for handling pagenames * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> * @todo Combine similar functions like {wiki,media,meta}FN() */ use dokuwiki\ChangeLog\MediaChangeLog; use dokuwiki\ChangeLog\PageChangeLog; /** * Fetch the an ID from request * * Uses either standard $_REQUEST variable or extracts it from * the full request URI when userewrite is set to 2 * * For $param='id' $conf['start'] is returned if no id was found. * If the second parameter is true (default) the ID is cleaned. * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $param the $_REQUEST variable name, default 'id' * @param bool $clean if true, ID is cleaned * @return string */ function getID($param='id',$clean=true){ /** @var Input $INPUT */ global $INPUT; global $conf; global $ACT; $id = $INPUT->str($param); //construct page id from request URI if(empty($id) && $conf['userewrite'] == 2){ $request = $INPUT->server->str('REQUEST_URI'); $script = ''; //get the script URL if($conf['basedir']){ $relpath = ''; if($param != 'id') { $relpath = 'lib/exe/'; } $script = $conf['basedir'] . $relpath . \dokuwiki\Utf8\PhpString::basename($INPUT->server->str('SCRIPT_FILENAME')); }elseif($INPUT->server->str('PATH_INFO')){ $request = $INPUT->server->str('PATH_INFO'); }elseif($INPUT->server->str('SCRIPT_NAME')){ $script = $INPUT->server->str('SCRIPT_NAME'); }elseif($INPUT->server->str('DOCUMENT_ROOT') && $INPUT->server->str('SCRIPT_FILENAME')){ $script = preg_replace ('/^'.preg_quote($INPUT->server->str('DOCUMENT_ROOT'),'/').'/','', $INPUT->server->str('SCRIPT_FILENAME')); $script = '/'.$script; } //clean script and request (fixes a windows problem) $script = preg_replace('/\/\/+/','/',$script); $request = preg_replace('/\/\/+/','/',$request); //remove script URL and Querystring to gain the id if(preg_match('/^'.preg_quote($script,'/').'(.*)/',$request, $match)){ $id = preg_replace ('/\?.*/','',$match[1]); } $id = urldecode($id); //strip leading slashes $id = preg_replace('!^/+!','',$id); } // Namespace autolinking from URL if(substr($id,-1) == ':' || ($conf['useslash'] && substr($id,-1) == '/')){ if(page_exists($id.$conf['start'])){ // start page inside namespace $id = $id.$conf['start']; }elseif(page_exists($id.noNS(cleanID($id)))){ // page named like the NS inside the NS $id = $id.noNS(cleanID($id)); }elseif(page_exists($id)){ // page like namespace exists $id = substr($id,0,-1); }else{ // fall back to default $id = $id.$conf['start']; } if (isset($ACT) && $ACT === 'show') { $urlParameters = $_GET; if (isset($urlParameters['id'])) { unset($urlParameters['id']); } send_redirect(wl($id, $urlParameters, true, '&')); } } if($clean) $id = cleanID($id); if($id === '' && $param=='id') $id = $conf['start']; return $id; } /** * Remove unwanted chars from ID * * Cleans a given ID to only use allowed characters. Accented characters are * converted to unaccented ones * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $raw_id The pageid to clean * @param boolean $ascii Force ASCII * @return string cleaned id */ function cleanID($raw_id,$ascii=false){ global $conf; static $sepcharpat = null; global $cache_cleanid; $cache = & $cache_cleanid; // check if it's already in the memory cache if (!$ascii && isset($cache[(string)$raw_id])) { return $cache[(string)$raw_id]; } $sepchar = $conf['sepchar']; if($sepcharpat == null) // build string only once to save clock cycles $sepcharpat = '#\\'.$sepchar.'+#'; $id = trim((string)$raw_id); $id = \dokuwiki\Utf8\PhpString::strtolower($id); //alternative namespace seperator if($conf['useslash']){ $id = strtr($id,';/','::'); }else{ $id = strtr($id,';/',':'.$sepchar); } if($conf['deaccent'] == 2 || $ascii) $id = \dokuwiki\Utf8\Clean::romanize($id); if($conf['deaccent'] || $ascii) $id = \dokuwiki\Utf8\Clean::deaccent($id,-1); //remove specials $id = \dokuwiki\Utf8\Clean::stripspecials($id,$sepchar,'\*'); if($ascii) $id = \dokuwiki\Utf8\Clean::strip($id); //clean up $id = preg_replace($sepcharpat,$sepchar,$id); $id = preg_replace('#:+#',':',$id); $id = trim($id,':._-'); $id = preg_replace('#:[:\._\-]+#',':',$id); $id = preg_replace('#[:\._\-]+:#',':',$id); if (!$ascii) $cache[(string)$raw_id] = $id; return($id); } /** * Return namespacepart of a wiki ID * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $id * @return string|false the namespace part or false if the given ID has no namespace (root) */ function getNS($id){ $pos = strrpos((string)$id,':'); if($pos!==false){ return substr((string)$id,0,$pos); } return false; } /** * Returns the ID without the namespace * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $id * @return string */ function noNS($id) { $pos = strrpos($id, ':'); if ($pos!==false) { return substr($id, $pos+1); } else { return $id; } } /** * Returns the current namespace * * @author Nathan Fritz <fritzn@crown.edu> * * @param string $id * @return string */ function curNS($id) { return noNS(getNS($id)); } /** * Returns the ID without the namespace or current namespace for 'start' pages * * @author Nathan Fritz <fritzn@crown.edu> * * @param string $id * @return string */ function noNSorNS($id) { global $conf; $p = noNS($id); if ($p === $conf['start'] || $p === false || $p === '') { $p = curNS($id); if ($p === false || $p === '') { return $conf['start']; } } return $p; } /** * Creates a XHTML valid linkid from a given headline title * * @param string $title The headline title * @param array|bool $check Existing IDs (title => number) * @return string the title * * @author Andreas Gohr <andi@splitbrain.org> */ function sectionID($title,&$check) { $title = str_replace(array(':','.'),'',cleanID($title)); $new = ltrim($title,'0123456789_-'); if(empty($new)){ $title = 'section'.preg_replace('/[^0-9]+/','',$title); //keep numbers from headline }else{ $title = $new; } if(is_array($check)){ // make sure tiles are unique if (!array_key_exists ($title,$check)) { $check[$title] = 0; } else { $title .= ++ $check[$title]; } } return $title; } /** * Wiki page existence check * * parameters as for wikiFN * * @author Chris Smith <chris@jalakai.co.uk> * * @param string $id page id * @param string|int $rev empty or revision timestamp * @param bool $clean flag indicating that $id should be cleaned (see wikiFN as well) * @param bool $date_at * @return bool exists? */ function page_exists($id,$rev='',$clean=true, $date_at=false) { if($rev !== '' && $date_at) { $pagelog = new PageChangeLog($id); $pagelog_rev = $pagelog->getLastRevisionAt($rev); if($pagelog_rev !== false) $rev = $pagelog_rev; } return file_exists(wikiFN($id,$rev,$clean)); } /** * returns the full path to the datafile specified by ID and optional revision * * The filename is URL encoded to protect Unicode chars * * @param $raw_id string id of wikipage * @param $rev int|string page revision, empty string for current * @param $clean bool flag indicating that $raw_id should be cleaned. Only set to false * when $id is guaranteed to have been cleaned already. * @return string full path * * @author Andreas Gohr <andi@splitbrain.org> */ function wikiFN($raw_id,$rev='',$clean=true){ global $conf; global $cache_wikifn; $cache = & $cache_wikifn; $id = $raw_id; if ($clean) $id = cleanID($id); $id = str_replace(':','/',$id); if (isset($cache[$id]) && isset($cache[$id][$rev])) { return $cache[$id][$rev]; } if(empty($rev)){ $fn = $conf['datadir'].'/'.utf8_encodeFN($id).'.txt'; }else{ $fn = $conf['olddir'].'/'.utf8_encodeFN($id).'.'.$rev.'.txt'; if($conf['compression']){ //test for extensions here, we want to read both compressions if (file_exists($fn . '.gz')){ $fn .= '.gz'; }else if(file_exists($fn . '.bz2')){ $fn .= '.bz2'; }else{ //file doesnt exist yet, so we take the configured extension $fn .= '.' . $conf['compression']; } } } if (!isset($cache[$id])) { $cache[$id] = array(); } $cache[$id][$rev] = $fn; return $fn; } /** * Returns the full path to the file for locking the page while editing. * * @author Ben Coburn <btcoburn@silicodon.net> * * @param string $id page id * @return string full path */ function wikiLockFN($id) { global $conf; return $conf['lockdir'].'/'.md5(cleanID($id)).'.lock'; } /** * returns the full path to the meta file specified by ID and extension * * @author Steven Danz <steven-danz@kc.rr.com> * * @param string $id page id * @param string $ext file extension * @return string full path */ function metaFN($id,$ext){ global $conf; $id = cleanID($id); $id = str_replace(':','/',$id); $fn = $conf['metadir'].'/'.utf8_encodeFN($id).$ext; return $fn; } /** * returns the full path to the media's meta file specified by ID and extension * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $id media id * @param string $ext extension of media * @return string */ function mediaMetaFN($id,$ext){ global $conf; $id = cleanID($id); $id = str_replace(':','/',$id); $fn = $conf['mediametadir'].'/'.utf8_encodeFN($id).$ext; return $fn; } /** * returns an array of full paths to all metafiles of a given ID * * @author Esther Brunner <esther@kaffeehaus.ch> * @author Michael Hamann <michael@content-space.de> * * @param string $id page id * @return array */ function metaFiles($id){ $basename = metaFN($id, ''); $files = glob($basename.'.*', GLOB_MARK); // filter files like foo.bar.meta when $id == 'foo' return $files ? preg_grep('/^'.preg_quote($basename, '/').'\.[^.\/]*$/u', $files) : array(); } /** * returns the full path to the mediafile specified by ID * * The filename is URL encoded to protect Unicode chars * * @author Andreas Gohr <andi@splitbrain.org> * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $id media id * @param string|int $rev empty string or revision timestamp * @param bool $clean * * @return string full path */ function mediaFN($id, $rev='', $clean=true){ global $conf; if ($clean) $id = cleanID($id); $id = str_replace(':','/',$id); if(empty($rev)){ $fn = $conf['mediadir'].'/'.utf8_encodeFN($id); }else{ $ext = mimetype($id); $name = substr($id,0, -1*strlen($ext[0])-1); $fn = $conf['mediaolddir'].'/'.utf8_encodeFN($name .'.'.( (int) $rev ).'.'.$ext[0]); } return $fn; } /** * Returns the full filepath to a localized file if local * version isn't found the english one is returned * * @param string $id The id of the local file * @param string $ext The file extension (usually txt) * @return string full filepath to localized file * * @author Andreas Gohr <andi@splitbrain.org> */ function localeFN($id,$ext='txt'){ global $conf; $file = DOKU_CONF.'lang/'.$conf['lang'].'/'.$id.'.'.$ext; if(!file_exists($file)){ $file = DOKU_INC.'inc/lang/'.$conf['lang'].'/'.$id.'.'.$ext; if(!file_exists($file)){ //fall back to english $file = DOKU_INC.'inc/lang/en/'.$id.'.'.$ext; } } return $file; } /** * Resolve relative paths in IDs * * Do not call directly use resolve_mediaid or resolve_pageid * instead * * Partyly based on a cleanPath function found at * http://php.net/manual/en/function.realpath.php#57016 * * @author <bart at mediawave dot nl> * * @param string $ns namespace which is context of id * @param string $id relative id * @param bool $clean flag indicating that id should be cleaned * @return string */ function resolve_id($ns,$id,$clean=true){ global $conf; // some pre cleaning for useslash: if($conf['useslash']) $id = str_replace('/',':',$id); // if the id starts with a dot we need to handle the // relative stuff if($id && $id[0] == '.'){ // normalize initial dots without a colon $id = preg_replace('/^((\.+:)*)(\.+)(?=[^:\.])/','\1\3:',$id); // prepend the current namespace $id = $ns.':'.$id; // cleanup relatives $result = array(); $pathA = explode(':', $id); if (!$pathA[0]) $result[] = ''; foreach ($pathA AS $key => $dir) { if ($dir == '..') { if (end($result) == '..') { $result[] = '..'; } elseif (!array_pop($result)) { $result[] = '..'; } } elseif ($dir && $dir != '.') { $result[] = $dir; } } if (!end($pathA)) $result[] = ''; $id = implode(':', $result); }elseif($ns !== false && strpos($id,':') === false){ //if link contains no namespace. add current namespace (if any) $id = $ns.':'.$id; } if($clean) $id = cleanID($id); return $id; } /** * Returns a full media id * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $ns namespace which is context of id * @param string &$page (reference) relative media id, updated to resolved id * @param bool &$exists (reference) updated with existance of media * @param int|string $rev * @param bool $date_at */ function resolve_mediaid($ns,&$page,&$exists,$rev='',$date_at=false){ $page = resolve_id($ns,$page); if($rev !== '' && $date_at){ $medialog = new MediaChangeLog($page); $medialog_rev = $medialog->getLastRevisionAt($rev); if($medialog_rev !== false) { $rev = $medialog_rev; } } $file = mediaFN($page,$rev); $exists = file_exists($file); } /** * Returns a full page id * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $ns namespace which is context of id * @param string &$page (reference) relative page id, updated to resolved id * @param bool &$exists (reference) updated with existance of media * @param string $rev * @param bool $date_at */ function resolve_pageid($ns,&$page,&$exists,$rev='',$date_at=false ){ global $conf; global $ID; $exists = false; //empty address should point to current page if ($page === "") { $page = $ID; } //keep hashlink if exists then clean both parts if (strpos($page,'#')) { list($page,$hash) = explode('#',$page,2); } else { $hash = ''; } $hash = cleanID($hash); $page = resolve_id($ns,$page,false); // resolve but don't clean, yet // get filename (calls clean itself) if($rev !== '' && $date_at) { $pagelog = new PageChangeLog($page); $pagelog_rev = $pagelog->getLastRevisionAt($rev); if($pagelog_rev !== false)//something found $rev = $pagelog_rev; } $file = wikiFN($page,$rev); // if ends with colon or slash we have a namespace link if(in_array(substr($page,-1), array(':', ';')) || ($conf['useslash'] && substr($page,-1) == '/')){ if(page_exists($page.$conf['start'],$rev,true,$date_at)){ // start page inside namespace $page = $page.$conf['start']; $exists = true; }elseif(page_exists($page.noNS(cleanID($page)),$rev,true,$date_at)){ // page named like the NS inside the NS $page = $page.noNS(cleanID($page)); $exists = true; }elseif(page_exists($page,$rev,true,$date_at)){ // page like namespace exists $page = $page; $exists = true; }else{ // fall back to default $page = $page.$conf['start']; } }else{ //check alternative plural/nonplural form if(!file_exists($file)){ if( $conf['autoplural'] ){ if(substr($page,-1) == 's'){ $try = substr($page,0,-1); }else{ $try = $page.'s'; } if(page_exists($try,$rev,true,$date_at)){ $page = $try; $exists = true; } } }else{ $exists = true; } } // now make sure we have a clean page $page = cleanID($page); //add hash if any if(!empty($hash)) $page .= '#'.$hash; } /** * Returns the name of a cachefile from given data * * The needed directory is created by this function! * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $data This data is used to create a unique md5 name * @param string $ext This is appended to the filename if given * @return string The filename of the cachefile */ function getCacheName($data,$ext=''){ global $conf; $md5 = md5($data); $file = $conf['cachedir'].'/'.$md5[0].'/'.$md5.$ext; io_makeFileDir($file); return $file; } /** * Checks a pageid against $conf['hidepages'] * * @author Andreas Gohr <gohr@cosmocode.de> * * @param string $id page id * @return bool */ function isHiddenPage($id){ $data = array( 'id' => $id, 'hidden' => false ); \dokuwiki\Extension\Event::createAndTrigger('PAGEUTILS_ID_HIDEPAGE', $data, '_isHiddenPage'); return $data['hidden']; } /** * callback checks if page is hidden * * @param array $data event data - see isHiddenPage() */ function _isHiddenPage(&$data) { global $conf; global $ACT; if ($data['hidden']) return; if(empty($conf['hidepages'])) return; if($ACT == 'admin') return; if(preg_match('/'.$conf['hidepages'].'/ui',':'.$data['id'])){ $data['hidden'] = true; } } /** * Reverse of isHiddenPage * * @author Andreas Gohr <gohr@cosmocode.de> * * @param string $id page id * @return bool */ function isVisiblePage($id){ return !isHiddenPage($id); } /** * Format an id for output to a user * * Namespaces are denoted by a trailing “:*”. The root namespace is * “*”. Output is escaped. * * @author Adrian Lang <lang@cosmocode.de> * * @param string $id page id * @return string */ function prettyprint_id($id) { if (!$id || $id === ':') { return '*'; } if ((substr($id, -1, 1) === ':')) { $id .= '*'; } return hsc($id); } /** * Encode a UTF-8 filename to use on any filesystem * * Uses the 'fnencode' option to determine encoding * * When the second parameter is true the string will * be encoded only if non ASCII characters are detected - * This makes it safe to run it multiple times on the * same string (default is true) * * @author Andreas Gohr <andi@splitbrain.org> * @see urlencode * * @param string $file file name * @param bool $safe if true, only encoded when non ASCII characters detected * @return string */ function utf8_encodeFN($file,$safe=true){ global $conf; if($conf['fnencode'] == 'utf-8') return $file; if($safe && preg_match('#^[a-zA-Z0-9/_\-\.%]+$#',$file)){ return $file; } if($conf['fnencode'] == 'safe'){ return SafeFN::encode($file); } $file = urlencode($file); $file = str_replace('%2F','/',$file); return $file; } /** * Decode a filename back to UTF-8 * * Uses the 'fnencode' option to determine encoding * * @author Andreas Gohr <andi@splitbrain.org> * @see urldecode * * @param string $file file name * @return string */ function utf8_decodeFN($file){ global $conf; if($conf['fnencode'] == 'utf-8') return $file; if($conf['fnencode'] == 'safe'){ return SafeFN::decode($file); } return urldecode($file); } /** * Find a page in the current namespace (determined from $ID) or any * higher namespace that can be accessed by the current user, * this condition can be overriden by an optional parameter. * * Used for sidebars, but can be used other stuff as well * * @todo add event hook * * @param string $page the pagename you're looking for * @param bool $useacl only return pages readable by the current user, false to ignore ACLs * @return false|string the full page id of the found page, false if any */ function page_findnearest($page, $useacl = true){ if ((string) $page === '') return false; global $ID; $ns = $ID; do { $ns = getNS($ns); $pageid = cleanID("$ns:$page"); if(page_exists($pageid) && (!$useacl || auth_quickaclcheck($pageid) >= AUTH_READ)){ return $pageid; } } while($ns !== false); return false; } media.php 0000644 00000226634 15233462216 0006355 0 ustar 00 <?php /** * All output and handler function needed for the media management popup * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ use dokuwiki\ChangeLog\MediaChangeLog; use dokuwiki\HTTP\DokuHTTPClient; use dokuwiki\Subscriptions\MediaSubscriptionSender; use dokuwiki\Extension\Event; /** * Lists pages which currently use a media file selected for deletion * * References uses the same visual as search results and share * their CSS tags except pagenames won't be links. * * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> * * @param array $data * @param string $id */ function media_filesinuse($data,$id){ global $lang; echo '<h1>'.$lang['reference'].' <code>'.hsc(noNS($id)).'</code></h1>'; echo '<p>'.hsc($lang['ref_inuse']).'</p>'; $hidden=0; //count of hits without read permission foreach($data as $row){ if(auth_quickaclcheck($row) >= AUTH_READ && isVisiblePage($row)){ echo '<div class="search_result">'; echo '<span class="mediaref_ref">'.hsc($row).'</span>'; echo '</div>'; }else $hidden++; } if ($hidden){ print '<div class="mediaref_hidden">'.$lang['ref_hidden'].'</div>'; } } /** * Handles the saving of image meta data * * @author Andreas Gohr <andi@splitbrain.org> * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $id media id * @param int $auth permission level * @param array $data * @return false|string */ function media_metasave($id,$auth,$data){ if($auth < AUTH_UPLOAD) return false; if(!checkSecurityToken()) return false; global $lang; global $conf; $src = mediaFN($id); $meta = new JpegMeta($src); $meta->_parseAll(); foreach($data as $key => $val){ $val=trim($val); if(empty($val)){ $meta->deleteField($key); }else{ $meta->setField($key,$val); } } $old = @filemtime($src); if(!file_exists(mediaFN($id, $old)) && file_exists($src)) { // add old revision to the attic media_saveOldRevision($id); } $filesize_old = filesize($src); if($meta->save()){ if($conf['fperm']) chmod($src, $conf['fperm']); @clearstatcache(true, $src); $new = @filemtime($src); $filesize_new = filesize($src); $sizechange = $filesize_new - $filesize_old; // add a log entry to the media changelog addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, $lang['media_meta_edited'], '', null, $sizechange); msg($lang['metasaveok'],1); return $id; }else{ msg($lang['metasaveerr'],-1); return false; } } /** * check if a media is external source * * @author Gerrit Uitslag <klapinklapin@gmail.com> * * @param string $id the media ID or URL * @return bool */ function media_isexternal($id){ if (preg_match('#^(?:https?|ftp)://#i', $id)) return true; return false; } /** * Check if a media item is public (eg, external URL or readable by @ALL) * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $id the media ID or URL * @return bool */ function media_ispublic($id){ if(media_isexternal($id)) return true; $id = cleanID($id); if(auth_aclcheck(getNS($id).':*', '', array()) >= AUTH_READ) return true; return false; } /** * Display the form to edit image meta data * * @author Andreas Gohr <andi@splitbrain.org> * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $id media id * @param int $auth permission level * @return bool */ function media_metaform($id,$auth){ global $lang; if($auth < AUTH_UPLOAD) { echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.NL; return false; } // load the field descriptions static $fields = null; if(is_null($fields)){ $config_files = getConfigFiles('mediameta'); foreach ($config_files as $config_file) { if(file_exists($config_file)) include($config_file); } } $src = mediaFN($id); // output $form = new Doku_Form(array('action' => media_managerURL(array('tab_details' => 'view'), '&'), 'class' => 'meta')); $form->addHidden('img', $id); $form->addHidden('mediado', 'save'); foreach($fields as $key => $field){ // get current value if (empty($field[0])) continue; $tags = array($field[0]); if(is_array($field[3])) $tags = array_merge($tags,$field[3]); $value = tpl_img_getTag($tags,'',$src); $value = cleanText($value); // prepare attributes $p = array(); $p['class'] = 'edit'; $p['id'] = 'meta__'.$key; $p['name'] = 'meta['.$field[0].']'; $p_attrs = array('class' => 'edit'); $form->addElement('<div class="row">'); if($field[2] == 'text'){ $form->addElement( form_makeField( 'text', $p['name'], $value, ($lang[$field[1]]) ? $lang[$field[1]] : $field[1] . ':', $p['id'], $p['class'], $p_attrs ) ); }else{ $att = buildAttributes($p); $form->addElement('<label for="meta__'.$key.'">'.$lang[$field[1]].'</label>'); $form->addElement("<textarea $att rows=\"6\" cols=\"50\">".formText($value).'</textarea>'); } $form->addElement('</div>'.NL); } $form->addElement('<div class="buttons">'); $form->addElement( form_makeButton( 'submit', '', $lang['btn_save'], array('accesskey' => 's', 'name' => 'mediado[save]') ) ); $form->addElement('</div>'.NL); $form->printForm(); return true; } /** * Convenience function to check if a media file is still in use * * @author Michael Klier <chi@chimeric.de> * * @param string $id media id * @return array|bool */ function media_inuse($id) { global $conf; if($conf['refcheck']){ $mediareferences = ft_mediause($id,true); if(!count($mediareferences)) { return false; } else { return $mediareferences; } } else { return false; } } /** * Handles media file deletions * * If configured, checks for media references before deletion * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $id media id * @param int $auth no longer used * @return int One of: 0, * DOKU_MEDIA_DELETED, * DOKU_MEDIA_DELETED | DOKU_MEDIA_EMPTY_NS, * DOKU_MEDIA_NOT_AUTH, * DOKU_MEDIA_INUSE */ function media_delete($id,$auth){ global $lang; $auth = auth_quickaclcheck(ltrim(getNS($id).':*', ':')); if($auth < AUTH_DELETE) return DOKU_MEDIA_NOT_AUTH; if(media_inuse($id)) return DOKU_MEDIA_INUSE; $file = mediaFN($id); // trigger an event - MEDIA_DELETE_FILE $data = array(); $data['id'] = $id; $data['name'] = \dokuwiki\Utf8\PhpString::basename($file); $data['path'] = $file; $data['size'] = (file_exists($file)) ? filesize($file) : 0; $data['unl'] = false; $data['del'] = false; $evt = new Event('MEDIA_DELETE_FILE',$data); if ($evt->advise_before()) { $old = @filemtime($file); if(!file_exists(mediaFN($id, $old)) && file_exists($file)) { // add old revision to the attic media_saveOldRevision($id); } $data['unl'] = @unlink($file); if($data['unl']) { $sizechange = 0 - $data['size']; addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_DELETE, $lang['deleted'], '', null, $sizechange); $data['del'] = io_sweepNS($id, 'mediadir'); } } $evt->advise_after(); unset($evt); if($data['unl'] && $data['del']){ return DOKU_MEDIA_DELETED | DOKU_MEDIA_EMPTY_NS; } return $data['unl'] ? DOKU_MEDIA_DELETED : 0; } /** * Handle file uploads via XMLHttpRequest * * @param string $ns target namespace * @param int $auth current auth check result * @return false|string false on error, id of the new file on success */ function media_upload_xhr($ns,$auth){ if(!checkSecurityToken()) return false; global $INPUT; $id = $INPUT->get->str('qqfile'); list($ext,$mime) = mimetype($id); $input = fopen("php://input", "r"); if (!($tmp = io_mktmpdir())) return false; $path = $tmp.'/'.md5($id); $target = fopen($path, "w"); $realSize = stream_copy_to_stream($input, $target); fclose($target); fclose($input); if (isset($_SERVER["CONTENT_LENGTH"]) && ($realSize != (int)$_SERVER["CONTENT_LENGTH"])){ unlink($path); return false; } $res = media_save( array('name' => $path, 'mime' => $mime, 'ext' => $ext), $ns.':'.$id, (($INPUT->get->str('ow') == 'true') ? true : false), $auth, 'copy' ); unlink($path); if ($tmp) io_rmdir($tmp, true); if (is_array($res)) { msg($res[0], $res[1]); return false; } return $res; } /** * Handles media file uploads * * @author Andreas Gohr <andi@splitbrain.org> * @author Michael Klier <chi@chimeric.de> * * @param string $ns target namespace * @param int $auth current auth check result * @param bool|array $file $_FILES member, $_FILES['upload'] if false * @return false|string false on error, id of the new file on success */ function media_upload($ns,$auth,$file=false){ if(!checkSecurityToken()) return false; global $lang; global $INPUT; // get file and id $id = $INPUT->post->str('mediaid'); if (!$file) $file = $_FILES['upload']; if(empty($id)) $id = $file['name']; // check for errors (messages are done in lib/exe/mediamanager.php) if($file['error']) return false; // check extensions list($fext,$fmime) = mimetype($file['name']); list($iext,$imime) = mimetype($id); if($fext && !$iext){ // no extension specified in id - read original one $id .= '.'.$fext; $imime = $fmime; }elseif($fext && $fext != $iext){ // extension was changed, print warning msg(sprintf($lang['mediaextchange'],$fext,$iext)); } $res = media_save(array('name' => $file['tmp_name'], 'mime' => $imime, 'ext' => $iext), $ns.':'.$id, $INPUT->post->bool('ow'), $auth, 'copy_uploaded_file'); if (is_array($res)) { msg($res[0], $res[1]); return false; } return $res; } /** * An alternative to move_uploaded_file that copies * * Using copy, makes sure any setgid bits on the media directory are honored * * @see move_uploaded_file() * * @param string $from * @param string $to * @return bool */ function copy_uploaded_file($from, $to){ if(!is_uploaded_file($from)) return false; $ok = copy($from, $to); @unlink($from); return $ok; } /** * This generates an action event and delegates to _media_upload_action(). * Action plugins are allowed to pre/postprocess the uploaded file. * (The triggered event is preventable.) * * Event data: * $data[0] fn_tmp: the temporary file name (read from $_FILES) * $data[1] fn: the file name of the uploaded file * $data[2] id: the future directory id of the uploaded file * $data[3] imime: the mimetype of the uploaded file * $data[4] overwrite: if an existing file is going to be overwritten * $data[5] move: name of function that performs move/copy/.. * * @triggers MEDIA_UPLOAD_FINISH * * @param array $file * @param string $id media id * @param bool $ow overwrite? * @param int $auth permission level * @param string $move name of functions that performs move/copy/.. * @return false|array|string */ function media_save($file, $id, $ow, $auth, $move) { if($auth < AUTH_UPLOAD) { return array("You don't have permissions to upload files.", -1); } if (!isset($file['mime']) || !isset($file['ext'])) { list($ext, $mime) = mimetype($id); if (!isset($file['mime'])) { $file['mime'] = $mime; } if (!isset($file['ext'])) { $file['ext'] = $ext; } } global $lang, $conf; // get filename $id = cleanID($id); $fn = mediaFN($id); // get filetype regexp $types = array_keys(getMimeTypes()); $types = array_map( function ($q) { return preg_quote($q, "/"); }, $types ); $regex = join('|',$types); // because a temp file was created already if(!preg_match('/\.('.$regex.')$/i',$fn)) { return array($lang['uploadwrong'],-1); } //check for overwrite $overwrite = file_exists($fn); $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE); if($overwrite && (!$ow || $auth < $auth_ow)) { return array($lang['uploadexist'], 0); } // check for valid content $ok = media_contentcheck($file['name'], $file['mime']); if($ok == -1){ return array(sprintf($lang['uploadbadcontent'],'.' . $file['ext']),-1); }elseif($ok == -2){ return array($lang['uploadspam'],-1); }elseif($ok == -3){ return array($lang['uploadxss'],-1); } // prepare event data $data = array(); $data[0] = $file['name']; $data[1] = $fn; $data[2] = $id; $data[3] = $file['mime']; $data[4] = $overwrite; $data[5] = $move; // trigger event return Event::createAndTrigger('MEDIA_UPLOAD_FINISH', $data, '_media_upload_action', true); } /** * Callback adapter for media_upload_finish() triggered by MEDIA_UPLOAD_FINISH * * @author Michael Klier <chi@chimeric.de> * * @param array $data event data * @return false|array|string */ function _media_upload_action($data) { // fixme do further sanity tests of given data? if(is_array($data) && count($data)===6) { return media_upload_finish($data[0], $data[1], $data[2], $data[3], $data[4], $data[5]); } else { return false; //callback error } } /** * Saves an uploaded media file * * @author Andreas Gohr <andi@splitbrain.org> * @author Michael Klier <chi@chimeric.de> * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $fn_tmp * @param string $fn * @param string $id media id * @param string $imime mime type * @param bool $overwrite overwrite existing? * @param string $move function name * @return array|string */ function media_upload_finish($fn_tmp, $fn, $id, $imime, $overwrite, $move = 'move_uploaded_file') { global $conf; global $lang; global $REV; $old = @filemtime($fn); if(!file_exists(mediaFN($id, $old)) && file_exists($fn)) { // add old revision to the attic if missing media_saveOldRevision($id); } // prepare directory io_createNamespace($id, 'media'); $filesize_old = file_exists($fn) ? filesize($fn) : 0; if($move($fn_tmp, $fn)) { @clearstatcache(true,$fn); $new = @filemtime($fn); // Set the correct permission here. // Always chmod media because they may be saved with different permissions than expected from the php umask. // (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,$new); // add a log entry to the media changelog $filesize_new = filesize($fn); $sizechange = $filesize_new - $filesize_old; if($REV) { addMediaLogEntry( $new, $id, DOKU_CHANGE_TYPE_REVERT, sprintf($lang['restored'], dformat($REV)), $REV, null, $sizechange ); } elseif($overwrite) { addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange); } else { addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created'], '', null, $sizechange); } return $id; }else{ return array($lang['uploadfail'],-1); } } /** * Moves the current version of media file to the media_attic * directory * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $id * @return int - revision date */ function media_saveOldRevision($id){ global $conf, $lang; $oldf = mediaFN($id); if(!file_exists($oldf)) return ''; $date = filemtime($oldf); if (!$conf['mediarevisions']) return $date; $medialog = new MediaChangeLog($id); if (!$medialog->getRevisionInfo($date)) { // there was an external edit, // there is no log entry for current version of file $sizechange = filesize($oldf); if(!file_exists(mediaMetaFN($id, '.changes'))) { addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created'], '', null, $sizechange); } else { $oldRev = $medialog->getRevisions(-1, 1); // from changelog $oldRev = (int) (empty($oldRev) ? 0 : $oldRev[0]); $filesize_old = filesize(mediaFN($id, $oldRev)); $sizechange = $sizechange - $filesize_old; addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange); } } $newf = mediaFN($id,$date); io_makeFileDir($newf); if(copy($oldf, $newf)) { // Set the correct permission here. // Always chmod media because they may be saved with different permissions than expected from the php umask. // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.) chmod($newf, $conf['fmode']); } return $date; } /** * This function checks if the uploaded content is really what the * mimetype says it is. We also do spam checking for text types here. * * We need to do this stuff because we can not rely on the browser * to do this check correctly. Yes, IE is broken as usual. * * @author Andreas Gohr <andi@splitbrain.org> * @link http://www.splitbrain.org/blog/2007-02/12-internet_explorer_facilitates_cross_site_scripting * @fixme check all 26 magic IE filetypes here? * * @param string $file path to file * @param string $mime mimetype * @return int */ function media_contentcheck($file,$mime){ global $conf; if($conf['iexssprotect']){ $fh = @fopen($file, 'rb'); if($fh){ $bytes = fread($fh, 256); fclose($fh); if(preg_match('/<(script|a|img|html|body|iframe)[\s>]/i',$bytes)){ return -3; //XSS: possibly malicious content } } } if(substr($mime,0,6) == 'image/'){ $info = @getimagesize($file); if($mime == 'image/gif' && $info[2] != 1){ return -1; // uploaded content did not match the file extension }elseif($mime == 'image/jpeg' && $info[2] != 2){ return -1; }elseif($mime == 'image/png' && $info[2] != 3){ return -1; } # fixme maybe check other images types as well }elseif(substr($mime,0,5) == 'text/'){ global $TEXT; $TEXT = io_readFile($file); if(checkwordblock()){ return -2; //blocked by the spam blacklist } } return 0; } /** * Send a notify mail on uploads * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $id media id * @param string $file path to file * @param string $mime mime type * @param bool|int $old_rev revision timestamp or false * @return bool */ 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, $current_rev); } /** * List all files in a given Media namespace * * @param string $ns namespace * @param null|int $auth permission level * @param string $jump id * @param bool $fullscreenview * @param bool|string $sort sorting order, false skips sorting */ function media_filelist($ns,$auth=null,$jump='',$fullscreenview=false,$sort=false){ global $conf; global $lang; $ns = cleanID($ns); // check auth our self if not given (needed for ajax calls) if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); if (!$fullscreenview) echo '<h1 id="media__ns">:'.hsc($ns).'</h1>'.NL; if($auth < AUTH_READ){ // FIXME: print permission warning here instead? echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL; }else{ if (!$fullscreenview) { media_uploadform($ns, $auth); media_searchform($ns); } $dir = utf8_encodeFN(str_replace(':','/',$ns)); $data = array(); search($data,$conf['mediadir'],'search_media', array('showmsg'=>true,'depth'=>1),$dir,1,$sort); if(!count($data)){ echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL; }else { if ($fullscreenview) { echo '<ul class="' . _media_get_list_type() . '">'; } foreach($data as $item){ if (!$fullscreenview) { media_printfile($item,$auth,$jump); } else { media_printfile_thumbs($item,$auth,$jump); } } if ($fullscreenview) echo '</ul>'.NL; } } } /** * Prints tabs for files list actions * * @author Kate Arzamastseva <pshns@ukr.net> * @author Adrian Lang <mail@adrianlang.de> * * @param string $selected_tab - opened tab */ function media_tabs_files($selected_tab = ''){ global $lang; $tabs = array(); foreach(array('files' => 'mediaselect', 'upload' => 'media_uploadtab', 'search' => 'media_searchtab') as $tab => $caption) { $tabs[$tab] = array('href' => media_managerURL(array('tab_files' => $tab), '&'), 'caption' => $lang[$caption]); } html_tabs($tabs, $selected_tab); } /** * Prints tabs for files details actions * * @author Kate Arzamastseva <pshns@ukr.net> * @param string $image filename of the current image * @param string $selected_tab opened tab */ function media_tabs_details($image, $selected_tab = ''){ global $lang, $conf; $tabs = array(); $tabs['view'] = array('href' => media_managerURL(array('tab_details' => 'view'), '&'), 'caption' => $lang['media_viewtab']); list(, $mime) = mimetype($image); if ($mime == 'image/jpeg' && file_exists(mediaFN($image))) { $tabs['edit'] = array('href' => media_managerURL(array('tab_details' => 'edit'), '&'), 'caption' => $lang['media_edittab']); } if ($conf['mediarevisions']) { $tabs['history'] = array('href' => media_managerURL(array('tab_details' => 'history'), '&'), 'caption' => $lang['media_historytab']); } html_tabs($tabs, $selected_tab); } /** * Prints options for the tab that displays a list of all files * * @author Kate Arzamastseva <pshns@ukr.net> */ function media_tab_files_options(){ global $lang; global $INPUT; global $ID; $form = new Doku_Form(array('class' => 'options', 'method' => 'get', 'action' => wl($ID))); $media_manager_params = media_managerURL(array(), '', false, true); foreach($media_manager_params as $pKey => $pVal){ $form->addHidden($pKey, $pVal); } $form->addHidden('sectok', null); if ($INPUT->has('q')) { $form->addHidden('q', $INPUT->str('q')); } $form->addElement('<ul>'.NL); foreach(array('list' => array('listType', array('thumbs', 'rows')), 'sort' => array('sortBy', array('name', 'date'))) as $group => $content) { $checked = "_media_get_${group}_type"; $checked = $checked(); $form->addElement('<li class="' . $content[0] . '">'); foreach($content[1] as $option) { $attrs = array(); if ($checked == $option) { $attrs['checked'] = 'checked'; } $form->addElement(form_makeRadioField($group . '_dwmedia', $option, $lang['media_' . $group . '_' . $option], $content[0] . '__' . $option, $option, $attrs)); } $form->addElement('</li>'.NL); } $form->addElement('<li>'); $form->addElement(form_makeButton('submit', '', $lang['btn_apply'])); $form->addElement('</li>'.NL); $form->addElement('</ul>'.NL); $form->printForm(); } /** * Returns type of sorting for the list of files in media manager * * @author Kate Arzamastseva <pshns@ukr.net> * * @return string - sort type */ function _media_get_sort_type() { return _media_get_display_param('sort', array('default' => 'name', 'date')); } /** * Returns type of listing for the list of files in media manager * * @author Kate Arzamastseva <pshns@ukr.net> * * @return string - list type */ function _media_get_list_type() { return _media_get_display_param('list', array('default' => 'thumbs', 'rows')); } /** * Get display parameters * * @param string $param name of parameter * @param array $values allowed values, where default value has index key 'default' * @return string the parameter value */ function _media_get_display_param($param, $values) { global $INPUT; if (in_array($INPUT->str($param), $values)) { // FIXME: Set cookie return $INPUT->str($param); } else { $val = get_doku_pref($param, $values['default']); if (!in_array($val, $values)) { $val = $values['default']; } return $val; } } /** * Prints tab that displays a list of all files * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $ns * @param null|int $auth permission level * @param string $jump item id */ function media_tab_files($ns,$auth=null,$jump='') { global $lang; if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); if($auth < AUTH_READ){ echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL; }else{ media_filelist($ns,$auth,$jump,true,_media_get_sort_type()); } } /** * Prints tab that displays uploading form * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $ns * @param null|int $auth permission level * @param string $jump item id */ function media_tab_upload($ns,$auth=null,$jump='') { global $lang; if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); echo '<div class="upload">'.NL; if ($auth >= AUTH_UPLOAD) { echo '<p>' . $lang['mediaupload'] . '</p>'; } media_uploadform($ns, $auth, true); echo '</div>'.NL; } /** * Prints tab that displays search form * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $ns * @param null|int $auth permission level */ function media_tab_search($ns,$auth=null) { global $INPUT; $do = $INPUT->str('mediado'); $query = $INPUT->str('q'); echo '<div class="search">'.NL; media_searchform($ns, $query, true); if ($do == 'searchlist' || $query) { media_searchlist($query,$ns,$auth,true,_media_get_sort_type()); } echo '</div>'.NL; } /** * Prints tab that displays mediafile details * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $image media id * @param string $ns * @param null|int $auth permission level * @param string|int $rev revision timestamp or empty string */ function media_tab_view($image, $ns, $auth=null, $rev='') { global $lang; if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); if ($image && $auth >= AUTH_READ) { $meta = new JpegMeta(mediaFN($image, $rev)); media_preview($image, $auth, $rev, $meta); media_preview_buttons($image, $auth, $rev); media_details($image, $auth, $rev, $meta); } else { echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL; } } /** * Prints tab that displays form for editing mediafile metadata * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $image media id * @param string $ns * @param null|int $auth permission level */ function media_tab_edit($image, $ns, $auth=null) { if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); if ($image) { list(, $mime) = mimetype($image); if ($mime == 'image/jpeg') media_metaform($image,$auth); } } /** * Prints tab that displays mediafile revisions * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $image media id * @param string $ns * @param null|int $auth permission level */ function media_tab_history($image, $ns, $auth=null) { global $lang; global $INPUT; if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*"); $do = $INPUT->str('mediado'); if ($auth >= AUTH_READ && $image) { if ($do == 'diff'){ media_diff($image, $ns, $auth); } else { $first = $INPUT->int('first'); html_revisions($first, $image); } } else { echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL; } } /** * Prints mediafile details * * @param string $image media id * @param int $auth permission level * @param int|string $rev revision timestamp or empty string * @param JpegMeta|bool $meta * * @author Kate Arzamastseva <pshns@ukr.net> */ function media_preview($image, $auth, $rev='', $meta=false) { $size = media_image_preview_size($image, $rev, $meta); if ($size) { global $lang; echo '<div class="image">'; $more = array(); if ($rev) { $more['rev'] = $rev; } else { $t = @filemtime(mediaFN($image)); $more['t'] = $t; } $more['w'] = $size[0]; $more['h'] = $size[1]; $src = ml($image, $more); echo '<a href="'.$src.'" target="_blank" title="'.$lang['mediaview'].'">'; echo '<img src="'.$src.'" alt="" style="max-width: '.$size[0].'px;" />'; echo '</a>'; echo '</div>'.NL; } } /** * Prints mediafile action buttons * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $image media id * @param int $auth permission level * @param string|int $rev revision timestamp, or empty string */ function media_preview_buttons($image, $auth, $rev='') { global $lang, $conf; echo '<ul class="actions">'.NL; if($auth >= AUTH_DELETE && !$rev && file_exists(mediaFN($image))){ // delete button $form = new Doku_Form(array('id' => 'mediamanager__btn_delete', 'action'=>media_managerURL(array('delete' => $image), '&'))); $form->addElement(form_makeButton('submit','',$lang['btn_delete'])); echo '<li>'; $form->printForm(); echo '</li>'.NL; } $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE); if($auth >= $auth_ow && !$rev){ // upload new version button $form = new Doku_Form(array('id' => 'mediamanager__btn_update', 'action'=>media_managerURL(array('image' => $image, 'mediado' => 'update'), '&'))); $form->addElement(form_makeButton('submit','',$lang['media_update'])); echo '<li>'; $form->printForm(); echo '</li>'.NL; } if($auth >= AUTH_UPLOAD && $rev && $conf['mediarevisions'] && file_exists(mediaFN($image, $rev))){ // restore button $form = new Doku_Form(array('id' => 'mediamanager__btn_restore', 'action'=>media_managerURL(array('image' => $image), '&'))); $form->addHidden('mediado','restore'); $form->addHidden('rev',$rev); $form->addElement(form_makeButton('submit','',$lang['media_restore'])); echo '<li>'; $form->printForm(); echo '</li>'.NL; } echo '</ul>'.NL; } /** * Returns image width and height for mediamanager preview panel * * @author Kate Arzamastseva <pshns@ukr.net> * @param string $image * @param int|string $rev * @param JpegMeta|bool $meta * @param int $size * @return array|false */ function media_image_preview_size($image, $rev, $meta, $size = 500) { if (!preg_match("/\.(jpe?g|gif|png)$/", $image) || !file_exists(mediaFN($image, $rev))) return false; $info = getimagesize(mediaFN($image, $rev)); $w = (int) $info[0]; $h = (int) $info[1]; if($meta && ($w > $size || $h > $size)){ $ratio = $meta->getResizeRatio($size, $size); $w = floor($w * $ratio); $h = floor($h * $ratio); } return array($w, $h); } /** * Returns the requested EXIF/IPTC tag from the image meta * * @author Kate Arzamastseva <pshns@ukr.net> * * @param array $tags array with tags, first existing is returned * @param JpegMeta $meta * @param string $alt alternative value * @return string */ function media_getTag($tags,$meta,$alt=''){ if($meta === false) return $alt; $info = $meta->getField($tags); if($info == false) return $alt; return $info; } /** * Returns mediafile tags * * @author Kate Arzamastseva <pshns@ukr.net> * * @param JpegMeta $meta * @return array list of tags of the mediafile */ function media_file_tags($meta) { // load the field descriptions static $fields = null; if(is_null($fields)){ $config_files = getConfigFiles('mediameta'); foreach ($config_files as $config_file) { if(file_exists($config_file)) include($config_file); } } $tags = array(); foreach($fields as $key => $tag){ $t = array(); if (!empty($tag[0])) $t = array($tag[0]); if(isset($tag[3]) && is_array($tag[3])) $t = array_merge($t,$tag[3]); $value = media_getTag($t, $meta); $tags[] = array('tag' => $tag, 'value' => $value); } return $tags; } /** * Prints mediafile tags * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $image image id * @param int $auth permission level * @param string|int $rev revision timestamp, or empty string * @param bool|JpegMeta $meta image object, or create one if false */ function media_details($image, $auth, $rev='', $meta=false) { global $lang; if (!$meta) $meta = new JpegMeta(mediaFN($image, $rev)); $tags = media_file_tags($meta); echo '<dl>'.NL; foreach($tags as $tag){ if ($tag['value']) { $value = cleanText($tag['value']); echo '<dt>'.$lang[$tag['tag'][1]].'</dt><dd>'; if ($tag['tag'][2] == 'date') echo dformat($value); else echo hsc($value); echo '</dd>'.NL; } } echo '</dl>'.NL; echo '<dl>'.NL; echo '<dt>'.$lang['reference'].':</dt>'; $media_usage = ft_mediause($image,true); if(count($media_usage) > 0){ foreach($media_usage as $path){ echo '<dd>'.html_wikilink($path).'</dd>'; } }else{ echo '<dd>'.$lang['nothingfound'].'</dd>'; } echo '</dl>'.NL; } /** * Shows difference between two revisions of file * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $image image id * @param string $ns * @param int $auth permission level * @param bool $fromajax * @return false|null|string */ function media_diff($image, $ns, $auth, $fromajax = false) { global $conf; global $INPUT; if ($auth < AUTH_READ || !$image || !$conf['mediarevisions']) return ''; $rev1 = $INPUT->int('rev'); $rev2 = $INPUT->ref('rev2'); if(is_array($rev2)){ $rev1 = (int) $rev2[0]; $rev2 = (int) $rev2[1]; if(!$rev1){ $rev1 = $rev2; unset($rev2); } }else{ $rev2 = $INPUT->int('rev2'); } if ($rev1 && !file_exists(mediaFN($image, $rev1))) $rev1 = false; if ($rev2 && !file_exists(mediaFN($image, $rev2))) $rev2 = false; if($rev1 && $rev2){ // two specific revisions wanted // make sure order is correct (older on the left) if($rev1 < $rev2){ $l_rev = $rev1; $r_rev = $rev2; }else{ $l_rev = $rev2; $r_rev = $rev1; } }elseif($rev1){ // single revision given, compare to current $r_rev = ''; $l_rev = $rev1; }else{ // no revision was given, compare previous to current $r_rev = ''; $medialog = new MediaChangeLog($image); $revs = $medialog->getRevisions(0, 1); if (file_exists(mediaFN($image, $revs[0]))) { $l_rev = $revs[0]; } else { $l_rev = ''; } } // prepare event data $data = array(); $data[0] = $image; $data[1] = $l_rev; $data[2] = $r_rev; $data[3] = $ns; $data[4] = $auth; $data[5] = $fromajax; // trigger event return Event::createAndTrigger('MEDIA_DIFF', $data, '_media_file_diff', true); } /** * Callback for media file diff * * @param array $data event data * @return false|null */ function _media_file_diff($data) { if(is_array($data) && count($data)===6) { media_file_diff($data[0], $data[1], $data[2], $data[3], $data[4], $data[5]); } else { return false; } } /** * Shows difference between two revisions of image * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $image * @param string|int $l_rev revision timestamp, or empty string * @param string|int $r_rev revision timestamp, or empty string * @param string $ns * @param int $auth permission level * @param bool $fromajax */ function media_file_diff($image, $l_rev, $r_rev, $ns, $auth, $fromajax){ global $lang; global $INPUT; $l_meta = new JpegMeta(mediaFN($image, $l_rev)); $r_meta = new JpegMeta(mediaFN($image, $r_rev)); $is_img = preg_match('/\.(jpe?g|gif|png)$/', $image); if ($is_img) { $l_size = media_image_preview_size($image, $l_rev, $l_meta); $r_size = media_image_preview_size($image, $r_rev, $r_meta); $is_img = ($l_size && $r_size && ($l_size[0] >= 30 || $r_size[0] >= 30)); $difftype = $INPUT->str('difftype'); if (!$fromajax) { $form = new Doku_Form(array( 'action' => media_managerURL(array(), '&'), 'method' => 'get', 'id' => 'mediamanager__form_diffview', 'class' => 'diffView' )); $form->addHidden('sectok', null); $form->addElement('<input type="hidden" name="rev2[]" value="'.$l_rev.'" ></input>'); $form->addElement('<input type="hidden" name="rev2[]" value="'.$r_rev.'" ></input>'); $form->addHidden('mediado', 'diff'); $form->printForm(); echo NL.'<div id="mediamanager__diff" >'.NL; } if ($difftype == 'opacity' || $difftype == 'portions') { media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $difftype); if (!$fromajax) echo '</div>'; return; } } list($l_head, $r_head) = html_diff_head($l_rev, $r_rev, $image, true); ?> <div class="table"> <table> <tr> <th><?php echo $l_head; ?></th> <th><?php echo $r_head; ?></th> </tr> <?php echo '<tr class="image">'; echo '<td>'; media_preview($image, $auth, $l_rev, $l_meta); echo '</td>'; echo '<td>'; media_preview($image, $auth, $r_rev, $r_meta); echo '</td>'; echo '</tr>'.NL; echo '<tr class="actions">'; echo '<td>'; media_preview_buttons($image, $auth, $l_rev); echo '</td>'; echo '<td>'; media_preview_buttons($image, $auth, $r_rev); echo '</td>'; echo '</tr>'.NL; $l_tags = media_file_tags($l_meta); $r_tags = media_file_tags($r_meta); // FIXME r_tags-only stuff foreach ($l_tags as $key => $l_tag) { if ($l_tag['value'] != $r_tags[$key]['value']) { $r_tags[$key]['highlighted'] = true; $l_tags[$key]['highlighted'] = true; } else if (!$l_tag['value'] || !$r_tags[$key]['value']) { unset($r_tags[$key]); unset($l_tags[$key]); } } echo '<tr>'; foreach(array($l_tags,$r_tags) as $tags){ echo '<td>'.NL; echo '<dl class="img_tags">'; foreach($tags as $tag){ $value = cleanText($tag['value']); if (!$value) $value = '-'; echo '<dt>'.$lang[$tag['tag'][1]].'</dt>'; echo '<dd>'; if ($tag['highlighted']) { echo '<strong>'; } if ($tag['tag'][2] == 'date') echo dformat($value); else echo hsc($value); if ($tag['highlighted']) { echo '</strong>'; } echo '</dd>'; } echo '</dl>'.NL; echo '</td>'; } echo '</tr>'.NL; echo '</table>'.NL; echo '</div>'.NL; if ($is_img && !$fromajax) echo '</div>'; } /** * Prints two images side by side * and slider * * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $image image id * @param int $l_rev revision timestamp, or empty string * @param int $r_rev revision timestamp, or empty string * @param array $l_size array with width and height * @param array $r_size array with width and height * @param string $type */ function media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $type) { if ($l_size != $r_size) { if ($r_size[0] > $l_size[0]) { $l_size = $r_size; } } $l_more = array('rev' => $l_rev, 'h' => $l_size[1], 'w' => $l_size[0]); $r_more = array('rev' => $r_rev, 'h' => $l_size[1], 'w' => $l_size[0]); $l_src = ml($image, $l_more); $r_src = ml($image, $r_more); // slider echo '<div class="slider" style="max-width: '.($l_size[0]-20).'px;" ></div>'.NL; // two images in divs echo '<div class="imageDiff ' . $type . '">'.NL; echo '<div class="image1" style="max-width: '.$l_size[0].'px;">'; echo '<img src="'.$l_src.'" alt="" />'; echo '</div>'.NL; echo '<div class="image2" style="max-width: '.$l_size[0].'px;">'; echo '<img src="'.$r_src.'" alt="" />'; echo '</div>'.NL; echo '</div>'.NL; } /** * Restores an old revision of a media file * * @param string $image media id * @param int $rev revision timestamp or empty string * @param int $auth * @return string - file's id * * @author Kate Arzamastseva <pshns@ukr.net> */ function media_restore($image, $rev, $auth){ global $conf; if ($auth < AUTH_UPLOAD || !$conf['mediarevisions']) return false; $removed = (!file_exists(mediaFN($image)) && file_exists(mediaMetaFN($image, '.changes'))); if (!$image || (!file_exists(mediaFN($image)) && !$removed)) return false; if (!$rev || !file_exists(mediaFN($image, $rev))) return false; list(,$imime,) = mimetype($image); $res = media_upload_finish(mediaFN($image, $rev), mediaFN($image), $image, $imime, true, 'copy'); if (is_array($res)) { msg($res[0], $res[1]); return false; } return $res; } /** * List all files found by the search request * * @author Tobias Sarnowski <sarnowski@cosmocode.de> * @author Andreas Gohr <gohr@cosmocode.de> * @author Kate Arzamastseva <pshns@ukr.net> * @triggers MEDIA_SEARCH * * @param string $query * @param string $ns * @param null|int $auth * @param bool $fullscreen * @param string $sort */ function media_searchlist($query,$ns,$auth=null,$fullscreen=false,$sort='natural'){ global $conf; global $lang; $ns = cleanID($ns); $evdata = array( 'ns' => $ns, 'data' => array(), 'query' => $query ); if (!blank($query)) { $evt = new Event('MEDIA_SEARCH', $evdata); if ($evt->advise_before()) { $dir = utf8_encodeFN(str_replace(':','/',$evdata['ns'])); $quoted = preg_quote($evdata['query'],'/'); //apply globbing $quoted = str_replace(array('\*', '\?'), array('.*', '.'), $quoted, $count); //if we use globbing file name must match entirely but may be preceded by arbitrary namespace if ($count > 0) $quoted = '^([^:]*:)*'.$quoted.'$'; $pattern = '/'.$quoted.'/i'; search($evdata['data'], $conf['mediadir'], 'search_media', array('showmsg'=>false,'pattern'=>$pattern), $dir, 1, $sort); } $evt->advise_after(); unset($evt); } if (!$fullscreen) { echo '<h1 id="media__ns">'.sprintf($lang['searchmedia_in'],hsc($ns).':*').'</h1>'.NL; media_searchform($ns,$query); } if(!count($evdata['data'])){ echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL; }else { if ($fullscreen) { echo '<ul class="' . _media_get_list_type() . '">'; } foreach($evdata['data'] as $item){ if (!$fullscreen) media_printfile($item,$item['perm'],'',true); else media_printfile_thumbs($item,$item['perm'],false,true); } if ($fullscreen) echo '</ul>'.NL; } } /** * Formats and prints one file in the list * * @param array $item * @param int $auth permission level * @param string $jump item id * @param bool $display_namespace */ function media_printfile($item,$auth,$jump,$display_namespace=false){ global $lang; // Prepare zebra coloring // I always wanted to use this variable name :-D static $twibble = 1; $twibble *= -1; $zebra = ($twibble == -1) ? 'odd' : 'even'; // Automatically jump to recent action if($jump == $item['id']) { $jump = ' id="scroll__here" '; }else{ $jump = ''; } // Prepare fileicons list($ext) = mimetype($item['file'],false); $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext); $class = 'select mediafile mf_'.$class; // Prepare filename $file = utf8_decodeFN($item['file']); // Prepare info $info = ''; if($item['isimg']){ $info .= (int) $item['meta']->getField('File.Width'); $info .= '×'; $info .= (int) $item['meta']->getField('File.Height'); $info .= ' '; } $info .= '<i>'.dformat($item['mtime']).'</i>'; $info .= ' '; $info .= filesize_h($item['size']); // output echo '<div class="'.$zebra.'"'.$jump.' title="'.hsc($item['id']).'">'.NL; if (!$display_namespace) { echo '<a id="h_:'.$item['id'].'" class="'.$class.'">'.hsc($file).'</a> '; } else { echo '<a id="h_:'.$item['id'].'" class="'.$class.'">'.hsc($item['id']).'</a><br/>'; } echo '<span class="info">('.$info.')</span>'.NL; // view button $link = ml($item['id'],'',true); echo ' <a href="'.$link.'" target="_blank"><img src="'.DOKU_BASE.'lib/images/magnifier.png" '. 'alt="'.$lang['mediaview'].'" title="'.$lang['mediaview'].'" class="btn" /></a>'; // mediamanager button $link = wl('',array('do'=>'media','image'=>$item['id'],'ns'=>getNS($item['id']))); echo ' <a href="'.$link.'" target="_blank"><img src="'.DOKU_BASE.'lib/images/mediamanager.png" '. 'alt="'.$lang['btn_media'].'" title="'.$lang['btn_media'].'" class="btn" /></a>'; // delete button if($item['writable'] && $auth >= AUTH_DELETE){ $link = DOKU_BASE.'lib/exe/mediamanager.php?delete='.rawurlencode($item['id']). '&sectok='.getSecurityToken(); echo ' <a href="'.$link.'" class="btn_media_delete" title="'.$item['id'].'">'. '<img src="'.DOKU_BASE.'lib/images/trash.png" alt="'.$lang['btn_delete'].'" '. 'title="'.$lang['btn_delete'].'" class="btn" /></a>'; } echo '<div class="example" id="ex_'.str_replace(':','_',$item['id']).'">'; echo $lang['mediausage'].' <code>{{:'.$item['id'].'}}</code>'; echo '</div>'; if($item['isimg']) media_printimgdetail($item); echo '<div class="clearer"></div>'.NL; echo '</div>'.NL; } /** * Display a media icon * * @param string $filename media id * @param string $size the size subfolder, if not specified 16x16 is used * @return string html */ function media_printicon($filename, $size=''){ list($ext) = mimetype(mediaFN($filename),false); if (file_exists(DOKU_INC.'lib/images/fileicons/'.$size.'/'.$ext.'.png')) { $icon = DOKU_BASE.'lib/images/fileicons/'.$size.'/'.$ext.'.png'; } else { $icon = DOKU_BASE.'lib/images/fileicons/'.$size.'/file.png'; } return '<img src="'.$icon.'" alt="'.$filename.'" class="icon" />'; } /** * Formats and prints one file in the list in the thumbnails view * * @author Kate Arzamastseva <pshns@ukr.net> * * @param array $item * @param int $auth permission level * @param bool|string $jump item id * @param bool $display_namespace */ function media_printfile_thumbs($item,$auth,$jump=false,$display_namespace=false){ // Prepare filename $file = utf8_decodeFN($item['file']); // output echo '<li><dl title="'.hsc($item['id']).'">'.NL; echo '<dt>'; if($item['isimg']) { media_printimgdetail($item, true); } else { echo '<a id="d_:'.$item['id'].'" class="image" title="'.$item['id'].'" href="'. media_managerURL(array('image' => hsc($item['id']), 'ns' => getNS($item['id']), 'tab_details' => 'view')).'">'; echo media_printicon($item['id'], '32x32'); echo '</a>'; } echo '</dt>'.NL; if (!$display_namespace) { $name = hsc($file); } else { $name = hsc($item['id']); } echo '<dd class="name"><a href="'.media_managerURL(array('image' => hsc($item['id']), 'ns' => getNS($item['id']), 'tab_details' => 'view')).'" id="h_:'.$item['id'].'">'.$name.'</a></dd>'.NL; if($item['isimg']){ $size = ''; $size .= (int) $item['meta']->getField('File.Width'); $size .= '×'; $size .= (int) $item['meta']->getField('File.Height'); echo '<dd class="size">'.$size.'</dd>'.NL; } else { echo '<dd class="size"> </dd>'.NL; } $date = dformat($item['mtime']); echo '<dd class="date">'.$date.'</dd>'.NL; $filesize = filesize_h($item['size']); echo '<dd class="filesize">'.$filesize.'</dd>'.NL; echo '</dl></li>'.NL; } /** * Prints a thumbnail and metainfo * * @param array $item * @param bool $fullscreen */ function media_printimgdetail($item, $fullscreen=false){ // prepare thumbnail $size = $fullscreen ? 90 : 120; $w = (int) $item['meta']->getField('File.Width'); $h = (int) $item['meta']->getField('File.Height'); if($w>$size || $h>$size){ if (!$fullscreen) { $ratio = $item['meta']->getResizeRatio($size); } else { $ratio = $item['meta']->getResizeRatio($size,$size); } $w = floor($w * $ratio); $h = floor($h * $ratio); } $src = ml($item['id'],array('w'=>$w,'h'=>$h,'t'=>$item['mtime'])); $p = array(); if (!$fullscreen) { // In fullscreen mediamanager view, image resizing is done via CSS. $p['width'] = $w; $p['height'] = $h; } $p['alt'] = $item['id']; $att = buildAttributes($p); // output if ($fullscreen) { echo '<a id="l_:'.$item['id'].'" class="image thumb" href="'. media_managerURL(['image' => hsc($item['id']), 'ns' => getNS($item['id']), 'tab_details' => 'view']).'">'; echo '<img src="'.$src.'" '.$att.' />'; echo '</a>'; } if ($fullscreen) return; echo '<div class="detail">'; echo '<div class="thumb">'; echo '<a id="d_:'.$item['id'].'" class="select">'; echo '<img src="'.$src.'" '.$att.' />'; echo '</a>'; echo '</div>'; // read EXIF/IPTC data $t = $item['meta']->getField(array('IPTC.Headline','xmp.dc:title')); $d = $item['meta']->getField(array('IPTC.Caption','EXIF.UserComment', 'EXIF.TIFFImageDescription', 'EXIF.TIFFUserComment')); if(\dokuwiki\Utf8\PhpString::strlen($d) > 250) $d = \dokuwiki\Utf8\PhpString::substr($d,0,250).'...'; $k = $item['meta']->getField(array('IPTC.Keywords','IPTC.Category','xmp.dc:subject')); // print EXIF/IPTC data if($t || $d || $k ){ echo '<p>'; if($t) echo '<strong>'.hsc($t).'</strong><br />'; if($d) echo hsc($d).'<br />'; if($t) echo '<em>'.hsc($k).'</em>'; echo '</p>'; } echo '</div>'; } /** * Build link based on the current, adding/rewriting parameters * * @author Kate Arzamastseva <pshns@ukr.net> * * @param array|bool $params * @param string $amp separator * @param bool $abs absolute url? * @param bool $params_array return the parmeters array? * @return string|array - link or link parameters */ function media_managerURL($params=false, $amp='&', $abs=false, $params_array=false) { global $ID; global $INPUT; $gets = array('do' => 'media'); $media_manager_params = array('tab_files', 'tab_details', 'image', 'ns', 'list', 'sort'); foreach ($media_manager_params as $x) { if ($INPUT->has($x)) $gets[$x] = $INPUT->str($x); } if ($params) { $gets = $params + $gets; } unset($gets['id']); if (isset($gets['delete'])) { unset($gets['image']); unset($gets['tab_details']); } if ($params_array) return $gets; return wl($ID,$gets,$abs,$amp); } /** * Print the media upload form if permissions are correct * * @author Andreas Gohr <andi@splitbrain.org> * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $ns * @param int $auth permission level * @param bool $fullscreen */ function media_uploadform($ns, $auth, $fullscreen = false){ global $lang; global $conf; global $INPUT; if($auth < AUTH_UPLOAD) { echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.NL; return; } $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE); $update = false; $id = ''; if ($auth >= $auth_ow && $fullscreen && $INPUT->str('mediado') == 'update') { $update = true; $id = cleanID($INPUT->str('image')); } // The default HTML upload form $params = array('id' => 'dw__upload', 'enctype' => 'multipart/form-data'); if (!$fullscreen) { $params['action'] = DOKU_BASE.'lib/exe/mediamanager.php'; } else { $params['action'] = media_managerURL(array('tab_files' => 'files', 'tab_details' => 'view'), '&'); } $form = new Doku_Form($params); if (!$fullscreen) echo '<div class="upload">' . $lang['mediaupload'] . '</div>'; $form->addElement(formSecurityToken()); $form->addHidden('ns', hsc($ns)); $form->addElement(form_makeOpenTag('p')); $form->addElement(form_makeFileField('upload', $lang['txt_upload'], 'upload__file')); $form->addElement(form_makeCloseTag('p')); $form->addElement(form_makeOpenTag('p')); $form->addElement(form_makeTextField('mediaid', noNS($id), $lang['txt_filename'], 'upload__name')); $form->addElement(form_makeButton('submit', '', $lang['btn_upload'])); $form->addElement(form_makeCloseTag('p')); if($auth >= $auth_ow){ $form->addElement(form_makeOpenTag('p')); $attrs = array(); if ($update) $attrs['checked'] = 'checked'; $form->addElement(form_makeCheckboxField('ow', 1, $lang['txt_overwrt'], 'dw__ow', 'check', $attrs)); $form->addElement(form_makeCloseTag('p')); } echo NL.'<div id="mediamanager__uploader">'.NL; html_form('upload', $form); echo '</div>'.NL; echo '<p class="maxsize">'; printf($lang['maxuploadsize'],filesize_h(media_getuploadsize())); echo ' <a class="allowedmime" href="#">' . $lang['allowedmime'] . '</a>'; echo ' <span>' . implode(', ', array_keys(getMimeTypes())) .'</span>'; echo '</p>'.NL; } /** * Returns the size uploaded files may have * * This uses a conservative approach using the lowest number found * in any of the limiting ini settings * * @returns int size in bytes */ function media_getuploadsize(){ $okay = 0; $post = (int) php_to_byte(@ini_get('post_max_size')); $suho = (int) php_to_byte(@ini_get('suhosin.post.max_value_length')); $upld = (int) php_to_byte(@ini_get('upload_max_filesize')); if($post && ($post < $okay || $okay == 0)) $okay = $post; if($suho && ($suho < $okay || $okay == 0)) $okay = $suho; if($upld && ($upld < $okay || $okay == 0)) $okay = $upld; return $okay; } /** * Print the search field form * * @author Tobias Sarnowski <sarnowski@cosmocode.de> * @author Kate Arzamastseva <pshns@ukr.net> * * @param string $ns * @param string $query * @param bool $fullscreen */ function media_searchform($ns,$query='',$fullscreen=false){ global $lang; // The default HTML search form $params = array('id' => 'dw__mediasearch'); if (!$fullscreen) { $params['action'] = DOKU_BASE.'lib/exe/mediamanager.php'; } else { $params['action'] = media_managerURL(array(), '&'); } $form = new Doku_Form($params); $form->addHidden('ns', $ns); $form->addHidden($fullscreen ? 'mediado' : 'do', 'searchlist'); $form->addElement(form_makeOpenTag('p')); $form->addElement( form_makeTextField( 'q', $query, $lang['searchmedia'], '', '', array('title' => sprintf($lang['searchmedia_in'], hsc($ns) . ':*')) ) ); $form->addElement(form_makeButton('submit', '', $lang['btn_search'])); $form->addElement(form_makeCloseTag('p')); html_form('searchmedia', $form); } /** * Build a tree outline of available media namespaces * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $ns */ function media_nstree($ns){ global $conf; global $lang; // currently selected namespace $ns = cleanID($ns); if(empty($ns)){ global $ID; $ns = (string)getNS($ID); } $ns_dir = utf8_encodeFN(str_replace(':','/',$ns)); $data = array(); search($data,$conf['mediadir'],'search_index',array('ns' => $ns_dir, 'nofiles' => true)); // wrap a list with the root level around the other namespaces array_unshift($data, array('level' => 0, 'id' => '', 'open' =>'true', 'label' => '['.$lang['mediaroot'].']')); // insert the current ns into the hierarchy if it isn't already part of it $ns_parts = explode(':', $ns); $tmp_ns = ''; $pos = 0; foreach ($ns_parts as $level => $part) { if ($tmp_ns) $tmp_ns .= ':'.$part; else $tmp_ns = $part; // find the namespace parts or insert them while ($data[$pos]['id'] != $tmp_ns) { if ( $pos >= count($data) || ( $data[$pos]['level'] <= $level+1 && strnatcmp(utf8_encodeFN($data[$pos]['id']), utf8_encodeFN($tmp_ns)) > 0 ) ) { array_splice($data, $pos, 0, array(array('level' => $level+1, 'id' => $tmp_ns, 'open' => 'true'))); break; } ++$pos; } } echo html_buildlist($data,'idx','media_nstree_item','media_nstree_li'); } /** * Userfunction for html_buildlist * * Prints a media namespace tree item * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $item * @return string html */ function media_nstree_item($item){ global $INPUT; $pos = strrpos($item['id'], ':'); $label = substr($item['id'], $pos > 0 ? $pos + 1 : 0); if(empty($item['label'])) $item['label'] = $label; $ret = ''; if (!($INPUT->str('do') == 'media')) $ret .= '<a href="'.DOKU_BASE.'lib/exe/mediamanager.php?ns='.idfilter($item['id']).'" class="idx_dir">'; else $ret .= '<a href="'.media_managerURL(array('ns' => idfilter($item['id'], false), 'tab_files' => 'files')) .'" class="idx_dir">'; $ret .= $item['label']; $ret .= '</a>'; return $ret; } /** * Userfunction for html_buildlist * * Prints a media namespace tree item opener * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $item * @return string html */ function media_nstree_li($item){ $class='media level'.$item['level']; if($item['open']){ $class .= ' open'; $img = DOKU_BASE.'lib/images/minus.gif'; $alt = '−'; }else{ $class .= ' closed'; $img = DOKU_BASE.'lib/images/plus.gif'; $alt = '+'; } // TODO: only deliver an image if it actually has a subtree... return '<li class="'.$class.'">'. '<img src="'.$img.'" alt="'.$alt.'" />'; } /** * Resizes the given image to the given size * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $file filename, path to file * @param string $ext extension * @param int $w desired width * @param int $h desired height * @return string path to resized or original size if failed */ function media_resize_image($file, $ext, $w, $h=0){ global $conf; $info = @getimagesize($file); //get original size if($info == false) return $file; // that's no image - it's a spaceship! if(!$h) $h = round(($w * $info[1]) / $info[0]); if(!$w) $w = round(($h * $info[0]) / $info[1]); // we wont scale up to infinity if($w > 2000 || $h > 2000) return $file; // resize necessary? - (w,h) = native dimensions if(($w == $info[0]) && ($h == $info[1])) return $file; //cache $local = getCacheName($file,'.media.'.$w.'x'.$h.'.'.$ext); $mtime = @filemtime($local); // 0 if not exists if($mtime > filemtime($file) || 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($conf['fperm']) @chmod($local, $conf['fperm']); return $local; } //still here? resizing failed return $file; } /** * Crops the given image to the wanted ratio, then calls media_resize_image to scale it * to the wanted size * * Crops are centered horizontally but prefer the upper third of an vertical * image because most pics are more interesting in that area (rule of thirds) * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $file filename, path to file * @param string $ext extension * @param int $w desired width * @param int $h desired height * @return string path to resized or original size if failed */ function media_crop_image($file, $ext, $w, $h=0){ global $conf; if(!$h) $h = $w; $info = @getimagesize($file); //get original size if($info == false) return $file; // that's no image - it's a spaceship! // calculate crop size $fr = $info[0]/$info[1]; $tr = $w/$h; // check if the crop can be handled completely by resize, // i.e. the specified width & height match the aspect ratio of the source image if ($w == round($h*$fr)) { return media_resize_image($file, $ext, $w); } if($tr >= 1){ if($tr > $fr){ $cw = $info[0]; $ch = (int) ($info[0]/$tr); }else{ $cw = (int) ($info[1]*$tr); $ch = $info[1]; } }else{ if($tr < $fr){ $cw = (int) ($info[1]*$tr); $ch = $info[1]; }else{ $cw = $info[0]; $ch = (int) ($info[0]/$tr); } } // calculate crop offset $cx = (int) (($info[0]-$cw)/2); $cy = (int) (($info[1]-$ch)/3); //cache $local = getCacheName($file,'.media.'.$cw.'x'.$ch.'.crop.'.$ext); $mtime = @filemtime($local); // 0 if not exists 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($conf['fperm']) @chmod($local, $conf['fperm']); return media_resize_image($local,$ext, $w, $h); } //still here? cropping failed return media_resize_image($file,$ext, $w, $h); } /** * Calculate a token to be used to verify fetch requests for resized or * cropped images have been internally generated - and prevent external * DDOS attacks via fetch * * @author Christopher Smith <chris@jalakai.co.uk> * * @param string $id id of the image * @param int $w resize/crop width * @param int $h resize/crop height * @return string token or empty string if no token required */ function media_get_token($id,$w,$h){ // token is only required for modified images if ($w || $h || media_isexternal($id)) { $token = $id; if ($w) $token .= '.'.$w; if ($h) $token .= '.'.$h; return substr(\dokuwiki\PassHash::hmac('md5', $token, auth_cookiesalt()),0,6); } return ''; } /** * Download a remote file and return local filename * * returns false if download fails. Uses cached file if available and * wanted * * @author Andreas Gohr <andi@splitbrain.org> * @author Pavel Vitis <Pavel.Vitis@seznam.cz> * * @param string $url * @param string $ext extension * @param int $cache cachetime in seconds * @return false|string path to cached file */ function media_get_from_URL($url,$ext,$cache){ global $conf; // if no cache or fetchsize just redirect if ($cache==0) return false; if (!$conf['fetchsize']) return false; $local = getCacheName(strtolower($url),".media.$ext"); $mtime = @filemtime($local); // 0 if not exists //decide if download needed: if(($mtime == 0) || // cache does not exist ($cache != -1 && $mtime < time() - $cache) // 'recache' and cache has expired ) { if(media_image_download($url, $local)) { return $local; } else { return false; } } //if cache exists use it else if($mtime) return $local; //else return false return false; } /** * Download image files * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $url * @param string $file path to file in which to put the downloaded content * @return bool */ function media_image_download($url,$file){ global $conf; $http = new DokuHTTPClient(); $http->keep_alive = false; // we do single ops here, no need for keep-alive $http->max_bodysize = $conf['fetchsize']; $http->timeout = 25; //max. 25 sec $http->header_regexp = '!\r\nContent-Type: image/(jpe?g|gif|png)!i'; $data = $http->get($url); if(!$data) return false; $fileexists = file_exists($file); $fp = @fopen($file,"w"); if(!$fp) return false; fwrite($fp,$data); fclose($fp); if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']); // check if it is really an image $info = @getimagesize($file); if(!$info){ @unlink($file); return false; } return true; } /** * resize images using external ImageMagick convert program * * @author Pavel Vitis <Pavel.Vitis@seznam.cz> * @author Andreas Gohr <andi@splitbrain.org> * * @param string $ext extension * @param string $from filename path to file * @param int $from_w original width * @param int $from_h original height * @param string $to path to resized file * @param int $to_w desired width * @param int $to_h desired height * @return bool */ function media_resize_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){ global $conf; // check if convert is configured if(!$conf['im_convert']) return false; // prepare command $cmd = $conf['im_convert']; $cmd .= ' -resize '.$to_w.'x'.$to_h.'!'; if ($ext == 'jpg' || $ext == 'jpeg') { $cmd .= ' -quality '.$conf['jpg_quality']; } $cmd .= " $from $to"; @exec($cmd,$out,$retval); if ($retval == 0) return true; return false; } /** * crop images using external ImageMagick convert program * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $ext extension * @param string $from filename path to file * @param int $from_w original width * @param int $from_h original height * @param string $to path to resized file * @param int $to_w desired width * @param int $to_h desired height * @param int $ofs_x offset of crop centre * @param int $ofs_y offset of crop centre * @return bool */ function media_crop_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h,$ofs_x,$ofs_y){ global $conf; // check if convert is configured if(!$conf['im_convert']) return false; // prepare command $cmd = $conf['im_convert']; $cmd .= ' -crop '.$to_w.'x'.$to_h.'+'.$ofs_x.'+'.$ofs_y; if ($ext == 'jpg' || $ext == 'jpeg') { $cmd .= ' -quality '.$conf['jpg_quality']; } $cmd .= " $from $to"; @exec($cmd,$out,$retval); if ($retval == 0) return true; return false; } /** * resize or crop images using PHP's libGD support * * @author Andreas Gohr <andi@splitbrain.org> * @author Sebastian Wienecke <s_wienecke@web.de> * * @param string $ext extension * @param string $from filename path to file * @param int $from_w original width * @param int $from_h original height * @param string $to path to resized file * @param int $to_w desired width * @param int $to_h desired height * @param int $ofs_x offset of crop centre * @param int $ofs_y offset of crop centre * @return bool */ function media_resize_imageGD($ext,$from,$from_w,$from_h,$to,$to_w,$to_h,$ofs_x=0,$ofs_y=0){ global $conf; if($conf['gdlib'] < 1) return false; //no GDlib available or wanted // check available memory if(!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))){ return false; } // create an image of the given filetype $image = false; if ($ext == 'jpg' || $ext == 'jpeg'){ if(!function_exists("imagecreatefromjpeg")) return false; $image = @imagecreatefromjpeg($from); }elseif($ext == 'png') { if(!function_exists("imagecreatefrompng")) return false; $image = @imagecreatefrompng($from); }elseif($ext == 'gif') { if(!function_exists("imagecreatefromgif")) return false; $image = @imagecreatefromgif($from); } if(!$image) return false; $newimg = false; if(($conf['gdlib']>1) && function_exists("imagecreatetruecolor") && $ext != 'gif'){ $newimg = @imagecreatetruecolor ($to_w, $to_h); } if(!$newimg) $newimg = @imagecreate($to_w, $to_h); if(!$newimg){ imagedestroy($image); return false; } //keep png alpha channel if possible if($ext == 'png' && $conf['gdlib']>1 && function_exists('imagesavealpha')){ imagealphablending($newimg, false); imagesavealpha($newimg,true); } //keep gif transparent color if possible if($ext == 'gif' && function_exists('imagefill') && function_exists('imagecolorallocate')) { if(function_exists('imagecolorsforindex') && function_exists('imagecolortransparent')) { $transcolorindex = @imagecolortransparent($image); if($transcolorindex >= 0 ) { //transparent color exists $transcolor = @imagecolorsforindex($image, $transcolorindex); $transcolorindex = @imagecolorallocate( $newimg, $transcolor['red'], $transcolor['green'], $transcolor['blue'] ); @imagefill($newimg, 0, 0, $transcolorindex); @imagecolortransparent($newimg, $transcolorindex); }else{ //filling with white $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255); @imagefill($newimg, 0, 0, $whitecolorindex); } }else{ //filling with white $whitecolorindex = @imagecolorallocate($newimg, 255, 255, 255); @imagefill($newimg, 0, 0, $whitecolorindex); } } //try resampling first if(function_exists("imagecopyresampled")){ if(!@imagecopyresampled($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h)) { imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h); } }else{ imagecopyresized($newimg, $image, 0, 0, $ofs_x, $ofs_y, $to_w, $to_h, $from_w, $from_h); } $okay = false; if ($ext == 'jpg' || $ext == 'jpeg'){ if(!function_exists('imagejpeg')){ $okay = false; }else{ $okay = imagejpeg($newimg, $to, $conf['jpg_quality']); } }elseif($ext == 'png') { if(!function_exists('imagepng')){ $okay = false; }else{ $okay = imagepng($newimg, $to); } }elseif($ext == 'gif') { if(!function_exists('imagegif')){ $okay = false; }else{ $okay = imagegif($newimg, $to); } } // destroy GD image ressources if($image) imagedestroy($image); if($newimg) imagedestroy($newimg); return $okay; } /** * Return other media files with the same base name * but different extensions. * * @param string $src - ID of media file * @param string[] $exts - alternative extensions to find other files for * @return array - array(mime type => file ID) * * @author Anika Henke <anika@selfthinker.org> */ function media_alternativefiles($src, $exts){ $files = array(); list($srcExt, /* $srcMime */) = mimetype($src); $filebase = substr($src, 0, -1 * (strlen($srcExt)+1)); foreach($exts as $ext) { $fileid = $filebase.'.'.$ext; $file = mediaFN($fileid); if(file_exists($file)) { list(/* $fileExt */, $fileMime) = mimetype($file); $files[$fileMime] = $fileid; } } return $files; } /** * Check if video/audio is supported to be embedded. * * @param string $mime - mimetype of media file * @param string $type - type of media files to check ('video', 'audio', or null for all) * @return boolean * * @author Anika Henke <anika@selfthinker.org> */ function media_supportedav($mime, $type=NULL){ $supportedAudio = array( 'ogg' => 'audio/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/wav', ); $supportedVideo = array( 'webm' => 'video/webm', 'ogv' => 'video/ogg', 'mp4' => 'video/mp4', ); if ($type == 'audio') { $supportedAv = $supportedAudio; } elseif ($type == 'video') { $supportedAv = $supportedVideo; } else { $supportedAv = array_merge($supportedAudio, $supportedVideo); } return in_array($mime, $supportedAv); } /** * Return track media files with the same base name * but extensions that indicate kind and lang. * ie for foo.webm search foo.sub.lang.vtt, foo.cap.lang.vtt... * * @param string $src - ID of media file * @return array - array(mediaID => array( kind, srclang )) * * @author Schplurtz le Déboulonné <Schplurtz@laposte.net> */ function media_trackfiles($src){ $kinds=array( 'sub' => 'subtitles', 'cap' => 'captions', 'des' => 'descriptions', 'cha' => 'chapters', 'met' => 'metadata' ); $files = array(); $re='/\\.(sub|cap|des|cha|met)\\.([^.]+)\\.vtt$/'; $baseid=pathinfo($src, PATHINFO_FILENAME); $pattern=mediaFN($baseid).'.*.*.vtt'; $list=glob($pattern); foreach($list as $track) { if(preg_match($re, $track, $matches)){ $files[$baseid.'.'.$matches[1].'.'.$matches[2].'.vtt']=array( $kinds[$matches[1]], $matches[2], ); } } return $files; } /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ .htaccess 0000644 00000000261 15233462216 0006345 0 ustar 00 ## no access to the inc directory <IfModule mod_authz_core.c> Require all denied </IfModule> <IfModule !mod_authz_core.c> Order allow,deny Deny from all </IfModule> init.php 0000644 00000043650 15233462216 0006234 0 ustar 00 <?php /** * Initialize some defaults needed for DokuWiki */ use dokuwiki\Extension\Event; use dokuwiki\Extension\EventHandler; /** * timing Dokuwiki execution * * @param integer $start * * @return mixed */ function delta_time($start=0) { return microtime(true)-((float)$start); } define('DOKU_START_TIME', delta_time()); global $config_cascade; $config_cascade = array(); // if available load a preload config file $preload = fullpath(dirname(__FILE__)).'/preload.php'; if (file_exists($preload)) include($preload); // define the include path if(!defined('DOKU_INC')) define('DOKU_INC',fullpath(dirname(__FILE__).'/../').'/'); // define Plugin dir if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); // define config path (packagers may want to change this to /etc/dokuwiki/) if(!defined('DOKU_CONF')) define('DOKU_CONF',DOKU_INC.'conf/'); // check for error reporting override or set error reporting to sane values if (!defined('DOKU_E_LEVEL') && file_exists(DOKU_CONF.'report_e_all')) { define('DOKU_E_LEVEL', E_ALL); } if (!defined('DOKU_E_LEVEL')) { error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT); } else { error_reporting(DOKU_E_LEVEL); } // avoid caching issues #1594 header('Vary: Cookie'); // init memory caches global $cache_revinfo; $cache_revinfo = array(); global $cache_wikifn; $cache_wikifn = array(); global $cache_cleanid; $cache_cleanid = array(); global $cache_authname; $cache_authname = array(); global $cache_metadata; $cache_metadata = array(); // always include 'inc/config_cascade.php' // previously in preload.php set fields of $config_cascade will be merged with the defaults include(DOKU_INC.'inc/config_cascade.php'); //prepare config array() global $conf; $conf = array(); // load the global config file(s) foreach (array('default','local','protected') as $config_group) { if (empty($config_cascade['main'][$config_group])) continue; foreach ($config_cascade['main'][$config_group] as $config_file) { if (file_exists($config_file)) { include($config_file); } } } //prepare license array() global $license; $license = array(); // load the license file(s) foreach (array('default','local') as $config_group) { if (empty($config_cascade['license'][$config_group])) continue; foreach ($config_cascade['license'][$config_group] as $config_file) { if(file_exists($config_file)){ include($config_file); } } } // set timezone (as in pre 5.3.0 days) date_default_timezone_set(@date_default_timezone_get()); // define baseURL if(!defined('DOKU_REL')) define('DOKU_REL',getBaseURL(false)); if(!defined('DOKU_URL')) define('DOKU_URL',getBaseURL(true)); if(!defined('DOKU_BASE')){ if($conf['canonical']){ define('DOKU_BASE',DOKU_URL); }else{ define('DOKU_BASE',DOKU_REL); } } // define whitespace if(!defined('NL')) define ('NL',"\n"); if(!defined('DOKU_LF')) define ('DOKU_LF',"\n"); if(!defined('DOKU_TAB')) define ('DOKU_TAB',"\t"); // define cookie and session id, append server port when securecookie is configured FS#1664 if (!defined('DOKU_COOKIE')) { $serverPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : ''; define('DOKU_COOKIE', 'DW' . md5(DOKU_REL . (($conf['securecookie']) ? $serverPort : ''))); unset($serverPort); } // define main script if(!defined('DOKU_SCRIPT')) define('DOKU_SCRIPT','doku.php'); if(!defined('DOKU_TPL')) { /** * @deprecated 2012-10-13 replaced by more dynamic method * @see tpl_basedir() */ define('DOKU_TPL', DOKU_BASE.'lib/tpl/'.$conf['template'].'/'); } if(!defined('DOKU_TPLINC')) { /** * @deprecated 2012-10-13 replaced by more dynamic method * @see tpl_incdir() */ define('DOKU_TPLINC', DOKU_INC.'lib/tpl/'.$conf['template'].'/'); } // make session rewrites XHTML compliant @ini_set('arg_separator.output', '&'); // make sure global zlib does not interfere FS#1132 @ini_set('zlib.output_compression', 'off'); // increase PCRE backtrack limit @ini_set('pcre.backtrack_limit', '20971520'); // enable gzip compression if supported $httpAcceptEncoding = isset($_SERVER['HTTP_ACCEPT_ENCODING']) ? $_SERVER['HTTP_ACCEPT_ENCODING'] : ''; $conf['gzip_output'] &= (strpos($httpAcceptEncoding, 'gzip') !== false); global $ACT; if ($conf['gzip_output'] && !defined('DOKU_DISABLE_GZIP_OUTPUT') && function_exists('ob_gzhandler') && // Disable compression when a (compressed) sitemap might be delivered // See https://bugs.dokuwiki.org/index.php?do=details&task_id=2576 $ACT != 'sitemap') { ob_start('ob_gzhandler'); } // init session if(!headers_sent() && !defined('NOSESSION')) { if(!defined('DOKU_SESSION_NAME')) define ('DOKU_SESSION_NAME', "DokuWiki"); if(!defined('DOKU_SESSION_LIFETIME')) define ('DOKU_SESSION_LIFETIME', 0); if(!defined('DOKU_SESSION_PATH')) { $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; define ('DOKU_SESSION_PATH', $cookieDir); } if(!defined('DOKU_SESSION_DOMAIN')) define ('DOKU_SESSION_DOMAIN', ''); // start the session init_session(); // load left over messages if(isset($_SESSION[DOKU_COOKIE]['msg'])) { $MSG = $_SESSION[DOKU_COOKIE]['msg']; unset($_SESSION[DOKU_COOKIE]['msg']); } } // don't let cookies ever interfere with request vars $_REQUEST = array_merge($_GET,$_POST); // we don't want a purge URL to be digged if(isset($_REQUEST['purge']) && !empty($_SERVER['HTTP_REFERER'])) unset($_REQUEST['purge']); // precalculate file creation modes init_creationmodes(); // make real paths and check them init_paths(); init_files(); // setup plugin controller class (can be overwritten in preload.php) global $plugin_controller_class, $plugin_controller; if (empty($plugin_controller_class)) $plugin_controller_class = dokuwiki\Extension\PluginController::class; // load libraries require_once(DOKU_INC.'vendor/autoload.php'); require_once(DOKU_INC.'inc/load.php'); // disable gzip if not available define('DOKU_HAS_BZIP', function_exists('bzopen')); define('DOKU_HAS_GZIP', function_exists('gzopen')); if($conf['compression'] == 'bz2' && !DOKU_HAS_BZIP) { $conf['compression'] = 'gz'; } if($conf['compression'] == 'gz' && !DOKU_HAS_GZIP) { $conf['compression'] = 0; } // input handle class global $INPUT; $INPUT = new \dokuwiki\Input\Input(); // initialize plugin controller $plugin_controller = new $plugin_controller_class(); // initialize the event handler global $EVENT_HANDLER; $EVENT_HANDLER = new EventHandler(); $local = $conf['lang']; Event::createAndTrigger('INIT_LANG_LOAD', $local, 'init_lang', true); // setup authentication system if (!defined('NOSESSION')) { auth_setup(); } // setup mail system mail_setup(); /** * Initializes the session * * Makes sure the passed session cookie is valid, invalid ones are ignored an a new session ID is issued * * @link http://stackoverflow.com/a/33024310/172068 * @link http://php.net/manual/en/session.configuration.php#ini.session.sid-length */ function init_session() { global $conf; session_name(DOKU_SESSION_NAME); session_set_cookie_params( DOKU_SESSION_LIFETIME, DOKU_SESSION_PATH, DOKU_SESSION_DOMAIN, ($conf['securecookie'] && is_ssl()), true ); // make sure the session cookie contains a valid session ID if(isset($_COOKIE[DOKU_SESSION_NAME]) && !preg_match('/^[-,a-zA-Z0-9]{22,256}$/', $_COOKIE[DOKU_SESSION_NAME])) { unset($_COOKIE[DOKU_SESSION_NAME]); } session_start(); } /** * Checks paths from config file */ function init_paths(){ global $conf; $paths = array('datadir' => 'pages', 'olddir' => 'attic', 'mediadir' => 'media', 'mediaolddir' => 'media_attic', 'metadir' => 'meta', 'mediametadir' => 'media_meta', 'cachedir' => 'cache', 'indexdir' => 'index', 'lockdir' => 'locks', 'tmpdir' => 'tmp'); foreach($paths as $c => $p) { $path = empty($conf[$c]) ? $conf['savedir'].'/'.$p : $conf[$c]; $conf[$c] = init_path($path); if(empty($conf[$c])) nice_die("The $c ('$p') at $path is not found, isn't accessible or writable. You should check your config and permission settings. Or maybe you want to <a href=\"install.php\">run the installer</a>?"); } // path to old changelog only needed for upgrading $conf['changelog_old'] = init_path( (isset($conf['changelog'])) ? ($conf['changelog']) : ($conf['savedir'] . '/changes.log') ); if ($conf['changelog_old']=='') { unset($conf['changelog_old']); } // hardcoded changelog because it is now a cache that lives in meta $conf['changelog'] = $conf['metadir'].'/_dokuwiki.changes'; $conf['media_changelog'] = $conf['metadir'].'/_media.changes'; } /** * Load the language strings * * @param string $langCode language code, as passed by event handler */ function init_lang($langCode) { //prepare language array global $lang, $config_cascade; $lang = array(); //load the language files require(DOKU_INC.'inc/lang/en/lang.php'); foreach ($config_cascade['lang']['core'] as $config_file) { if (file_exists($config_file . 'en/lang.php')) { include($config_file . 'en/lang.php'); } } if ($langCode && $langCode != 'en') { if (file_exists(DOKU_INC."inc/lang/$langCode/lang.php")) { require(DOKU_INC."inc/lang/$langCode/lang.php"); } foreach ($config_cascade['lang']['core'] as $config_file) { if (file_exists($config_file . "$langCode/lang.php")) { include($config_file . "$langCode/lang.php"); } } } } /** * Checks the existence of certain files and creates them if missing. */ function init_files(){ global $conf; $files = array($conf['indexdir'].'/page.idx'); foreach($files as $file){ if(!file_exists($file)){ $fh = @fopen($file,'a'); if($fh){ fclose($fh); if($conf['fperm']) chmod($file, $conf['fperm']); }else{ nice_die("$file is not writable. Check your permissions settings!"); } } } } /** * Returns absolute path * * This tries the given path first, then checks in DOKU_INC. * Check for accessibility on directories as well. * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $path * * @return bool|string */ function init_path($path){ // check existence $p = fullpath($path); if(!file_exists($p)){ $p = fullpath(DOKU_INC.$path); if(!file_exists($p)){ return ''; } } // check writability if(!@is_writable($p)){ return ''; } // check accessability (execute bit) for directories if(@is_dir($p) && !file_exists("$p/.")){ return ''; } return $p; } /** * Sets the internal config values fperm and dperm which, when set, * will be used to change the permission of a newly created dir or * file with chmod. Considers the influence of the system's umask * setting the values only if needed. */ function init_creationmodes(){ global $conf; // Legacy support for old umask/dmask scheme unset($conf['dmask']); unset($conf['fmask']); unset($conf['umask']); unset($conf['fperm']); unset($conf['dperm']); // get system umask, fallback to 0 if none available $umask = @umask(); if(!$umask) $umask = 0000; // 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 = $conf['fmode'] & ~$umask; if($auto_fmode != $conf['fmode']) $conf['fperm'] = $conf['fmode']; // check what is set automatically by the system on file creation // and set the dperm param if it's not what we want $auto_dmode = $conf['dmode'] & ~$umask; if($auto_dmode != $conf['dmode']) $conf['dperm'] = $conf['dmode']; } /** * Returns the full absolute URL to the directory where * DokuWiki is installed in (includes a trailing slash) * * !! Can not access $_SERVER values through $INPUT * !! here as this function is called before $INPUT is * !! initialized. * * @author Andreas Gohr <andi@splitbrain.org> * * @param null|string $abs * * @return string */ function getBaseURL($abs=null){ global $conf; //if canonical url enabled always return absolute if(is_null($abs)) $abs = $conf['canonical']; if(!empty($conf['basedir'])){ $dir = $conf['basedir']; }elseif(substr($_SERVER['SCRIPT_NAME'],-4) == '.php'){ $dir = dirname($_SERVER['SCRIPT_NAME']); }elseif(substr($_SERVER['PHP_SELF'],-4) == '.php'){ $dir = dirname($_SERVER['PHP_SELF']); }elseif($_SERVER['DOCUMENT_ROOT'] && $_SERVER['SCRIPT_FILENAME']){ $dir = preg_replace ('/^'.preg_quote($_SERVER['DOCUMENT_ROOT'],'/').'/','', $_SERVER['SCRIPT_FILENAME']); $dir = dirname('/'.$dir); }else{ $dir = '.'; //probably wrong } $dir = str_replace('\\','/',$dir); // bugfix for weird WIN behaviour $dir = preg_replace('#//+#','/',"/$dir/"); // ensure leading and trailing slashes //handle script in lib/exe dir $dir = preg_replace('!lib/exe/$!','',$dir); //handle script in lib/plugins dir $dir = preg_replace('!lib/plugins/.*$!','',$dir); //finish here for relative URLs if(!$abs) return $dir; //use config if available, trim any slash from end of baseurl to avoid multiple consecutive slashes in the path if(!empty($conf['baseurl'])) return rtrim($conf['baseurl'],'/').$dir; //split hostheader into host and port if(isset($_SERVER['HTTP_HOST'])){ $parsed_host = parse_url('http://'.$_SERVER['HTTP_HOST']); $host = isset($parsed_host['host']) ? $parsed_host['host'] : null; $port = isset($parsed_host['port']) ? $parsed_host['port'] : null; }elseif(isset($_SERVER['SERVER_NAME'])){ $parsed_host = parse_url('http://'.$_SERVER['SERVER_NAME']); $host = isset($parsed_host['host']) ? $parsed_host['host'] : null; $port = isset($parsed_host['port']) ? $parsed_host['port'] : null; }else{ $host = php_uname('n'); $port = ''; } if(is_null($port)){ $port = ''; } if(!is_ssl()){ $proto = 'http://'; if ($port == '80') { $port = ''; } }else{ $proto = 'https://'; if ($port == '443') { $port = ''; } } if($port !== '') $port = ':'.$port; return $proto.$host.$port.$dir; } /** * Check if accessed via HTTPS * * Apache leaves ,$_SERVER['HTTPS'] empty when not available, IIS sets it to 'off'. * 'false' and 'disabled' are just guessing * * @returns bool true when SSL is active */ function is_ssl() { // check if we are behind a reverse proxy if(isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { if($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') { return true; } else { return false; } } if(!isset($_SERVER['HTTPS']) || preg_match('/^(|off|false|disabled)$/i', $_SERVER['HTTPS'])) { return false; } else { return true; } } /** * checks it is windows OS * @return bool */ function isWindows() { return (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? true : false; } /** * print a nice message even if no styles are loaded yet. * * @param integer|string $msg */ function nice_die($msg){ echo<<<EOT <!DOCTYPE html> <html> <head><title>DokuWiki Setup Error</title></head> <body style="font-family: Arial, sans-serif"> <div style="width:60%; margin: auto; background-color: #fcc; border: 1px solid #faa; padding: 0.5em 1em;"> <h1 style="font-size: 120%">DokuWiki Setup Error</h1> <p>$msg</p> </div> </body> </html> EOT; if(defined('DOKU_UNITTEST')) { throw new RuntimeException('nice_die: '.$msg); } exit(1); } /** * A realpath() replacement * * This function behaves similar to PHP's realpath() but does not resolve * symlinks or accesses upper directories * * @author Andreas Gohr <andi@splitbrain.org> * @author <richpageau at yahoo dot co dot uk> * @link http://php.net/manual/en/function.realpath.php#75992 * * @param string $path * @param bool $exists * * @return bool|string */ function fullpath($path,$exists=false){ static $run = 0; $root = ''; $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] == '/'){ $root = '/'; }elseif($iswin){ // match drive letter and UNC paths if(preg_match('!^([a-zA-z]:)(.*)!',$path,$match)){ $root = $match[1].'/'; $path = $match[2]; }else if(preg_match('!^(\\\\\\\\[^\\\\/]+\\\\[^\\\\/]+[\\\\/])(.*)!',$path,$match)){ $root = $match[1]; $path = $match[2]; } } $path = str_replace('\\','/',$path); // if the given path wasn't absolute already, prepend the script path and retry if(!$root){ $base = dirname($_SERVER['SCRIPT_FILENAME']); $path = $base.'/'.$path; if($run == 0){ // avoid endless recursion when base isn't absolute for some reason $run++; return fullpath($path,$exists); } } $run = 0; // canonicalize $path=explode('/', $path); $newpath=array(); foreach($path as $p) { if ($p === '' || $p === '.') continue; if ($p==='..') { array_pop($newpath); continue; } array_push($newpath, $p); } $finalpath = $root.implode('/', $newpath); // check for existence when needed (except when unit testing) if($exists && !defined('DOKU_UNITTEST') && !file_exists($finalpath)) { return false; } return $finalpath; } mail.php 0000644 00000012647 15233462216 0006215 0 ustar 00 <?php /** * Mail functions * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ // end of line for mail lines - RFC822 says CRLF but postfix (and other MTAs?) // think different if(!defined('MAILHEADER_EOL')) define('MAILHEADER_EOL',"\n"); #define('MAILHEADER_ASCIIONLY',1); /** * Patterns for use in email detection and validation * * NOTE: there is an unquoted '/' in RFC2822_ATEXT, it must remain unquoted to be used in the parser * the pattern uses non-capturing groups as captured groups aren't allowed in the parser * select pattern delimiters with care! * * May not be completly RFC conform! * @link http://www.faqs.org/rfcs/rfc2822.html (paras 3.4.1 & 3.2.4) * * @author Chris Smith <chris@jalakai.co.uk> * Check if a given mail address is valid */ if (!defined('RFC2822_ATEXT')) define('RFC2822_ATEXT',"0-9a-zA-Z!#$%&'*+/=?^_`{|}~-"); if (!defined('PREG_PATTERN_VALID_EMAIL')) define( 'PREG_PATTERN_VALID_EMAIL', '['.RFC2822_ATEXT.']+(?:\.['.RFC2822_ATEXT.']+)*@(?i:[0-9a-z][0-9a-z-]*\.)+(?i:[a-z]{2,63})' ); /** * Prepare mailfrom replacement patterns * * Also prepares a mailfromnobody config that contains an autoconstructed address * if the mailfrom one is userdependent and this might not be wanted (subscriptions) * * @author Andreas Gohr <andi@splitbrain.org> */ function mail_setup(){ global $conf; global $USERINFO; /** @var Input $INPUT */ global $INPUT; // auto constructed address $host = @parse_url(DOKU_URL,PHP_URL_HOST); if(!$host) $host = 'example.com'; $noreply = 'noreply@'.$host; $replace = array(); if(!empty($USERINFO['mail'])){ $replace['@MAIL@'] = $USERINFO['mail']; }else{ $replace['@MAIL@'] = $noreply; } // use 'noreply' if no user $replace['@USER@'] = $INPUT->server->str('REMOTE_USER', 'noreply', true); if(!empty($USERINFO['name'])){ $replace['@NAME@'] = $USERINFO['name']; }else{ $replace['@NAME@'] = ''; } // apply replacements $from = str_replace(array_keys($replace), array_values($replace), $conf['mailfrom']); // any replacements done? set different mailfromnone if($from != $conf['mailfrom']){ $conf['mailfromnobody'] = $noreply; }else{ $conf['mailfromnobody'] = $from; } $conf['mailfrom'] = $from; } /** * Check if a given mail address is valid * * @param string $email the address to check * @return bool true if address is valid */ function mail_isvalid($email) { return EmailAddressValidator::checkEmailAddress($email, true); } /** * Quoted printable encoding * * @author umu <umuAThrz.tu-chemnitz.de> * @link http://php.net/manual/en/function.imap-8bit.php#61216 * * @param string $sText * @param int $maxlen * @param bool $bEmulate_imap_8bit * * @return string */ function mail_quotedprintable_encode($sText,$maxlen=74,$bEmulate_imap_8bit=true) { // split text into lines $aLines= preg_split("/(?:\r\n|\r|\n)/", $sText); $cnt = count($aLines); for ($i=0;$i<$cnt;$i++) { $sLine =& $aLines[$i]; if (strlen($sLine)===0) continue; // do nothing, if empty $sRegExp = '/[^\x09\x20\x21-\x3C\x3E-\x7E]/e'; // imap_8bit encodes x09 everywhere, not only at lineends, // for EBCDIC safeness encode !"#$@[\]^`{|}~, // for complete safeness encode every character :) if ($bEmulate_imap_8bit) $sRegExp = '/[^\x20\x21-\x3C\x3E-\x7E]/'; $sLine = preg_replace_callback( $sRegExp, 'mail_quotedprintable_encode_callback', $sLine ); // encode x09,x20 at lineends { $iLength = strlen($sLine); $iLastChar = ord($sLine[$iLength-1]); // !!!!!!!! // imap_8_bit does not encode x20 at the very end of a text, // here is, where I don't agree with imap_8_bit, // please correct me, if I'm wrong, // 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 .= ($iLastChar==0x09)?'09':'20'; } } } // imap_8bit encodes x20 before chr(13), too // although IMHO not requested by RFC2045, why not do it safer :) // and why not encode any x20 around chr(10) or chr(13) if ($bEmulate_imap_8bit) { $sLine=str_replace(' =0D','=20=0D',$sLine); //$sLine=str_replace(' =0A','=20=0A',$sLine); //$sLine=str_replace('=0D ','=0D=20',$sLine); //$sLine=str_replace('=0A ','=0A=20',$sLine); } // finally split into softlines no longer than $maxlen chars, // for even more safeness one could encode x09,x20 // at the very first character of the line // and after soft linebreaks, as well, // but this wouldn't be caught by such an easy RegExp if($maxlen){ preg_match_all( '/.{1,'.($maxlen - 2).'}([^=]{0,2})?/', $sLine, $aMatch ); $sLine = implode( '=' . MAILHEADER_EOL, $aMatch[0] ); // add soft crlf's } } // join lines into text return implode(MAILHEADER_EOL,$aLines); } function mail_quotedprintable_encode_callback($matches){ return sprintf( "=%02X", ord ( $matches[0] ) ) ; } FeedParser.php 0000644 00000001115 15233462216 0007277 0 ustar 00 <?php /** * We override some methods of the original SimplePie class here */ class FeedParser extends SimplePie { /** * Constructor. Set some defaults */ public function __construct(){ parent::__construct(); $this->enable_cache(false); $this->set_file_class(\dokuwiki\FeedParserFile::class); } /** * Backward compatibility for older plugins * * phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps * @param string $url */ public function feed_url($url){ $this->set_feed_url($url); } } Ajax.php 0000644 00000030126 15233462216 0006146 0 ustar 00 <?php namespace dokuwiki; /** * Manage all builtin AJAX calls * * @todo The calls should be refactored out to their own proper classes * @package dokuwiki */ class Ajax { /** * Execute the given call * * @param string $call name of the ajax call */ public function __construct($call) { $callfn = 'call' . ucfirst($call); if(method_exists($this, $callfn)) { $this->$callfn(); } else { $evt = new Extension\Event('AJAX_CALL_UNKNOWN', $call); if($evt->advise_before()) { print "AJAX call '" . hsc($call) . "' unknown!\n"; } else { $evt->advise_after(); unset($evt); } } } /** * Searches for matching pagenames * * @author Andreas Gohr <andi@splitbrain.org> */ protected function callQsearch() { global $lang; global $INPUT; $maxnumbersuggestions = 50; $query = $INPUT->post->str('q'); if(empty($query)) $query = $INPUT->get->str('q'); if(empty($query)) return; $query = urldecode($query); $data = ft_pageLookup($query, true, useHeading('navigation')); if(!count($data)) return; print '<strong>' . $lang['quickhits'] . '</strong>'; print '<ul>'; $counter = 0; foreach($data as $id => $title) { if(useHeading('navigation')) { $name = $title; } else { $ns = getNS($id); if($ns) { $name = noNS($id) . ' (' . $ns . ')'; } else { $name = $id; } } echo '<li>' . html_wikilink(':' . $id, $name) . '</li>'; $counter++; if($counter > $maxnumbersuggestions) { echo '<li>...</li>'; break; } } print '</ul>'; } /** * Support OpenSearch suggestions * * @link http://www.opensearch.org/Specifications/OpenSearch/Extensions/Suggestions/1.0 * @author Mike Frysinger <vapier@gentoo.org> */ protected function callSuggestions() { global $INPUT; $query = cleanID($INPUT->post->str('q')); if(empty($query)) $query = cleanID($INPUT->get->str('q')); if(empty($query)) return; $data = ft_pageLookup($query); if(!count($data)) return; $data = array_keys($data); // limit results to 15 hits $data = array_slice($data, 0, 15); $data = array_map('trim', $data); $data = array_map('noNS', $data); $data = array_unique($data); sort($data); /* now construct a json */ $suggestions = array( $query, // the original query $data, // some suggestions array(), // no description array() // no urls ); header('Content-Type: application/x-suggestions+json'); print json_encode($suggestions); } /** * Refresh a page lock and save draft * * Andreas Gohr <andi@splitbrain.org> */ protected function callLock() { global $ID; global $INFO; global $INPUT; $ID = cleanID($INPUT->post->str('id')); if(empty($ID)) return; $INFO = pageinfo(); $response = [ 'errors' => [], 'lock' => '0', 'draft' => '', ]; if(!$INFO['writable']) { $response['errors'][] = 'Permission to write this page has been denied.'; echo json_encode($response); return; } if(!checklock($ID)) { lock($ID); $response['lock'] = '1'; } $draft = new Draft($ID, $INFO['client']); if ($draft->saveDraft()) { $response['draft'] = $draft->getDraftMessage(); } else { $response['errors'] = array_merge($response['errors'], $draft->getErrors()); } echo json_encode($response); } /** * Delete a draft * * @author Andreas Gohr <andi@splitbrain.org> */ protected function callDraftdel() { global $INPUT; $id = cleanID($INPUT->str('id')); if(empty($id)) return; $client = $_SERVER['REMOTE_USER']; if(!$client) $client = clientIP(true); $cname = getCacheName($client . $id, '.draft'); @unlink($cname); } /** * Return subnamespaces for the Mediamanager * * @author Andreas Gohr <andi@splitbrain.org> */ protected function callMedians() { global $conf; global $INPUT; // wanted namespace $ns = cleanID($INPUT->post->str('ns')); $dir = utf8_encodeFN(str_replace(':', '/', $ns)); $lvl = count(explode(':', $ns)); $data = array(); search($data, $conf['mediadir'], 'search_index', array('nofiles' => true), $dir); foreach(array_keys($data) as $item) { $data[$item]['level'] = $lvl + 1; } echo html_buildlist($data, 'idx', 'media_nstree_item', 'media_nstree_li'); } /** * Return list of files for the Mediamanager * * @author Andreas Gohr <andi@splitbrain.org> */ protected function callMedialist() { global $NS; global $INPUT; $NS = cleanID($INPUT->post->str('ns')); $sort = $INPUT->post->bool('recent') ? 'date' : 'natural'; if($INPUT->post->str('do') == 'media') { tpl_mediaFileList(); } else { tpl_mediaContent(true, $sort); } } /** * Return the content of the right column * (image details) for the Mediamanager * * @author Kate Arzamastseva <pshns@ukr.net> */ protected function callMediadetails() { global $IMG, $JUMPTO, $REV, $fullscreen, $INPUT; $fullscreen = true; require_once(DOKU_INC . 'lib/exe/mediamanager.php'); $image = ''; if($INPUT->has('image')) $image = cleanID($INPUT->str('image')); if(isset($IMG)) $image = $IMG; if(isset($JUMPTO)) $image = $JUMPTO; $rev = false; if(isset($REV) && !$JUMPTO) $rev = $REV; html_msgarea(); tpl_mediaFileDetails($image, $rev); } /** * Returns image diff representation for mediamanager * * @author Kate Arzamastseva <pshns@ukr.net> */ protected function callMediadiff() { global $NS; global $INPUT; $image = ''; if($INPUT->has('image')) $image = cleanID($INPUT->str('image')); $NS = getNS($image); $auth = auth_quickaclcheck("$NS:*"); media_diff($image, $NS, $auth, true); } /** * Manages file uploads * * @author Kate Arzamastseva <pshns@ukr.net> */ protected function callMediaupload() { global $NS, $MSG, $INPUT; $id = ''; if(isset($_FILES['qqfile']['tmp_name'])) { $id = $INPUT->post->str('mediaid', $_FILES['qqfile']['name']); } elseif($INPUT->get->has('qqfile')) { $id = $INPUT->get->str('qqfile'); } $id = cleanID($id); $NS = $INPUT->str('ns'); $ns = $NS . ':' . getNS($id); $AUTH = auth_quickaclcheck("$ns:*"); if($AUTH >= AUTH_UPLOAD) { io_createNamespace("$ns:xxx", 'media'); } if(isset($_FILES['qqfile']['error']) && $_FILES['qqfile']['error']) unset($_FILES['qqfile']); $res = false; 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) { $result = array( 'success' => true, 'link' => media_managerURL(array('ns' => $ns, 'image' => $NS . ':' . $id), '&'), 'id' => $NS . ':' . $id, 'ns' => $NS ); } else { $error = ''; if(isset($MSG)) { foreach($MSG as $msg) { $error .= $msg['msg']; } } $result = array( 'error' => $error, 'ns' => $NS ); } header('Content-Type: application/json'); echo json_encode($result); } /** * Return sub index for index view * * @author Andreas Gohr <andi@splitbrain.org> */ protected function callIndex() { global $conf; global $INPUT; // wanted namespace $ns = cleanID($INPUT->post->str('idx')); $dir = utf8_encodeFN(str_replace(':', '/', $ns)); $lvl = count(explode(':', $ns)); $data = array(); search($data, $conf['datadir'], 'search_index', array('ns' => $ns), $dir); foreach(array_keys($data) as $item) { $data[$item]['level'] = $lvl + 1; } echo html_buildlist($data, 'idx', 'html_list_index', 'html_li_index'); } /** * List matching namespaces and pages for the link wizard * * @author Andreas Gohr <gohr@cosmocode.de> */ protected function callLinkwiz() { global $conf; global $lang; global $INPUT; $q = ltrim(trim($INPUT->post->str('q')), ':'); $id = noNS($q); $ns = getNS($q); $ns = cleanID($ns); $id = cleanID($id); $nsd = utf8_encodeFN(str_replace(':', '/', $ns)); $data = array(); if($q !== '' && $ns === '') { // use index to lookup matching pages $pages = ft_pageLookup($id, true); // result contains matches in pages and namespaces // we now extract the matching namespaces to show // them seperately $dirs = array(); foreach($pages as $pid => $title) { if(strpos(noNS($pid), $id) === false) { // match was in the namespace $dirs[getNS($pid)] = 1; // assoc array avoids dupes } else { // it is a matching page, add it to the result $data[] = array( 'id' => $pid, 'title' => $title, 'type' => 'f', ); } unset($pages[$pid]); } foreach($dirs as $dir => $junk) { $data[] = array( 'id' => $dir, 'type' => 'd', ); } } else { $opts = array( 'depth' => 1, 'listfiles' => true, 'listdirs' => true, 'pagesonly' => true, 'firsthead' => true, 'sneakyacl' => $conf['sneaky_index'], ); if($id) $opts['filematch'] = '^.*\/' . $id; if($id) $opts['dirmatch'] = '^.*\/' . $id; search($data, $conf['datadir'], 'search_universal', $opts, $nsd); // add back to upper if($ns) { array_unshift( $data, array( 'id' => getNS($ns), 'type' => 'u', ) ); } } // fixme sort results in a useful way ? if(!count($data)) { echo $lang['nothingfound']; exit; } // output the found data $even = 1; foreach($data as $item) { $even *= -1; //zebra if(($item['type'] == 'd' || $item['type'] == 'u') && $item['id'] !== '') $item['id'] .= ':'; $link = wl($item['id']); echo '<div class="' . (($even > 0) ? 'even' : 'odd') . ' type_' . $item['type'] . '">'; if($item['type'] == 'u') { $name = $lang['upperns']; } else { $name = hsc($item['id']); } echo '<a href="' . $link . '" title="' . hsc($item['id']) . '" class="wikilink1">' . $name . '</a>'; if(!blank($item['title'])) { echo '<span>' . hsc($item['title']) . '</span>'; } echo '</div>'; } } } Remote/ApiCore.php 0000644 00000077461 15233462216 0010055 0 ustar 00 <?php namespace dokuwiki\Remote; use Doku_Renderer_xhtml; use dokuwiki\ChangeLog\MediaChangeLog; use dokuwiki\ChangeLog\PageChangeLog; use dokuwiki\Extension\Event; define('DOKU_API_VERSION', 10); /** * Provides the core methods for the remote API. * The methods are ordered in 'wiki.<method>' and 'dokuwiki.<method>' namespaces */ class ApiCore { /** @var int Increased whenever the API is changed */ const API_VERSION = 10; /** @var Api */ private $api; /** * @param Api $api */ public function __construct(Api $api) { $this->api = $api; } /** * Returns details about the core methods * * @return array */ public function __getRemoteInfo() { return array( 'dokuwiki.getVersion' => array( 'args' => array(), 'return' => 'string', 'doc' => 'Returns the running DokuWiki version.' ), 'dokuwiki.login' => array( 'args' => array('string', 'string'), 'return' => 'int', 'doc' => 'Tries to login with the given credentials and sets auth cookies.', 'public' => '1' ), 'dokuwiki.logoff' => array( 'args' => array(), 'return' => 'int', 'doc' => 'Tries to logoff by expiring auth cookies and the associated PHP session.' ), 'dokuwiki.getPagelist' => array( 'args' => array('string', 'array'), 'return' => 'array', 'doc' => 'List all pages within the given namespace.', 'name' => 'readNamespace' ), 'dokuwiki.search' => array( 'args' => array('string'), 'return' => 'array', 'doc' => 'Perform a fulltext search and return a list of matching pages' ), 'dokuwiki.getTime' => array( 'args' => array(), 'return' => 'int', 'doc' => 'Returns the current time at the remote wiki server as Unix timestamp.', ), 'dokuwiki.setLocks' => array( 'args' => array('array'), 'return' => 'array', 'doc' => 'Lock or unlock pages.' ), 'dokuwiki.getTitle' => array( 'args' => array(), 'return' => 'string', 'doc' => 'Returns the wiki title.', 'public' => '1' ), 'dokuwiki.appendPage' => array( 'args' => array('string', 'string', 'array'), 'return' => 'bool', 'doc' => 'Append text to a wiki page.' ), 'dokuwiki.deleteUsers' => array( 'args' => array('array'), 'return' => 'bool', 'doc' => 'Remove one or more users from the list of registered users.' ), 'wiki.getPage' => array( 'args' => array('string'), 'return' => 'string', 'doc' => 'Get the raw Wiki text of page, latest version.', 'name' => 'rawPage', ), 'wiki.getPageVersion' => array( 'args' => array('string', 'int'), 'name' => 'rawPage', 'return' => 'string', 'doc' => 'Return a raw wiki page' ), 'wiki.getPageHTML' => array( 'args' => array('string'), 'return' => 'string', 'doc' => 'Return page in rendered HTML, latest version.', 'name' => 'htmlPage' ), 'wiki.getPageHTMLVersion' => array( 'args' => array('string', 'int'), 'return' => 'string', 'doc' => 'Return page in rendered HTML.', 'name' => 'htmlPage' ), 'wiki.getAllPages' => array( 'args' => array(), 'return' => 'array', 'doc' => 'Returns a list of all pages. The result is an array of utf8 pagenames.', 'name' => 'listPages' ), 'wiki.getAttachments' => array( 'args' => array('string', 'array'), 'return' => 'array', 'doc' => 'Returns a list of all media files.', 'name' => 'listAttachments' ), 'wiki.getBackLinks' => array( 'args' => array('string'), 'return' => 'array', 'doc' => 'Returns the pages that link to this page.', 'name' => 'listBackLinks' ), 'wiki.getPageInfo' => array( 'args' => array('string'), 'return' => 'array', 'doc' => 'Returns a struct with info about the page, latest version.', 'name' => 'pageInfo' ), 'wiki.getPageInfoVersion' => array( 'args' => array('string', 'int'), 'return' => 'array', 'doc' => 'Returns a struct with info about the page.', 'name' => 'pageInfo' ), 'wiki.getPageVersions' => array( 'args' => array('string', 'int'), 'return' => 'array', 'doc' => 'Returns the available revisions of the page.', 'name' => 'pageVersions' ), 'wiki.putPage' => array( 'args' => array('string', 'string', 'array'), 'return' => 'bool', 'doc' => 'Saves a wiki page.' ), 'wiki.listLinks' => array( 'args' => array('string'), 'return' => 'array', 'doc' => 'Lists all links contained in a wiki page.' ), 'wiki.getRecentChanges' => array( 'args' => array('int'), 'return' => 'array', 'Returns a struct about all recent changes since given timestamp.' ), 'wiki.getRecentMediaChanges' => array( 'args' => array('int'), 'return' => 'array', 'Returns a struct about all recent media changes since given timestamp.' ), 'wiki.aclCheck' => array( 'args' => array('string', 'string', 'array'), 'return' => 'int', 'doc' => 'Returns the permissions of a given wiki page. By default, for current user/groups' ), 'wiki.putAttachment' => array( 'args' => array('string', 'file', 'array'), 'return' => 'array', 'doc' => 'Upload a file to the wiki.' ), 'wiki.deleteAttachment' => array( 'args' => array('string'), 'return' => 'int', 'doc' => 'Delete a file from the wiki.' ), 'wiki.getAttachment' => array( 'args' => array('string'), 'doc' => 'Return a media file', 'return' => 'file', 'name' => 'getAttachment', ), 'wiki.getAttachmentInfo' => array( 'args' => array('string'), 'return' => 'array', 'doc' => 'Returns a struct with info about the attachment.' ), 'dokuwiki.getXMLRPCAPIVersion' => array( 'args' => array(), 'name' => 'getAPIVersion', 'return' => 'int', 'doc' => 'Returns the XMLRPC API version.', 'public' => '1', ), 'wiki.getRPCVersionSupported' => array( 'args' => array(), 'name' => 'wikiRpcVersion', 'return' => 'int', 'doc' => 'Returns 2 with the supported RPC API version.', 'public' => '1' ), ); } /** * @return string */ public function getVersion() { return getVersion(); } /** * @return int unix timestamp */ public function getTime() { return time(); } /** * Return a raw wiki page * * @param string $id wiki page id * @param int|string $rev revision timestamp of the page or empty string * @return string page text. * @throws AccessDeniedException if no permission for page */ public function rawPage($id, $rev = '') { $id = $this->resolvePageId($id); if (auth_quickaclcheck($id) < AUTH_READ) { throw new AccessDeniedException('You are not allowed to read this file', 111); } $text = rawWiki($id, $rev); if (!$text) { return pageTemplate($id); } else { return $text; } } /** * Return a media file * * @author Gina Haeussge <osd@foosel.net> * * @param string $id file id * @return mixed media file * @throws AccessDeniedException no permission for media * @throws RemoteException not exist */ public function getAttachment($id) { $id = cleanID($id); if (auth_quickaclcheck(getNS($id) . ':*') < AUTH_READ) { throw new AccessDeniedException('You are not allowed to read this file', 211); } $file = mediaFN($id); if (!@ file_exists($file)) { throw new RemoteException('The requested file does not exist', 221); } $data = io_readFile($file, false); return $this->api->toFile($data); } /** * Return info about a media file * * @author Gina Haeussge <osd@foosel.net> * * @param string $id page id * @return array */ public function getAttachmentInfo($id) { $id = cleanID($id); $info = array( 'lastModified' => $this->api->toDate(0), 'size' => 0, ); $file = mediaFN($id); if (auth_quickaclcheck(getNS($id) . ':*') >= AUTH_READ) { if (file_exists($file)) { $info['lastModified'] = $this->api->toDate(filemtime($file)); $info['size'] = filesize($file); } else { //Is it deleted media with changelog? $medialog = new MediaChangeLog($id); $revisions = $medialog->getRevisions(0, 1); if (!empty($revisions)) { $info['lastModified'] = $this->api->toDate($revisions[0]); } } } return $info; } /** * Return a wiki page rendered to html * * @param string $id page id * @param string|int $rev revision timestamp or empty string * @return null|string html * @throws AccessDeniedException no access to page */ public function htmlPage($id, $rev = '') { $id = $this->resolvePageId($id); if (auth_quickaclcheck($id) < AUTH_READ) { throw new AccessDeniedException('You are not allowed to read this page', 111); } return p_wiki_xhtml($id, $rev, false); } /** * List all pages - we use the indexer list here * * @return array */ public function listPages() { $list = array(); $pages = idx_get_indexer()->getPages(); $pages = array_filter(array_filter($pages, 'isVisiblePage'), 'page_exists'); foreach (array_keys($pages) as $idx) { $perm = auth_quickaclcheck($pages[$idx]); if ($perm < AUTH_READ) { continue; } $page = array(); $page['id'] = trim($pages[$idx]); $page['perms'] = $perm; $page['size'] = @filesize(wikiFN($pages[$idx])); $page['lastModified'] = $this->api->toDate(@filemtime(wikiFN($pages[$idx]))); $list[] = $page; } return $list; } /** * List all pages in the given namespace (and below) * * @param string $ns * @param array $opts * $opts['depth'] recursion level, 0 for all * $opts['hash'] do md5 sum of content? * @return array */ public function readNamespace($ns, $opts = array()) { global $conf; if (!is_array($opts)) $opts = array(); $ns = cleanID($ns); $dir = utf8_encodeFN(str_replace(':', '/', $ns)); $data = array(); $opts['skipacl'] = 0; // no ACL skipping for XMLRPC search($data, $conf['datadir'], 'search_allpages', $opts, $dir); return $data; } /** * List all pages in the given namespace (and below) * * @param string $query * @return array */ public function search($query) { $regex = array(); $data = ft_pageSearch($query, $regex); $pages = array(); // prepare additional data $idx = 0; foreach ($data as $id => $score) { $file = wikiFN($id); if ($idx < FT_SNIPPET_NUMBER) { $snippet = ft_snippet($id, $regex); $idx++; } else { $snippet = ''; } $pages[] = array( 'id' => $id, 'score' => intval($score), 'rev' => filemtime($file), 'mtime' => filemtime($file), 'size' => filesize($file), 'snippet' => $snippet, 'title' => useHeading('navigation') ? p_get_first_heading($id) : $id ); } return $pages; } /** * Returns the wiki title. * * @return string */ public function getTitle() { global $conf; return $conf['title']; } /** * List all media files. * * Available options are 'recursive' for also including the subnamespaces * in the listing, and 'pattern' for filtering the returned files against * a regular expression matching their name. * * @author Gina Haeussge <osd@foosel.net> * * @param string $ns * @param array $options * $options['depth'] recursion level, 0 for all * $options['showmsg'] shows message if invalid media id is used * $options['pattern'] check given pattern * $options['hash'] add hashes to result list * @return array * @throws AccessDeniedException no access to the media files */ public function listAttachments($ns, $options = array()) { global $conf; $ns = cleanID($ns); if (!is_array($options)) $options = array(); $options['skipacl'] = 0; // no ACL skipping for XMLRPC if (auth_quickaclcheck($ns . ':*') >= AUTH_READ) { $dir = utf8_encodeFN(str_replace(':', '/', $ns)); $data = array(); search($data, $conf['mediadir'], 'search_media', $options, $dir); $len = count($data); if (!$len) return array(); for ($i = 0; $i < $len; $i++) { unset($data[$i]['meta']); $data[$i]['perms'] = $data[$i]['perm']; unset($data[$i]['perm']); $data[$i]['lastModified'] = $this->api->toDate($data[$i]['mtime']); } return $data; } else { throw new AccessDeniedException('You are not allowed to list media files.', 215); } } /** * Return a list of backlinks * * @param string $id page id * @return array */ public function listBackLinks($id) { return ft_backlinks($this->resolvePageId($id)); } /** * Return some basic data about a page * * @param string $id page id * @param string|int $rev revision timestamp or empty string * @return array * @throws AccessDeniedException no access for page * @throws RemoteException page not exist */ public function pageInfo($id, $rev = '') { $id = $this->resolvePageId($id); if (auth_quickaclcheck($id) < AUTH_READ) { throw new AccessDeniedException('You are not allowed to read this page', 111); } $file = wikiFN($id, $rev); $time = @filemtime($file); if (!$time) { throw new RemoteException('The requested page does not exist', 121); } // set revision to current version if empty, use revision otherwise // as the timestamps of old files are not necessarily correct if ($rev === '') { $rev = $time; } $pagelog = new PageChangeLog($id, 1024); $info = $pagelog->getRevisionInfo($rev); $data = array( 'name' => $id, 'lastModified' => $this->api->toDate($rev), 'author' => is_array($info) ? (($info['user']) ? $info['user'] : $info['ip']) : null, 'version' => $rev ); return ($data); } /** * Save a wiki page * * @author Michael Klier <chi@chimeric.de> * * @param string $id page id * @param string $text wiki text * @param array $params parameters: summary, minor edit * @return bool * @throws AccessDeniedException no write access for page * @throws RemoteException no id, empty new page or locked */ public function putPage($id, $text, $params = array()) { global $TEXT; global $lang; $id = $this->resolvePageId($id); $TEXT = cleanText($text); $sum = $params['sum']; $minor = $params['minor']; if (empty($id)) { throw new RemoteException('Empty page ID', 131); } if (!page_exists($id) && trim($TEXT) == '') { throw new RemoteException('Refusing to write an empty new wiki page', 132); } if (auth_quickaclcheck($id) < AUTH_EDIT) { throw new AccessDeniedException('You are not allowed to edit this page', 112); } // Check, if page is locked if (checklock($id)) { throw new RemoteException('The page is currently locked', 133); } // SPAM check if (checkwordblock()) { throw new RemoteException('Positive wordblock check', 134); } // autoset summary on new pages if (!page_exists($id) && empty($sum)) { $sum = $lang['created']; } // autoset summary on deleted pages if (page_exists($id) && empty($TEXT) && empty($sum)) { $sum = $lang['deleted']; } lock($id); saveWikiText($id, $TEXT, $sum, $minor); unlock($id); // run the indexer if page wasn't indexed yet idx_addPage($id); return true; } /** * Appends text to a wiki page. * * @param string $id page id * @param string $text wiki text * @param array $params such as summary,minor * @return bool|string * @throws RemoteException */ public function appendPage($id, $text, $params = array()) { $currentpage = $this->rawPage($id); if (!is_string($currentpage)) { return $currentpage; } return $this->putPage($id, $currentpage . $text, $params); } /** * Remove one or more users from the list of registered users * * @param string[] $usernames List of usernames to remove * * @return bool * * @throws AccessDeniedException */ public function deleteUsers($usernames) { if (!auth_isadmin()) { throw new AccessDeniedException('Only admins are allowed to delete users', 114); } /** @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; return (bool)$auth->triggerUserMod('delete', array($usernames)); } /** * Uploads a file to the wiki. * * Michael Klier <chi@chimeric.de> * * @param string $id page id * @param string $file * @param array $params such as overwrite * @return false|string * @throws RemoteException */ public function putAttachment($id, $file, $params = array()) { $id = cleanID($id); $auth = auth_quickaclcheck(getNS($id) . ':*'); if (!isset($id)) { throw new RemoteException('Filename not given.', 231); } global $conf; $ftmp = $conf['tmpdir'] . '/' . md5($id . clientIP()); // save temporary file @unlink($ftmp); io_saveFile($ftmp, $file); $res = media_save(array('name' => $ftmp), $id, $params['ow'], $auth, 'rename'); if (is_array($res)) { throw new RemoteException($res[0], -$res[1]); } else { return $res; } } /** * Deletes a file from the wiki. * * @author Gina Haeussge <osd@foosel.net> * * @param string $id page id * @return int * @throws AccessDeniedException no permissions * @throws RemoteException file in use or not deleted */ public function deleteAttachment($id) { $id = cleanID($id); $auth = auth_quickaclcheck(getNS($id) . ':*'); $res = media_delete($id, $auth); if ($res & DOKU_MEDIA_DELETED) { return 0; } elseif ($res & DOKU_MEDIA_NOT_AUTH) { throw new AccessDeniedException('You don\'t have permissions to delete files.', 212); } elseif ($res & DOKU_MEDIA_INUSE) { throw new RemoteException('File is still referenced', 232); } else { throw new RemoteException('Could not delete file', 233); } } /** * Returns the permissions of a given wiki page for the current user or another user * * @param string $id page id * @param string|null $user username * @param array|null $groups array of groups * @return int permission level */ public function aclCheck($id, $user = null, $groups = null) { /** @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; $id = $this->resolvePageId($id); if ($user === null) { return auth_quickaclcheck($id); } else { if ($groups === null) { $userinfo = $auth->getUserData($user); if ($userinfo === false) { $groups = array(); } else { $groups = $userinfo['grps']; } } return auth_aclcheck($id, $user, $groups); } } /** * Lists all links contained in a wiki page * * @author Michael Klier <chi@chimeric.de> * * @param string $id page id * @return array * @throws AccessDeniedException no read access for page */ public function listLinks($id) { $id = $this->resolvePageId($id); if (auth_quickaclcheck($id) < AUTH_READ) { throw new AccessDeniedException('You are not allowed to read this page', 111); } $links = array(); // resolve page instructions $ins = p_cached_instructions(wikiFN($id)); // instantiate new Renderer - needed for interwiki links $Renderer = new Doku_Renderer_xhtml(); $Renderer->interwiki = getInterwiki(); // parse parse instructions foreach ($ins as $in) { $link = array(); switch ($in[0]) { case 'internallink': $link['type'] = 'local'; $link['page'] = $in[1][0]; $link['href'] = wl($in[1][0]); array_push($links, $link); break; case 'externallink': $link['type'] = 'extern'; $link['page'] = $in[1][0]; $link['href'] = $in[1][0]; array_push($links, $link); break; case 'interwikilink': $url = $Renderer->_resolveInterWiki($in[1][2], $in[1][3]); $link['type'] = 'extern'; $link['page'] = $url; $link['href'] = $url; array_push($links, $link); break; } } return ($links); } /** * Returns a list of recent changes since give timestamp * * @author Michael Hamann <michael@content-space.de> * @author Michael Klier <chi@chimeric.de> * * @param int $timestamp unix timestamp * @return array * @throws RemoteException no valid timestamp */ public function getRecentChanges($timestamp) { if (strlen($timestamp) != 10) { throw new RemoteException('The provided value is not a valid timestamp', 311); } $recents = getRecentsSince($timestamp); $changes = array(); foreach ($recents as $recent) { $change = array(); $change['name'] = $recent['id']; $change['lastModified'] = $this->api->toDate($recent['date']); $change['author'] = $recent['user']; $change['version'] = $recent['date']; $change['perms'] = $recent['perms']; $change['size'] = @filesize(wikiFN($recent['id'])); array_push($changes, $change); } if (!empty($changes)) { return $changes; } else { // in case we still have nothing at this point throw new RemoteException('There are no changes in the specified timeframe', 321); } } /** * Returns a list of recent media changes since give timestamp * * @author Michael Hamann <michael@content-space.de> * @author Michael Klier <chi@chimeric.de> * * @param int $timestamp unix timestamp * @return array * @throws RemoteException no valid timestamp */ public function getRecentMediaChanges($timestamp) { if (strlen($timestamp) != 10) throw new RemoteException('The provided value is not a valid timestamp', 311); $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES); $changes = array(); foreach ($recents as $recent) { $change = array(); $change['name'] = $recent['id']; $change['lastModified'] = $this->api->toDate($recent['date']); $change['author'] = $recent['user']; $change['version'] = $recent['date']; $change['perms'] = $recent['perms']; $change['size'] = @filesize(mediaFN($recent['id'])); array_push($changes, $change); } if (!empty($changes)) { return $changes; } else { // in case we still have nothing at this point throw new RemoteException('There are no changes in the specified timeframe', 321); } } /** * Returns a list of available revisions of a given wiki page * Number of returned pages is set by $conf['recent'] * However not accessible pages are skipped, so less than $conf['recent'] could be returned * * @author Michael Klier <chi@chimeric.de> * * @param string $id page id * @param int $first skip the first n changelog lines * 0 = from current(if exists) * 1 = from 1st old rev * 2 = from 2nd old rev, etc * @return array * @throws AccessDeniedException no read access for page * @throws RemoteException empty id */ public function pageVersions($id, $first = 0) { $id = $this->resolvePageId($id); if (auth_quickaclcheck($id) < AUTH_READ) { throw new AccessDeniedException('You are not allowed to read this page', 111); } global $conf; $versions = array(); if (empty($id)) { throw new RemoteException('Empty page ID', 131); } $first = (int) $first; $first_rev = $first - 1; $first_rev = $first_rev < 0 ? 0 : $first_rev; $pagelog = new PageChangeLog($id); $revisions = $pagelog->getRevisions($first_rev, $conf['recent']); if ($first == 0) { array_unshift($revisions, ''); // include current revision if (count($revisions) > $conf['recent']) { array_pop($revisions); // remove extra log entry } } if (!empty($revisions)) { foreach ($revisions as $rev) { $file = wikiFN($id, $rev); $time = @filemtime($file); // we check if the page actually exists, if this is not the // case this can lead to less pages being returned than // specified via $conf['recent'] if ($time) { $pagelog->setChunkSize(1024); $info = $pagelog->getRevisionInfo($rev ? $rev : $time); if (!empty($info)) { $data = array(); $data['user'] = $info['user']; $data['ip'] = $info['ip']; $data['type'] = $info['type']; $data['sum'] = $info['sum']; $data['modified'] = $this->api->toDate($info['date']); $data['version'] = $info['date']; array_push($versions, $data); } } } return $versions; } else { return array(); } } /** * The version of Wiki RPC API supported */ public function wikiRpcVersion() { return 2; } /** * Locks or unlocks a given batch of pages * * Give an associative array with two keys: lock and unlock. Both should contain a * list of pages to lock or unlock * * Returns an associative array with the keys locked, lockfail, unlocked and * unlockfail, each containing lists of pages. * * @param array[] $set list pages with array('lock' => array, 'unlock' => array) * @return array */ public function setLocks($set) { $locked = array(); $lockfail = array(); $unlocked = array(); $unlockfail = array(); foreach ((array) $set['lock'] as $id) { $id = $this->resolvePageId($id); if (auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)) { $lockfail[] = $id; } else { lock($id); $locked[] = $id; } } foreach ((array) $set['unlock'] as $id) { $id = $this->resolvePageId($id); if (auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)) { $unlockfail[] = $id; } else { $unlocked[] = $id; } } return array( 'locked' => $locked, 'lockfail' => $lockfail, 'unlocked' => $unlocked, 'unlockfail' => $unlockfail, ); } /** * Return API version * * @return int */ public function getAPIVersion() { return self::API_VERSION; } /** * Login * * @param string $user * @param string $pass * @return int */ public function login($user, $pass) { global $conf; /** @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; if (!$conf['useacl']) return 0; if (!$auth) return 0; @session_start(); // reopen session for login $ok = null; if ($auth->canDo('external')) { $ok = $auth->trustExternal($user, $pass, false); } if ($ok === null){ $evdata = array( 'user' => $user, 'password' => $pass, 'sticky' => false, 'silent' => true, ); $ok = Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper'); } session_write_close(); // we're done with the session return $ok; } /** * Log off * * @return int */ public function logoff() { global $conf; global $auth; if (!$conf['useacl']) return 0; if (!$auth) return 0; auth_logoff(); return 1; } /** * Resolve page id * * @param string $id page id * @return string */ private function resolvePageId($id) { $id = cleanID($id); if (empty($id)) { global $conf; $id = cleanID($conf['start']); } return $id; } } Remote/XmlRpcServer.php 0000644 00000003226 15233462216 0011113 0 ustar 00 <?php namespace dokuwiki\Remote; /** * Contains needed wrapper functions and registers all available XMLRPC functions. */ class XmlRpcServer extends \IXR_Server { protected $remote; /** * Constructor. Register methods and run Server */ public function __construct($wait=false) { $this->remote = new Api(); $this->remote->setDateTransformation(array($this, 'toDate')); $this->remote->setFileTransformation(array($this, 'toFile')); parent::__construct(false, false, $wait); } /** * @inheritdoc */ public function call($methodname, $args) { try { $result = $this->remote->call($methodname, $args); return $result; } /** @noinspection PhpRedundantCatchClauseInspection */ catch (AccessDeniedException $e) { if (!isset($_SERVER['REMOTE_USER'])) { http_status(401); return new \IXR_Error(-32603, "server error. not authorized to call method $methodname"); } else { http_status(403); return new \IXR_Error(-32604, "server error. forbidden to call the method $methodname"); } } catch (RemoteException $e) { return new \IXR_Error($e->getCode(), $e->getMessage()); } } /** * @param string|int $data iso date(yyyy[-]mm[-]dd[ hh:mm[:ss]]) or timestamp * @return \IXR_Date */ public function toDate($data) { return new \IXR_Date($data); } /** * @param string $data * @return \IXR_Base64 */ public function toFile($data) { return new \IXR_Base64($data); } } Remote/AccessDeniedException.php 0000644 00000000202 15233462216 0012677 0 ustar 00 <?php namespace dokuwiki\Remote; /** * Class AccessDeniedException */ class AccessDeniedException extends RemoteException { } Remote/RemoteException.php 0000644 00000000161 15233462216 0011624 0 ustar 00 <?php namespace dokuwiki\Remote; /** * Class RemoteException */ class RemoteException extends \Exception { } Remote/Api.php 0000644 00000030070 15233462216 0007225 0 ustar 00 <?php namespace dokuwiki\Remote; use dokuwiki\Extension\Event; use dokuwiki\Extension\RemotePlugin; /** * This class provides information about remote access to the wiki. * * == Types of methods == * There are two types of remote methods. The first is the core methods. * These are always available and provided by dokuwiki. * The other is plugin methods. These are provided by remote plugins. * * == Information structure == * The information about methods will be given in an array with the following structure: * array( * 'method.remoteName' => array( * 'args' => array( * 'type eg. string|int|...|date|file', * ) * 'name' => 'method name in class', * 'return' => 'type', * 'public' => 1/0 - method bypass default group check (used by login) * ['doc' = 'method documentation'], * ) * ) * * plugin names are formed the following: * core methods begin by a 'dokuwiki' or 'wiki' followed by a . and the method name itself. * i.e.: dokuwiki.version or wiki.getPage * * plugin methods are formed like 'plugin.<plugin name>.<method name>'. * i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime */ class Api { /** * @var ApiCore */ private $coreMethods = null; /** * @var array remote methods provided by dokuwiki plugins - will be filled lazy via * {@see dokuwiki\Remote\RemoteAPI#getPluginMethods} */ private $pluginMethods = null; /** * @var array contains custom calls to the api. Plugins can use the XML_CALL_REGISTER event. * The data inside is 'custom.call.something' => array('plugin name', 'remote method name') * * The remote method name is the same as in the remote name returned by _getMethods(). */ private $pluginCustomCalls = null; private $dateTransformation; private $fileTransformation; /** * constructor */ public function __construct() { $this->dateTransformation = array($this, 'dummyTransformation'); $this->fileTransformation = array($this, 'dummyTransformation'); } /** * Get all available methods with remote access. * * @return array with information to all available methods * @throws RemoteException */ public function getMethods() { return array_merge($this->getCoreMethods(), $this->getPluginMethods()); } /** * Call a method via remote api. * * @param string $method name of the method to call. * @param array $args arguments to pass to the given method * @return mixed result of method call, must be a primitive type. * @throws RemoteException */ public function call($method, $args = array()) { if ($args === null) { $args = array(); } list($type, $pluginName, /* $call */) = explode('.', $method, 3); if ($type === 'plugin') { return $this->callPlugin($pluginName, $method, $args); } if ($this->coreMethodExist($method)) { return $this->callCoreMethod($method, $args); } return $this->callCustomCallPlugin($method, $args); } /** * Check existance of core methods * * @param string $name name of the method * @return bool if method exists */ private function coreMethodExist($name) { $coreMethods = $this->getCoreMethods(); return array_key_exists($name, $coreMethods); } /** * Try to call custom methods provided by plugins * * @param string $method name of method * @param array $args * @return mixed * @throws RemoteException if method not exists */ private function callCustomCallPlugin($method, $args) { $customCalls = $this->getCustomCallPlugins(); if (!array_key_exists($method, $customCalls)) { throw new RemoteException('Method does not exist', -32603); } $customCall = $customCalls[$method]; return $this->callPlugin($customCall[0], $customCall[1], $args); } /** * Returns plugin calls that are registered via RPC_CALL_ADD action * * @return array with pairs of custom plugin calls * @triggers RPC_CALL_ADD */ private function getCustomCallPlugins() { if ($this->pluginCustomCalls === null) { $data = array(); Event::createAndTrigger('RPC_CALL_ADD', $data); $this->pluginCustomCalls = $data; } return $this->pluginCustomCalls; } /** * Call a plugin method * * @param string $pluginName * @param string $method method name * @param array $args * @return mixed return of custom method * @throws RemoteException */ private function callPlugin($pluginName, $method, $args) { $plugin = plugin_load('remote', $pluginName); $methods = $this->getPluginMethods(); if (!$plugin) { throw new RemoteException('Method does not exist', -32603); } $this->checkAccess($methods[$method]); $name = $this->getMethodName($methods, $method); try { set_error_handler(array($this, "argumentWarningHandler"), E_WARNING); // for PHP <7.1 return call_user_func_array(array($plugin, $name), $args); } catch (\ArgumentCountError $th) { throw new RemoteException('Method does not exist - wrong parameter count.', -32603); } finally { restore_error_handler(); } } /** * Call a core method * * @param string $method name of method * @param array $args * @return mixed * @throws RemoteException if method not exist */ private function callCoreMethod($method, $args) { $coreMethods = $this->getCoreMethods(); $this->checkAccess($coreMethods[$method]); if (!isset($coreMethods[$method])) { throw new RemoteException('Method does not exist', -32603); } $this->checkArgumentLength($coreMethods[$method], $args); try { set_error_handler(array($this, "argumentWarningHandler"), E_WARNING); // for PHP <7.1 return call_user_func_array(array($this->coreMethods, $this->getMethodName($coreMethods, $method)), $args); } catch (\ArgumentCountError $th) { throw new RemoteException('Method does not exist - wrong parameter count.', -32603); } finally { restore_error_handler(); } } /** * Check if access should be checked * * @param array $methodMeta data about the method * @throws AccessDeniedException */ private function checkAccess($methodMeta) { if (!isset($methodMeta['public'])) { $this->forceAccess(); } else { if ($methodMeta['public'] == '0') { $this->forceAccess(); } } } /** * Check the number of parameters * * @param array $methodMeta data about the method * @param array $args * @throws RemoteException if wrong parameter count */ private function checkArgumentLength($methodMeta, $args) { if (count($methodMeta['args']) < count($args)) { throw new RemoteException('Method does not exist - wrong parameter count.', -32603); } } /** * Determine the name of the real method * * @param array $methodMeta list of data of the methods * @param string $method name of method * @return string */ private function getMethodName($methodMeta, $method) { if (isset($methodMeta[$method]['name'])) { return $methodMeta[$method]['name']; } $method = explode('.', $method); return $method[count($method) - 1]; } /** * Perform access check for current user * * @return bool true if the current user has access to remote api. * @throws AccessDeniedException If remote access disabled */ public function hasAccess() { global $conf; global $USERINFO; /** @var \dokuwiki\Input\Input $INPUT */ global $INPUT; if (!$conf['remote']) { throw new AccessDeniedException('server error. RPC server not enabled.', -32604); } if (trim($conf['remoteuser']) == '!!not set!!') { return false; } if (!$conf['useacl']) { return true; } if (trim($conf['remoteuser']) == '') { return true; } return auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array) $USERINFO['grps']); } /** * Requests access * * @return void * @throws AccessDeniedException On denied access. */ public function forceAccess() { if (!$this->hasAccess()) { throw new AccessDeniedException('server error. not authorized to call method', -32604); } } /** * Collects all the methods of the enabled Remote Plugins * * @return array all plugin methods. * @throws RemoteException if not implemented */ public function getPluginMethods() { if ($this->pluginMethods === null) { $this->pluginMethods = array(); $plugins = plugin_list('remote'); foreach ($plugins as $pluginName) { /** @var RemotePlugin $plugin */ $plugin = plugin_load('remote', $pluginName); if (!is_subclass_of($plugin, 'dokuwiki\Extension\RemotePlugin')) { throw new RemoteException( "Plugin $pluginName does not implement dokuwiki\Plugin\DokuWiki_Remote_Plugin" ); } try { $methods = $plugin->_getMethods(); } catch (\ReflectionException $e) { throw new RemoteException('Automatic aggregation of available remote methods failed', 0, $e); } foreach ($methods as $method => $meta) { $this->pluginMethods["plugin.$pluginName.$method"] = $meta; } } } return $this->pluginMethods; } /** * Collects all the core methods * * @param ApiCore $apiCore this parameter is used for testing. Here you can pass a non-default RemoteAPICore * instance. (for mocking) * @return array all core methods. */ public function getCoreMethods($apiCore = null) { if ($this->coreMethods === null) { if ($apiCore === null) { $this->coreMethods = new ApiCore($this); } else { $this->coreMethods = $apiCore; } } return $this->coreMethods->__getRemoteInfo(); } /** * Transform file to xml * * @param mixed $data * @return mixed */ public function toFile($data) { return call_user_func($this->fileTransformation, $data); } /** * Transform date to xml * * @param mixed $data * @return mixed */ public function toDate($data) { return call_user_func($this->dateTransformation, $data); } /** * A simple transformation * * @param mixed $data * @return mixed */ public function dummyTransformation($data) { return $data; } /** * Set the transformer function * * @param callback $dateTransformation */ public function setDateTransformation($dateTransformation) { $this->dateTransformation = $dateTransformation; } /** * Set the transformer function * * @param callback $fileTransformation */ public function setFileTransformation($fileTransformation) { $this->fileTransformation = $fileTransformation; } /** * The error handler that catches argument-related warnings */ public function argumentWarningHandler($errno, $errstr) { if (substr($errstr, 0, 17) == 'Missing argument ') { throw new RemoteException('Method does not exist - wrong parameter count.', -32603); } } } utf8.php 0000644 00000016465 15233462216 0006163 0 ustar 00 <?php /** * UTF8 helper functions * * This file now only intitializes the UTF-8 capability detection and defines helper * functions if needed. All actual code is in the \dokuwiki\Utf8 classes * * @author Andreas Gohr <andi@splitbrain.org> */ use dokuwiki\Utf8\Clean; use dokuwiki\Utf8\Conversion; use dokuwiki\Utf8\PhpString; use dokuwiki\Utf8\Unicode; /** * check for mb_string support */ if (!defined('UTF8_MBSTRING')) { if (function_exists('mb_substr') && !defined('UTF8_NOMBSTRING')) { define('UTF8_MBSTRING', 1); } else { define('UTF8_MBSTRING', 0); } } /** * Check if PREG was compiled with UTF-8 support * * Without this many of the functions below will not work, so this is a minimal requirement */ if (!defined('UTF8_PREGSUPPORT')) { define('UTF8_PREGSUPPORT', (bool)@preg_match('/^.$/u', 'ñ')); } /** * Check if PREG was compiled with Unicode Property support * * This is not required for the functions below, but might be needed in a UTF-8 aware application */ if (!defined('UTF8_PROPERTYSUPPORT')) { define('UTF8_PROPERTYSUPPORT', (bool)@preg_match('/^\pL$/u', 'ñ')); } if (UTF8_MBSTRING) { mb_internal_encoding('UTF-8'); } if (!function_exists('utf8_isASCII')) { /** @deprecated 2019-06-09 */ function utf8_isASCII($str) { dbg_deprecated(Clean::class . '::isASCII()'); return Clean::isASCII($str); } } if (!function_exists('utf8_strip')) { /** @deprecated 2019-06-09 */ function utf8_strip($str) { dbg_deprecated(Clean::class . '::strip()'); return Clean::strip($str); } } if (!function_exists('utf8_check')) { /** @deprecated 2019-06-09 */ function utf8_check($str) { dbg_deprecated(Clean::class . '::isUtf8()'); return Clean::isUtf8($str); } } if (!function_exists('utf8_basename')) { /** @deprecated 2019-06-09 */ function utf8_basename($path, $suffix = '') { dbg_deprecated(PhpString::class . '::basename()'); return PhpString::basename($path, $suffix); } } if (!function_exists('utf8_strlen')) { /** @deprecated 2019-06-09 */ function utf8_strlen($str) { dbg_deprecated(PhpString::class . '::strlen()'); return PhpString::strlen($str); } } if (!function_exists('utf8_substr')) { /** @deprecated 2019-06-09 */ function utf8_substr($str, $offset, $length = null) { dbg_deprecated(PhpString::class . '::substr()'); return PhpString::substr($str, $offset, $length); } } if (!function_exists('utf8_substr_replace')) { /** @deprecated 2019-06-09 */ function utf8_substr_replace($string, $replacement, $start, $length = 0) { dbg_deprecated(PhpString::class . '::substr_replace()'); return PhpString::substr_replace($string, $replacement, $start, $length); } } if (!function_exists('utf8_ltrim')) { /** @deprecated 2019-06-09 */ function utf8_ltrim($str, $charlist = '') { dbg_deprecated(PhpString::class . '::ltrim()'); return PhpString::ltrim($str, $charlist); } } if (!function_exists('utf8_rtrim')) { /** @deprecated 2019-06-09 */ function utf8_rtrim($str, $charlist = '') { dbg_deprecated(PhpString::class . '::rtrim()'); return PhpString::rtrim($str, $charlist); } } if (!function_exists('utf8_trim')) { /** @deprecated 2019-06-09 */ function utf8_trim($str, $charlist = '') { dbg_deprecated(PhpString::class . '::trim()'); return PhpString::trim($str, $charlist); } } if (!function_exists('utf8_strtolower')) { /** @deprecated 2019-06-09 */ function utf8_strtolower($str) { dbg_deprecated(PhpString::class . '::strtolower()'); return PhpString::strtolower($str); } } if (!function_exists('utf8_strtoupper')) { /** @deprecated 2019-06-09 */ function utf8_strtoupper($str) { dbg_deprecated(PhpString::class . '::strtoupper()'); return PhpString::strtoupper($str); } } if (!function_exists('utf8_ucfirst')) { /** @deprecated 2019-06-09 */ function utf8_ucfirst($str) { dbg_deprecated(PhpString::class . '::ucfirst()'); return PhpString::ucfirst($str); } } if (!function_exists('utf8_ucwords')) { /** @deprecated 2019-06-09 */ function utf8_ucwords($str) { dbg_deprecated(PhpString::class . '::ucwords()'); return PhpString::ucwords($str); } } if (!function_exists('utf8_deaccent')) { /** @deprecated 2019-06-09 */ function utf8_deaccent($str, $case = 0) { dbg_deprecated(Clean::class . '::deaccent()'); return Clean::deaccent($str, $case); } } if (!function_exists('utf8_romanize')) { /** @deprecated 2019-06-09 */ function utf8_romanize($str) { dbg_deprecated(Clean::class . '::romanize()'); return Clean::romanize($str); } } if (!function_exists('utf8_stripspecials')) { /** @deprecated 2019-06-09 */ function utf8_stripspecials($str, $repl = '', $additional = '') { dbg_deprecated(Clean::class . '::stripspecials()'); return Clean::stripspecials($str, $repl, $additional); } } if (!function_exists('utf8_strpos')) { /** @deprecated 2019-06-09 */ function utf8_strpos($haystack, $needle, $offset = 0) { dbg_deprecated(PhpString::class . '::strpos()'); return PhpString::strpos($haystack, $needle, $offset); } } if (!function_exists('utf8_tohtml')) { /** @deprecated 2019-06-09 */ function utf8_tohtml($str, $all = false) { dbg_deprecated(Conversion::class . '::toHtml()'); return Conversion::toHtml($str, $all); } } if (!function_exists('utf8_unhtml')) { /** @deprecated 2019-06-09 */ function utf8_unhtml($str, $enties = false) { dbg_deprecated(Conversion::class . '::fromHtml()'); return Conversion::fromHtml($str, $enties); } } if (!function_exists('utf8_to_unicode')) { /** @deprecated 2019-06-09 */ function utf8_to_unicode($str, $strict = false) { dbg_deprecated(Unicode::class . '::fromUtf8()'); return Unicode::fromUtf8($str, $strict); } } if (!function_exists('unicode_to_utf8')) { /** @deprecated 2019-06-09 */ function unicode_to_utf8($arr, $strict = false) { dbg_deprecated(Unicode::class . '::toUtf8()'); return Unicode::toUtf8($arr, $strict); } } if (!function_exists('utf8_to_utf16be')) { /** @deprecated 2019-06-09 */ function utf8_to_utf16be($str, $bom = false) { dbg_deprecated(Conversion::class . '::toUtf16be()'); return Conversion::toUtf16be($str, $bom); } } if (!function_exists('utf16be_to_utf8')) { /** @deprecated 2019-06-09 */ function utf16be_to_utf8($str) { dbg_deprecated(Conversion::class . '::fromUtf16be()'); return Conversion::fromUtf16be($str); } } if (!function_exists('utf8_bad_replace')) { /** @deprecated 2019-06-09 */ function utf8_bad_replace($str, $replace = '') { dbg_deprecated(Clean::class . '::replaceBadBytes()'); return Clean::replaceBadBytes($str, $replace); } } if (!function_exists('utf8_correctIdx')) { /** @deprecated 2019-06-09 */ function utf8_correctIdx($str, $i, $next = false) { dbg_deprecated(Clean::class . '::correctIdx()'); return Clean::correctIdx($str, $i, $next); } } defines.php 0000644 00000002615 15233462216 0006702 0 ustar 00 <?php /** * Set up globally available constants */ /** * Auth Levels * @file inc/auth.php */ define('AUTH_NONE', 0); define('AUTH_READ', 1); define('AUTH_EDIT', 2); define('AUTH_CREATE', 4); define('AUTH_UPLOAD', 8); define('AUTH_DELETE', 16); define('AUTH_ADMIN', 255); /** * Message types * @see msg() */ define('MSG_PUBLIC', 0); define('MSG_USERS_ONLY', 1); define('MSG_MANAGERS_ONLY', 2); define('MSG_ADMINS_ONLY', 4); /** * Lexer constants * @see \dokuwiki\Parsing\Lexer\Lexer */ define('DOKU_LEXER_ENTER', 1); define('DOKU_LEXER_MATCHED', 2); define('DOKU_LEXER_UNMATCHED', 3); define('DOKU_LEXER_EXIT', 4); define('DOKU_LEXER_SPECIAL', 5); /** * Constants for known core changelog line types. * @file inc/changelog.php */ define('DOKU_CHANGE_TYPE_CREATE', 'C'); define('DOKU_CHANGE_TYPE_EDIT', 'E'); define('DOKU_CHANGE_TYPE_MINOR_EDIT', 'e'); define('DOKU_CHANGE_TYPE_DELETE', 'D'); define('DOKU_CHANGE_TYPE_REVERT', 'R'); /** * Changelog filter constants * @file inc/changelog.php */ define('RECENTS_SKIP_DELETED', 2); define('RECENTS_SKIP_MINORS', 4); define('RECENTS_SKIP_SUBSPACES', 8); define('RECENTS_MEDIA_CHANGES', 16); define('RECENTS_MEDIA_PAGES_MIXED', 32); define('RECENTS_ONLY_CREATION', 64); /** * Media error types * @file inc/media.php */ define('DOKU_MEDIA_DELETED', 1); define('DOKU_MEDIA_NOT_AUTH', 2); define('DOKU_MEDIA_INUSE', 4); define('DOKU_MEDIA_EMPTY_NS', 8); IXR_Library.php 0000644 00000100376 15233462216 0007416 0 ustar 00 <?php use dokuwiki\HTTP\DokuHTTPClient; /** * IXR - The Incutio XML-RPC Library * * Copyright (c) 2010, Incutio Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - Neither the name of Incutio Ltd. nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @package IXR * @since 1.5 * * @copyright Incutio Ltd 2010 (http://www.incutio.com) * @version 1.7.4 7th September 2010 * @author Simon Willison * @link http://scripts.incutio.com/xmlrpc/ Site/manual * * Modified for DokuWiki * @author Andreas Gohr <andi@splitbrain.org> */ class IXR_Value { /** @var IXR_Value[]|IXR_Date|IXR_Base64|int|bool|double|string */ var $data; /** @var string */ var $type; /** * @param mixed $data * @param bool $type */ function __construct($data, $type = false) { $this->data = $data; if(!$type) { $type = $this->calculateType(); } $this->type = $type; if($type == 'struct') { // Turn all the values in the array in to new IXR_Value objects foreach($this->data as $key => $value) { $this->data[$key] = new IXR_Value($value); } } if($type == 'array') { for($i = 0, $j = count($this->data); $i < $j; $i++) { $this->data[$i] = new IXR_Value($this->data[$i]); } } } /** * @return string */ function calculateType() { if($this->data === true || $this->data === false) { return 'boolean'; } if(is_integer($this->data)) { return 'int'; } if(is_double($this->data)) { return 'double'; } // Deal with IXR object types base64 and date if(is_object($this->data) && is_a($this->data, 'IXR_Date')) { return 'date'; } if(is_object($this->data) && is_a($this->data, 'IXR_Base64')) { return 'base64'; } // If it is a normal PHP object convert it in to a struct if(is_object($this->data)) { $this->data = get_object_vars($this->data); return 'struct'; } if(!is_array($this->data)) { return 'string'; } // We have an array - is it an array or a struct? if($this->isStruct($this->data)) { return 'struct'; } else { return 'array'; } } /** * @return bool|string */ function getXml() { // Return XML for this value switch($this->type) { case 'boolean': return '<boolean>' . (($this->data) ? '1' : '0') . '</boolean>'; break; case 'int': return '<int>' . $this->data . '</int>'; break; case 'double': return '<double>' . $this->data . '</double>'; break; case 'string': return '<string>' . htmlspecialchars($this->data) . '</string>'; break; case 'array': $return = '<array><data>' . "\n"; foreach($this->data as $item) { $return .= ' <value>' . $item->getXml() . "</value>\n"; } $return .= '</data></array>'; return $return; break; case 'struct': $return = '<struct>' . "\n"; foreach($this->data as $name => $value) { $return .= " <member><name>$name</name><value>"; $return .= $value->getXml() . "</value></member>\n"; } $return .= '</struct>'; return $return; break; case 'date': case 'base64': return $this->data->getXml(); break; } return false; } /** * Checks whether or not the supplied array is a struct or not * * @param array $array * @return boolean */ function isStruct($array) { $expected = 0; foreach($array as $key => $value) { if((string) $key != (string) $expected) { return true; } $expected++; } return false; } } /** * IXR_MESSAGE * * @package IXR * @since 1.5 * */ class IXR_Message { var $message; var $messageType; // methodCall / methodResponse / fault var $faultCode; var $faultString; var $methodName; var $params; // Current variable stacks var $_arraystructs = array(); // The stack used to keep track of the current array/struct var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array var $_currentStructName = array(); // A stack as well var $_param; var $_value; var $_currentTag; var $_currentTagContents; var $_lastseen; // The XML parser var $_parser; /** * @param string $message */ function __construct($message) { $this->message =& $message; } /** * @return bool */ function parse() { // first remove the XML declaration // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages $header = preg_replace('/<\?xml.*?\?' . '>/', '', substr($this->message, 0, 100), 1); $this->message = substr_replace($this->message, $header, 0, 100); // workaround for a bug in PHP/libxml2, see http://bugs.php.net/bug.php?id=45996 $this->message = str_replace('<', '<', $this->message); $this->message = str_replace('>', '>', $this->message); $this->message = str_replace('&', '&', $this->message); $this->message = str_replace(''', ''', $this->message); $this->message = str_replace('"', '"', $this->message); $this->message = str_replace("\x0b", ' ', $this->message); //vertical tab if(trim($this->message) == '') { return false; } $this->_parser = xml_parser_create(); // Set XML parser to take the case of tags in to account xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); // Set XML parser callback functions xml_set_object($this->_parser, $this); xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); xml_set_character_data_handler($this->_parser, 'cdata'); $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages $final = false; do { if(strlen($this->message) <= $chunk_size) { $final = true; } $part = substr($this->message, 0, $chunk_size); $this->message = substr($this->message, $chunk_size); if(!xml_parse($this->_parser, $part, $final)) { return false; } if($final) { break; } } while(true); xml_parser_free($this->_parser); // Grab the error messages, if any if($this->messageType == 'fault') { $this->faultCode = $this->params[0]['faultCode']; $this->faultString = $this->params[0]['faultString']; } return true; } /** * @param $parser * @param string $tag * @param $attr */ function tag_open($parser, $tag, $attr) { $this->_currentTagContents = ''; $this->_currentTag = $tag; switch($tag) { case 'methodCall': case 'methodResponse': case 'fault': $this->messageType = $tag; break; /* Deal with stacks of arrays and structs */ case 'data': // data is to all intents and purposes more interesting than array $this->_arraystructstypes[] = 'array'; $this->_arraystructs[] = array(); break; case 'struct': $this->_arraystructstypes[] = 'struct'; $this->_arraystructs[] = array(); break; } $this->_lastseen = $tag; } /** * @param $parser * @param string $cdata */ function cdata($parser, $cdata) { $this->_currentTagContents .= $cdata; } /** * @param $parser * @param $tag */ function tag_close($parser, $tag) { $value = null; $valueFlag = false; switch($tag) { case 'int': case 'i4': $value = (int) trim($this->_currentTagContents); $valueFlag = true; break; case 'double': $value = (double) trim($this->_currentTagContents); $valueFlag = true; break; case 'string': $value = (string) $this->_currentTagContents; $valueFlag = true; break; case 'dateTime.iso8601': $value = new IXR_Date(trim($this->_currentTagContents)); $valueFlag = true; break; case 'value': // "If no type is indicated, the type is string." if($this->_lastseen == 'value') { $value = (string) $this->_currentTagContents; $valueFlag = true; } break; case 'boolean': $value = (boolean) trim($this->_currentTagContents); $valueFlag = true; break; case 'base64': $value = base64_decode($this->_currentTagContents); $valueFlag = true; break; /* Deal with stacks of arrays and structs */ case 'data': case 'struct': $value = array_pop($this->_arraystructs); array_pop($this->_arraystructstypes); $valueFlag = true; break; case 'member': array_pop($this->_currentStructName); break; case 'name': $this->_currentStructName[] = trim($this->_currentTagContents); break; case 'methodName': $this->methodName = trim($this->_currentTagContents); break; } if($valueFlag) { if(count($this->_arraystructs) > 0) { // Add value to struct or array if($this->_arraystructstypes[count($this->_arraystructstypes) - 1] == 'struct') { // Add to struct $this->_arraystructs[count($this->_arraystructs) - 1][$this->_currentStructName[count($this->_currentStructName) - 1]] = $value; } else { // Add to array $this->_arraystructs[count($this->_arraystructs) - 1][] = $value; } } else { // Just add as a parameter $this->params[] = $value; } } $this->_currentTagContents = ''; $this->_lastseen = $tag; } } /** * IXR_Server * * @package IXR * @since 1.5 */ class IXR_Server { var $data; /** @var array */ var $callbacks = array(); var $message; /** @var array */ var $capabilities; /** * @param array|bool $callbacks * @param bool $data * @param bool $wait */ function __construct($callbacks = false, $data = false, $wait = false) { $this->setCapabilities(); if($callbacks) { $this->callbacks = $callbacks; } $this->setCallbacks(); if(!$wait) { $this->serve($data); } } /** * @param bool|string $data */ function serve($data = false) { if(!$data) { $postData = trim(http_get_raw_post_data()); if(!$postData) { header('Content-Type: text/plain'); // merged from WP #9093 die('XML-RPC server accepts POST requests only.'); } $data = $postData; } $this->message = new IXR_Message($data); if(!$this->message->parse()) { $this->error(-32700, 'parse error. not well formed'); } if($this->message->messageType != 'methodCall') { $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); } $result = $this->call($this->message->methodName, $this->message->params); // Is the result an error? if(is_a($result, 'IXR_Error')) { $this->error($result); } // Encode the result $r = new IXR_Value($result); $resultxml = $r->getXml(); // Create the XML $xml = <<<EOD <methodResponse> <params> <param> <value> $resultxml </value> </param> </params> </methodResponse> EOD; // Send it $this->output($xml); } /** * @param string $methodname * @param array $args * @return IXR_Error|mixed */ function call($methodname, $args) { if(!$this->hasMethod($methodname)) { return new IXR_Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.'); } $method = $this->callbacks[$methodname]; // Perform the callback and send the response # Removed for DokuWiki to have a more consistent interface # if (count($args) == 1) { # // If only one parameter just send that instead of the whole array # $args = $args[0]; # } # Adjusted for DokuWiki to use call_user_func_array // args need to be an array $args = (array) $args; // Are we dealing with a function or a method? if(is_string($method) && substr($method, 0, 5) == 'this:') { // It's a class method - check it exists $method = substr($method, 5); if(!method_exists($this, $method)) { return new IXR_Error(-32601, 'server error. requested class method "' . $method . '" does not exist.'); } // Call the method #$result = $this->$method($args); $result = call_user_func_array(array(&$this, $method), $args); } elseif(substr($method, 0, 7) == 'plugin:') { list($pluginname, $callback) = explode(':', substr($method, 7), 2); if(!plugin_isdisabled($pluginname)) { $plugin = plugin_load('action', $pluginname); return call_user_func_array(array($plugin, $callback), $args); } else { return new IXR_Error(-99999, 'server error'); } } else { // It's a function - does it exist? if(is_array($method)) { if(!is_callable(array($method[0], $method[1]))) { return new IXR_Error(-32601, 'server error. requested object method "' . $method[1] . '" does not exist.'); } } else if(!function_exists($method)) { return new IXR_Error(-32601, 'server error. requested function "' . $method . '" does not exist.'); } // Call the function $result = call_user_func($method, $args); } return $result; } /** * @param int $error * @param string|bool $message */ function error($error, $message = false) { // Accepts either an error object or an error code and message if($message && !is_object($error)) { $error = new IXR_Error($error, $message); } $this->output($error->getXml()); } /** * @param string $xml */ function output($xml) { header('Content-Type: text/xml; charset=utf-8'); echo '<?xml version="1.0"?>', "\n", $xml; exit; } /** * @param string $method * @return bool */ function hasMethod($method) { return in_array($method, array_keys($this->callbacks)); } function setCapabilities() { // Initialises capabilities array $this->capabilities = array( 'xmlrpc' => array( 'specUrl' => 'http://www.xmlrpc.com/spec', 'specVersion' => 1 ), 'faults_interop' => array( 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', 'specVersion' => 20010516 ), 'system.multicall' => array( 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', 'specVersion' => 1 ), ); } /** * @return mixed */ function getCapabilities() { return $this->capabilities; } function setCallbacks() { $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; $this->callbacks['system.listMethods'] = 'this:listMethods'; $this->callbacks['system.multicall'] = 'this:multiCall'; } /** * @return array */ function listMethods() { // Returns a list of methods - uses array_reverse to ensure user defined // methods are listed before server defined methods return array_reverse(array_keys($this->callbacks)); } /** * @param array $methodcalls * @return array */ function multiCall($methodcalls) { // See http://www.xmlrpc.com/discuss/msgReader$1208 $return = array(); foreach($methodcalls as $call) { $method = $call['methodName']; $params = $call['params']; if($method == 'system.multicall') { $result = new IXR_Error(-32800, 'Recursive calls to system.multicall are forbidden'); } else { $result = $this->call($method, $params); } if(is_a($result, 'IXR_Error')) { $return[] = array( 'faultCode' => $result->code, 'faultString' => $result->message ); } else { $return[] = array($result); } } return $return; } } /** * IXR_Request * * @package IXR * @since 1.5 */ class IXR_Request { /** @var string */ var $method; /** @var array */ var $args; /** @var string */ var $xml; /** * @param string $method * @param array $args */ function __construct($method, $args) { $this->method = $method; $this->args = $args; $this->xml = <<<EOD <?xml version="1.0"?> <methodCall> <methodName>{$this->method}</methodName> <params> EOD; foreach($this->args as $arg) { $this->xml .= '<param><value>'; $v = new IXR_Value($arg); $this->xml .= $v->getXml(); $this->xml .= "</value></param>\n"; } $this->xml .= '</params></methodCall>'; } /** * @return int */ function getLength() { return strlen($this->xml); } /** * @return string */ function getXml() { return $this->xml; } } /** * IXR_Client * * @package IXR * @since 1.5 * * Changed for DokuWiki to use DokuHTTPClient * * This should be compatible to the original class, but uses DokuWiki's * HTTP client library which will respect proxy settings * * Because the XMLRPC client is not used in DokuWiki currently this is completely * untested */ class IXR_Client extends DokuHTTPClient { var $posturl = ''; /** @var IXR_Message|bool */ var $message = false; // Storage place for an error message /** @var IXR_Error|bool */ var $xmlerror = false; /** * @param string $server * @param string|bool $path * @param int $port * @param int $timeout */ function __construct($server, $path = false, $port = 80, $timeout = 15) { parent::__construct(); if(!$path) { // Assume we have been given a URL instead $this->posturl = $server; } else { $this->posturl = 'http://' . $server . ':' . $port . $path; } $this->timeout = $timeout; } /** * parameters: method and arguments * @return bool success or error */ function query() { $args = func_get_args(); $method = array_shift($args); $request = new IXR_Request($method, $args); $xml = $request->getXml(); $this->headers['Content-Type'] = 'text/xml'; if(!$this->sendRequest($this->posturl, $xml, 'POST')) { $this->xmlerror = new IXR_Error(-32300, 'transport error - ' . $this->error); return false; } // Check HTTP Response code if($this->status < 200 || $this->status > 206) { $this->xmlerror = new IXR_Error(-32300, 'transport error - HTTP status ' . $this->status); return false; } // Now parse what we've got back $this->message = new IXR_Message($this->resp_body); if(!$this->message->parse()) { // XML error $this->xmlerror = new IXR_Error(-32700, 'parse error. not well formed'); return false; } // Is the message a fault? if($this->message->messageType == 'fault') { $this->xmlerror = new IXR_Error($this->message->faultCode, $this->message->faultString); return false; } // Message must be OK return true; } /** * @return mixed */ function getResponse() { // methodResponses can only have one param - return that return $this->message->params[0]; } /** * @return bool */ function isError() { return (is_object($this->xmlerror)); } /** * @return int */ function getErrorCode() { return $this->xmlerror->code; } /** * @return string */ function getErrorMessage() { return $this->xmlerror->message; } } /** * IXR_Error * * @package IXR * @since 1.5 */ class IXR_Error { var $code; var $message; /** * @param int $code * @param string $message */ function __construct($code, $message) { $this->code = $code; $this->message = htmlspecialchars($message); } /** * @return string */ function getXml() { $xml = <<<EOD <methodResponse> <fault> <value> <struct> <member> <name>faultCode</name> <value><int>{$this->code}</int></value> </member> <member> <name>faultString</name> <value><string>{$this->message}</string></value> </member> </struct> </value> </fault> </methodResponse> EOD; return $xml; } } /** * IXR_Date * * @package IXR * @since 1.5 */ class IXR_Date { const XMLRPC_ISO8601 = "Ymd\TH:i:sO" ; /** @var DateTime */ protected $date; /** * @param int|string $time */ public function __construct($time) { // $time can be a PHP timestamp or an ISO one if(is_numeric($time)) { $this->parseTimestamp($time); } else { $this->parseIso($time); } } /** * Parse unix timestamp * * @param int $timestamp */ protected function parseTimestamp($timestamp) { $this->date = new DateTime('@' . $timestamp); } /** * Parses less or more complete iso dates and much more, if no timezone given assumes UTC * * @param string $iso */ protected function parseIso($iso) { $this->date = new DateTime($iso, new DateTimeZone("UTC")); } /** * Returns date in ISO 8601 format * * @return string */ public function getIso() { return $this->date->format(self::XMLRPC_ISO8601); } /** * Returns date in valid xml * * @return string */ public function getXml() { return '<dateTime.iso8601>' . $this->getIso() . '</dateTime.iso8601>'; } /** * Returns Unix timestamp * * @return int */ function getTimestamp() { return $this->date->getTimestamp(); } } /** * IXR_Base64 * * @package IXR * @since 1.5 */ class IXR_Base64 { var $data; /** * @param string $data */ function __construct($data) { $this->data = $data; } /** * @return string */ function getXml() { return '<base64>' . base64_encode($this->data) . '</base64>'; } } /** * IXR_IntrospectionServer * * @package IXR * @since 1.5 */ class IXR_IntrospectionServer extends IXR_Server { /** @var array[] */ var $signatures; /** @var string[] */ var $help; /** * Constructor */ function __construct() { $this->setCallbacks(); $this->setCapabilities(); $this->capabilities['introspection'] = array( 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html', 'specVersion' => 1 ); $this->addCallback( 'system.methodSignature', 'this:methodSignature', array('array', 'string'), 'Returns an array describing the return type and required parameters of a method' ); $this->addCallback( 'system.getCapabilities', 'this:getCapabilities', array('struct'), 'Returns a struct describing the XML-RPC specifications supported by this server' ); $this->addCallback( 'system.listMethods', 'this:listMethods', array('array'), 'Returns an array of available methods on this server' ); $this->addCallback( 'system.methodHelp', 'this:methodHelp', array('string', 'string'), 'Returns a documentation string for the specified method' ); } /** * @param string $method * @param string $callback * @param string[] $args * @param string $help */ function addCallback($method, $callback, $args, $help) { $this->callbacks[$method] = $callback; $this->signatures[$method] = $args; $this->help[$method] = $help; } /** * @param string $methodname * @param array $args * @return IXR_Error|mixed */ function call($methodname, $args) { // Make sure it's in an array if($args && !is_array($args)) { $args = array($args); } // Over-rides default call method, adds signature check if(!$this->hasMethod($methodname)) { return new IXR_Error(-32601, 'server error. requested method "' . $this->message->methodName . '" not specified.'); } $method = $this->callbacks[$methodname]; $signature = $this->signatures[$methodname]; $returnType = array_shift($signature); // Check the number of arguments. Check only, if the minimum count of parameters is specified. More parameters are possible. // This is a hack to allow optional parameters... if(count($args) < count($signature)) { // print 'Num of args: '.count($args).' Num in signature: '.count($signature); return new IXR_Error(-32602, 'server error. wrong number of method parameters'); } // Check the argument types $ok = true; $argsbackup = $args; for($i = 0, $j = count($args); $i < $j; $i++) { $arg = array_shift($args); $type = array_shift($signature); switch($type) { case 'int': case 'i4': if(is_array($arg) || !is_int($arg)) { $ok = false; } break; case 'base64': case 'string': if(!is_string($arg)) { $ok = false; } break; case 'boolean': if($arg !== false && $arg !== true) { $ok = false; } break; case 'float': case 'double': if(!is_float($arg)) { $ok = false; } break; case 'date': case 'dateTime.iso8601': if(!is_a($arg, 'IXR_Date')) { $ok = false; } break; } if(!$ok) { return new IXR_Error(-32602, 'server error. invalid method parameters'); } } // It passed the test - run the "real" method call return parent::call($methodname, $argsbackup); } /** * @param string $method * @return array|IXR_Error */ function methodSignature($method) { if(!$this->hasMethod($method)) { return new IXR_Error(-32601, 'server error. requested method "' . $method . '" not specified.'); } // We should be returning an array of types $types = $this->signatures[$method]; $return = array(); foreach($types as $type) { switch($type) { case 'string': $return[] = 'string'; break; case 'int': case 'i4': $return[] = 42; break; case 'double': $return[] = 3.1415; break; case 'dateTime.iso8601': $return[] = new IXR_Date(time()); break; case 'boolean': $return[] = true; break; case 'base64': $return[] = new IXR_Base64('base64'); break; case 'array': $return[] = array('array'); break; case 'struct': $return[] = array('struct' => 'struct'); break; } } return $return; } /** * @param string $method * @return mixed */ function methodHelp($method) { return $this->help[$method]; } } /** * IXR_ClientMulticall * * @package IXR * @since 1.5 */ class IXR_ClientMulticall extends IXR_Client { /** @var array[] */ var $calls = array(); /** * @param string $server * @param string|bool $path * @param int $port */ function __construct($server, $path = false, $port = 80) { parent::__construct($server, $path, $port); //$this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)'; } /** * Add a call */ function addCall() { $args = func_get_args(); $methodName = array_shift($args); $struct = array( 'methodName' => $methodName, 'params' => $args ); $this->calls[] = $struct; } /** * @return bool */ function query() { // Prepare multicall, then call the parent::query() method return parent::query('system.multicall', $this->calls); } } Draft.php 0000644 00000010055 15233462216 0006322 0 ustar 00 <?php namespace dokuwiki; /** * Class Draft * * @package dokuwiki */ class Draft { protected $errors = []; protected $cname; protected $id; protected $client; /** * Draft constructor. * * @param string $ID the page id for this draft * @param string $client the client identification (username or ip or similar) for this draft */ public function __construct($ID, $client) { $this->id = $ID; $this->client = $client; $this->cname = getCacheName($client.$ID, '.draft'); if(file_exists($this->cname) && file_exists(wikiFN($ID))) { if (filemtime($this->cname) < filemtime(wikiFN($ID))) { // remove stale draft $this->deleteDraft(); } } } /** * Get the filename for this draft (whether or not it exists) * * @return string */ public function getDraftFilename() { return $this->cname; } /** * Checks if this draft exists on the filesystem * * @return bool */ public function isDraftAvailable() { return file_exists($this->cname); } /** * Save a draft of a current edit session * * The draft will not be saved if * - drafts are deactivated in the config * - or the editarea is empty and there are no event handlers registered * - or the event is prevented * * @triggers DRAFT_SAVE * * @return bool whether has the draft been saved */ public function saveDraft() { global $INPUT, $INFO, $EVENT_HANDLER, $conf; if (!$conf['usedraft']) { return false; } if (!$INPUT->post->has('wikitext') && !$EVENT_HANDLER->hasHandlerForEvent('DRAFT_SAVE')) { return false; } $draft = [ 'id' => $this->id, 'prefix' => substr($INPUT->post->str('prefix'), 0, -1), 'text' => $INPUT->post->str('wikitext'), 'suffix' => $INPUT->post->str('suffix'), 'date' => $INPUT->post->int('date'), 'client' => $this->client, 'cname' => $this->cname, 'errors' => [], ]; $event = new Extension\Event('DRAFT_SAVE', $draft); if ($event->advise_before()) { $draft['hasBeenSaved'] = io_saveFile($draft['cname'], serialize($draft)); if ($draft['hasBeenSaved']) { $INFO['draft'] = $draft['cname']; } } else { $draft['hasBeenSaved'] = false; } $event->advise_after(); $this->errors = $draft['errors']; return $draft['hasBeenSaved']; } /** * Get the text from the draft file * * @throws \RuntimeException if the draft file doesn't exist * * @return string */ public function getDraftText() { if (!file_exists($this->cname)) { throw new \RuntimeException( "Draft for page $this->id and user $this->client doesn't exist at $this->cname." ); } $draft = unserialize(io_readFile($this->cname,false)); return cleanText(con($draft['prefix'],$draft['text'],$draft['suffix'],true)); } /** * Remove the draft from the filesystem * * Also sets $INFO['draft'] to null */ public function deleteDraft() { global $INFO; @unlink($this->cname); $INFO['draft'] = null; } /** * Get a formatted message stating when the draft was saved * * @return string */ public function getDraftMessage() { global $lang; return $lang['draftdate'] . ' ' . dformat(filemtime($this->cname)); } /** * Retrieve the errors that occured when saving the draft * * @return array */ public function getErrors() { return $this->errors; } /** * Get the timestamp when this draft was saved * * @return int */ public function getDraftDate() { return filemtime($this->cname); } } ChangeLog/MediaChangeLog.php 0000644 00000001000 15233462216 0011666 0 ustar 00 <?php namespace dokuwiki\ChangeLog; /** * handles changelog of a media file */ class MediaChangeLog extends ChangeLog { /** * Returns path to changelog * * @return string path to file */ protected function getChangelogFilename() { return mediaMetaFN($this->id, '.changes'); } /** * Returns path to current page/media * * @return string path to file */ protected function getFilename() { return mediaFN($this->id); } } ChangeLog/PageChangeLog.php 0000644 00000000770 15233462216 0011540 0 ustar 00 <?php namespace dokuwiki\ChangeLog; /** * handles changelog of a wiki page */ class PageChangeLog extends ChangeLog { /** * Returns path to changelog * * @return string path to file */ protected function getChangelogFilename() { return metaFN($this->id, '.changes'); } /** * Returns path to current page/media * * @return string path to file */ protected function getFilename() { return wikiFN($this->id); } } ChangeLog/ChangeLog.php 0000644 00000053544 15233462216 0010752 0 ustar 00 <?php namespace dokuwiki\ChangeLog; /** * methods for handling of changelog of pages or media files */ abstract class ChangeLog { /** @var string */ protected $id; /** @var int */ protected $chunk_size; /** @var array */ protected $cache; /** * Constructor * * @param string $id page id * @param int $chunk_size maximum block size read from file */ public function __construct($id, $chunk_size = 8192) { global $cache_revinfo; $this->cache =& $cache_revinfo; if (!isset($this->cache[$id])) { $this->cache[$id] = array(); } $this->id = $id; $this->setChunkSize($chunk_size); } /** * Set chunk size for file reading * Chunk size zero let read whole file at once * * @param int $chunk_size maximum block size read from file */ public function setChunkSize($chunk_size) { if (!is_numeric($chunk_size)) $chunk_size = 0; $this->chunk_size = (int)max($chunk_size, 0); } /** * Returns path to changelog * * @return string path to file */ abstract protected function getChangelogFilename(); /** * Returns path to current page/media * * @return string path to file */ abstract protected function getFilename(); /** * Get the changelog information for a specific page id and revision (timestamp) * * Adjacent changelog lines are optimistically parsed and cached to speed up * consecutive calls to getRevisionInfo. For large changelog files, only the chunk * containing the requested changelog line is read. * * @param int $rev revision timestamp * @return bool|array false or array with entries: * - date: unix timestamp * - ip: IPv4 address (127.0.0.1) * - type: log line type * - id: page id * - user: user name * - sum: edit summary (or action reason) * - extra: extra data (varies by line type) * * @author Ben Coburn <btcoburn@silicodon.net> * @author Kate Arzamastseva <pshns@ukr.net> */ public function getRevisionInfo($rev) { $rev = max($rev, 0); // check if it's already in the memory cache if (isset($this->cache[$this->id]) && isset($this->cache[$this->id][$rev])) { return $this->cache[$this->id][$rev]; } //read lines from changelog list($fp, $lines) = $this->readloglines($rev); if ($fp) { fclose($fp); } if (empty($lines)) return false; // parse and cache changelog lines foreach ($lines as $value) { $tmp = parseChangelogLine($value); if ($tmp !== false) { $this->cache[$this->id][$tmp['date']] = $tmp; } } if (!isset($this->cache[$this->id][$rev])) { return false; } return $this->cache[$this->id][$rev]; } /** * Return a list of page revisions numbers * * Does not guarantee that the revision exists in the attic, * only that a line with the date exists in the changelog. * By default the current revision is skipped. * * The current revision is automatically skipped when the page exists. * See $INFO['meta']['last_change'] for the current revision. * A negative $first let read the current revision too. * * For efficiency, the log lines are parsed and cached for later * calls to getRevisionInfo. Large changelog files are read * backwards in chunks until the requested number of changelog * lines are recieved. * * @param int $first skip the first n changelog lines * @param int $num number of revisions to return * @return array with the revision timestamps * * @author Ben Coburn <btcoburn@silicodon.net> * @author Kate Arzamastseva <pshns@ukr.net> */ public function getRevisions($first, $num) { $revs = array(); $lines = array(); $count = 0; $num = max($num, 0); if ($num == 0) { return $revs; } if ($first < 0) { $first = 0; } else { if (file_exists($this->getFilename())) { // skip current revision if the page exists $first = max($first + 1, 0); } } $file = $this->getChangelogFilename(); if (!file_exists($file)) { return $revs; } if (filesize($file) < $this->chunk_size || $this->chunk_size == 0) { // read whole file $lines = file($file); if ($lines === false) { return $revs; } } else { // read chunks backwards $fp = fopen($file, 'rb'); // "file pointer" if ($fp === false) { return $revs; } fseek($fp, 0, SEEK_END); $tail = ftell($fp); // chunk backwards $finger = max($tail - $this->chunk_size, 0); while ($count < $num + $first) { $nl = $this->getNewlinepointer($fp, $finger); // was the chunk big enough? if not, take another bite if ($nl > 0 && $tail <= $nl) { $finger = max($finger - $this->chunk_size, 0); continue; } else { $finger = $nl; } // read chunk $chunk = ''; $read_size = max($tail - $finger, 0); // found chunk size $got = 0; while ($got < $read_size && !feof($fp)) { $tmp = @fread($fp, max(min($this->chunk_size, $read_size - $got), 0)); if ($tmp === false) { break; } //error state $got += strlen($tmp); $chunk .= $tmp; } $tmp = explode("\n", $chunk); array_pop($tmp); // remove trailing newline // combine with previous chunk $count += count($tmp); $lines = array_merge($tmp, $lines); // next chunk if ($finger == 0) { break; } else { // already read all the lines $tail = $finger; $finger = max($tail - $this->chunk_size, 0); } } fclose($fp); } // skip parsing extra lines $num = max(min(count($lines) - $first, $num), 0); if ($first > 0 && $num > 0) { $lines = array_slice($lines, max(count($lines) - $first - $num, 0), $num); } else { if ($first > 0 && $num == 0) { $lines = array_slice($lines, 0, max(count($lines) - $first, 0)); } elseif ($first == 0 && $num > 0) { $lines = array_slice($lines, max(count($lines) - $num, 0)); } } // handle lines in reverse order for ($i = count($lines) - 1; $i >= 0; $i--) { $tmp = parseChangelogLine($lines[$i]); if ($tmp !== false) { $this->cache[$this->id][$tmp['date']] = $tmp; $revs[] = $tmp['date']; } } return $revs; } /** * Get the nth revision left or right handside for a specific page id and revision (timestamp) * * For large changelog files, only the chunk containing the * reference revision $rev is read and sometimes a next chunck. * * Adjacent changelog lines are optimistically parsed and cached to speed up * consecutive calls to getRevisionInfo. * * @param int $rev revision timestamp used as startdate (doesn't need to be revisionnumber) * @param int $direction give position of returned revision with respect to $rev; positive=next, negative=prev * @return bool|int * timestamp of the requested revision * otherwise false */ public function getRelativeRevision($rev, $direction) { $rev = max($rev, 0); $direction = (int)$direction; //no direction given or last rev, so no follow-up if (!$direction || ($direction > 0 && $this->isCurrentRevision($rev))) { return false; } //get lines from changelog list($fp, $lines, $head, $tail, $eof) = $this->readloglines($rev); if (empty($lines)) return false; // look for revisions later/earlier then $rev, when founded count till the wanted revision is reached // also parse and cache changelog lines for getRevisionInfo(). $revcounter = 0; $relativerev = false; $checkotherchunck = true; //always runs once while (!$relativerev && $checkotherchunck) { $tmp = array(); //parse in normal or reverse order $count = count($lines); if ($direction > 0) { $start = 0; $step = 1; } else { $start = $count - 1; $step = -1; } for ($i = $start; $i >= 0 && $i < $count; $i = $i + $step) { $tmp = parseChangelogLine($lines[$i]); if ($tmp !== false) { $this->cache[$this->id][$tmp['date']] = $tmp; //look for revs older/earlier then reference $rev and select $direction-th one if (($direction > 0 && $tmp['date'] > $rev) || ($direction < 0 && $tmp['date'] < $rev)) { $revcounter++; if ($revcounter == abs($direction)) { $relativerev = $tmp['date']; } } } } //true when $rev is found, but not the wanted follow-up. $checkotherchunck = $fp && ($tmp['date'] == $rev || ($revcounter > 0 && !$relativerev)) && !(($tail == $eof && $direction > 0) || ($head == 0 && $direction < 0)); if ($checkotherchunck) { list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, $direction); if (empty($lines)) break; } } if ($fp) { fclose($fp); } return $relativerev; } /** * Returns revisions around rev1 and rev2 * When available it returns $max entries for each revision * * @param int $rev1 oldest revision timestamp * @param int $rev2 newest revision timestamp (0 looks up last revision) * @param int $max maximum number of revisions returned * @return array with two arrays with revisions surrounding rev1 respectively rev2 */ public function getRevisionsAround($rev1, $rev2, $max = 50) { $max = floor(abs($max) / 2) * 2 + 1; $rev1 = max($rev1, 0); $rev2 = max($rev2, 0); if ($rev2) { if ($rev2 < $rev1) { $rev = $rev2; $rev2 = $rev1; $rev1 = $rev; } } else { //empty right side means a removed page. Look up last revision. $revs = $this->getRevisions(-1, 1); $rev2 = $revs[0]; } //collect revisions around rev2 list($revs2, $allrevs, $fp, $lines, $head, $tail) = $this->retrieveRevisionsAround($rev2, $max); if (empty($revs2)) return array(array(), array()); //collect revisions around rev1 $index = array_search($rev1, $allrevs); if ($index === false) { //no overlapping revisions list($revs1, , , , ,) = $this->retrieveRevisionsAround($rev1, $max); if (empty($revs1)) $revs1 = array(); } else { //revisions overlaps, reuse revisions around rev2 $revs1 = $allrevs; while ($head > 0) { for ($i = count($lines) - 1; $i >= 0; $i--) { $tmp = parseChangelogLine($lines[$i]); if ($tmp !== false) { $this->cache[$this->id][$tmp['date']] = $tmp; $revs1[] = $tmp['date']; $index++; if ($index > floor($max / 2)) break 2; } } list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, -1); } sort($revs1); //return wanted selection $revs1 = array_slice($revs1, max($index - floor($max / 2), 0), $max); } return array(array_reverse($revs1), array_reverse($revs2)); } /** * Checks if the ID has old revisons * @return boolean */ public function hasRevisions() { $file = $this->getChangelogFilename(); return file_exists($file); } /** * Returns lines from changelog. * If file larger than $chuncksize, only chunck is read that could contain $rev. * * @param int $rev revision timestamp * @return array|false * if success returns array(fp, array(changeloglines), $head, $tail, $eof) * where fp only defined for chuck reading, needs closing. * otherwise false */ protected function readloglines($rev) { $file = $this->getChangelogFilename(); if (!file_exists($file)) { return false; } $fp = null; $head = 0; $tail = 0; $eof = 0; if (filesize($file) < $this->chunk_size || $this->chunk_size == 0) { // read whole file $lines = file($file); if ($lines === false) { return false; } } else { // read by chunk $fp = fopen($file, 'rb'); // "file pointer" if ($fp === false) { return false; } $head = 0; fseek($fp, 0, SEEK_END); $eof = ftell($fp); $tail = $eof; // find chunk while ($tail - $head > $this->chunk_size) { $finger = $head + floor(($tail - $head) / 2.0); $finger = $this->getNewlinepointer($fp, $finger); $tmp = fgets($fp); if ($finger == $head || $finger == $tail) { break; } $tmp = parseChangelogLine($tmp); $finger_rev = $tmp['date']; if ($finger_rev > $rev) { $tail = $finger; } else { $head = $finger; } } if ($tail - $head < 1) { // cound not find chunk, assume requested rev is missing fclose($fp); return false; } $lines = $this->readChunk($fp, $head, $tail); } return array( $fp, $lines, $head, $tail, $eof, ); } /** * Read chunk and return array with lines of given chunck. * Has no check if $head and $tail are really at a new line * * @param resource $fp resource filepointer * @param int $head start point chunck * @param int $tail end point chunck * @return array lines read from chunck */ protected function readChunk($fp, $head, $tail) { $chunk = ''; $chunk_size = max($tail - $head, 0); // found chunk size $got = 0; fseek($fp, $head); while ($got < $chunk_size && !feof($fp)) { $tmp = @fread($fp, max(min($this->chunk_size, $chunk_size - $got), 0)); if ($tmp === false) { //error state break; } $got += strlen($tmp); $chunk .= $tmp; } $lines = explode("\n", $chunk); array_pop($lines); // remove trailing newline return $lines; } /** * Set pointer to first new line after $finger and return its position * * @param resource $fp filepointer * @param int $finger a pointer * @return int pointer */ protected function getNewlinepointer($fp, $finger) { fseek($fp, $finger); $nl = $finger; if ($finger > 0) { fgets($fp); // slip the finger forward to a new line $nl = ftell($fp); } return $nl; } /** * Check whether given revision is the current page * * @param int $rev timestamp of current page * @return bool true if $rev is current revision, otherwise false */ public function isCurrentRevision($rev) { return $rev == @filemtime($this->getFilename()); } /** * Return an existing revision for a specific date which is * the current one or younger or equal then the date * * @param number $date_at timestamp * @return string revision ('' for current) */ public function getLastRevisionAt($date_at) { //requested date_at(timestamp) younger or equal then modified_time($this->id) => load current if (file_exists($this->getFilename()) && $date_at >= @filemtime($this->getFilename())) { return ''; } else { if ($rev = $this->getRelativeRevision($date_at + 1, -1)) { //+1 to get also the requested date revision return $rev; } else { return false; } } } /** * Returns the next lines of the changelog of the chunck before head or after tail * * @param resource $fp filepointer * @param int $head position head of last chunk * @param int $tail position tail of last chunk * @param int $direction positive forward, negative backward * @return array with entries: * - $lines: changelog lines of readed chunk * - $head: head of chunk * - $tail: tail of chunk */ protected function readAdjacentChunk($fp, $head, $tail, $direction) { if (!$fp) return array(array(), $head, $tail); if ($direction > 0) { //read forward $head = $tail; $tail = $head + floor($this->chunk_size * (2 / 3)); $tail = $this->getNewlinepointer($fp, $tail); } else { //read backward $tail = $head; $head = max($tail - $this->chunk_size, 0); while (true) { $nl = $this->getNewlinepointer($fp, $head); // was the chunk big enough? if not, take another bite if ($nl > 0 && $tail <= $nl) { $head = max($head - $this->chunk_size, 0); } else { $head = $nl; break; } } } //load next chunck $lines = $this->readChunk($fp, $head, $tail); return array($lines, $head, $tail); } /** * Collect the $max revisions near to the timestamp $rev * * @param int $rev revision timestamp * @param int $max maximum number of revisions to be returned * @return bool|array * return array with entries: * - $requestedrevs: array of with $max revision timestamps * - $revs: all parsed revision timestamps * - $fp: filepointer only defined for chuck reading, needs closing. * - $lines: non-parsed changelog lines before the parsed revisions * - $head: position of first readed changelogline * - $lasttail: position of end of last readed changelogline * otherwise false */ protected function retrieveRevisionsAround($rev, $max) { //get lines from changelog list($fp, $lines, $starthead, $starttail, /* $eof */) = $this->readloglines($rev); if (empty($lines)) return false; //parse chunk containing $rev, and read forward more chunks until $max/2 is reached $head = $starthead; $tail = $starttail; $revs = array(); $aftercount = $beforecount = 0; while (count($lines) > 0) { foreach ($lines as $line) { $tmp = parseChangelogLine($line); if ($tmp !== false) { $this->cache[$this->id][$tmp['date']] = $tmp; $revs[] = $tmp['date']; if ($tmp['date'] >= $rev) { //count revs after reference $rev $aftercount++; if ($aftercount == 1) $beforecount = count($revs); } //enough revs after reference $rev? if ($aftercount > floor($max / 2)) break 2; } } //retrieve next chunk list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, 1); } if ($aftercount == 0) return false; $lasttail = $tail; //read additional chuncks backward until $max/2 is reached and total number of revs is equal to $max $lines = array(); $i = 0; if ($aftercount > 0) { $head = $starthead; $tail = $starttail; while ($head > 0) { list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, -1); for ($i = count($lines) - 1; $i >= 0; $i--) { $tmp = parseChangelogLine($lines[$i]); if ($tmp !== false) { $this->cache[$this->id][$tmp['date']] = $tmp; $revs[] = $tmp['date']; $beforecount++; //enough revs before reference $rev? if ($beforecount > max(floor($max / 2), $max - $aftercount)) break 2; } } } } sort($revs); //keep only non-parsed lines $lines = array_slice($lines, 0, $i); //trunk desired selection $requestedrevs = array_slice($revs, -$max, $max); return array($requestedrevs, $revs, $fp, $lines, $head, $lasttail); } } search.php 0000644 00000040654 15233462216 0006537 0 ustar 00 <?php /** * DokuWiki search functions * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ /** * Recurse directory * * This function recurses into a given base directory * and calls the supplied function for each file and directory * * @param array &$data The results of the search are stored here * @param string $base Where to start the search * @param callback $func Callback (function name or array with object,method) * @param array $opts option array will be given to the Callback * @param string $dir Current directory beyond $base * @param int $lvl Recursion Level * @param mixed $sort 'natural' to use natural order sorting (default); * 'date' to sort by filemtime; leave empty to skip sorting. * @author Andreas Gohr <andi@splitbrain.org> */ function search(&$data,$base,$func,$opts,$dir='',$lvl=1,$sort='natural'){ $dirs = array(); $files = array(); $filepaths = array(); // safeguard against runaways #1452 if($base == '' || $base == '/') { throw new RuntimeException('No valid $base passed to search() - possible misconfiguration or bug'); } //read in directories and files $dh = @opendir($base.'/'.$dir); if(!$dh) return; while(($file = readdir($dh)) !== false){ if(preg_match('/^[\._]/',$file)) continue; //skip hidden files and upper dirs if(is_dir($base.'/'.$dir.'/'.$file)){ $dirs[] = $dir.'/'.$file; continue; } $files[] = $dir.'/'.$file; $filepaths[] = $base.'/'.$dir.'/'.$file; } closedir($dh); if (!empty($sort)) { if ($sort == 'date') { @array_multisort(array_map('filemtime', $filepaths), SORT_NUMERIC, SORT_DESC, $files); } else /* natural */ { natsort($files); } natsort($dirs); } //give directories to userfunction then recurse foreach($dirs as $dir){ if (call_user_func_array($func, array(&$data,$base,$dir,'d',$lvl,$opts))){ search($data,$base,$func,$opts,$dir,$lvl+1,$sort); } } //now handle the files foreach($files as $file){ call_user_func_array($func, array(&$data,$base,$file,'f',$lvl,$opts)); } } /** * The following functions are userfunctions to use with the search * function above. This function is called for every found file or * directory. When a directory is given to the function it has to * decide if this directory should be traversed (true) or not (false) * The function has to accept the following parameters: * * array &$data - Reference to the result data structure * string $base - Base usually $conf['datadir'] * string $file - current file or directory relative to $base * string $type - Type either 'd' for directory or 'f' for file * int $lvl - Current recursion depht * array $opts - option array as given to search() * * return values for files are ignored * * All functions should check the ACL for document READ rights * namespaces (directories) are NOT checked (when sneaky_index is 0) as this * would break the recursion (You can have an nonreadable dir over a readable * one deeper nested) also make sure to check the file type (for example * in case of lockfiles). */ /** * Searches for pages beginning with the given query * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $data * @param string $base * @param string $file * @param string $type * @param integer $lvl * @param array $opts * * @return bool */ function search_qsearch(&$data,$base,$file,$type,$lvl,$opts){ $opts = array( 'idmatch' => '(^|:)'.preg_quote($opts['query'],'/').'/', 'listfiles' => true, 'pagesonly' => true, ); return search_universal($data,$base,$file,$type,$lvl,$opts); } /** * Build the browsable index of pages * * $opts['ns'] is the currently viewed namespace * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $data * @param string $base * @param string $file * @param string $type * @param integer $lvl * @param array $opts * * @return bool */ function search_index(&$data,$base,$file,$type,$lvl,$opts){ global $conf; $opts = array( 'pagesonly' => true, 'listdirs' => true, 'listfiles' => empty($opts['nofiles']), 'sneakyacl' => $conf['sneaky_index'], // Hacky, should rather use recmatch 'depth' => preg_match('#^'.preg_quote($file, '#').'(/|$)#','/'.$opts['ns']) ? 0 : -1 ); return search_universal($data, $base, $file, $type, $lvl, $opts); } /** * List all namespaces * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $data * @param string $base * @param string $file * @param string $type * @param integer $lvl * @param array $opts * * @return bool */ function search_namespaces(&$data,$base,$file,$type,$lvl,$opts){ $opts = array( 'listdirs' => true, ); return search_universal($data,$base,$file,$type,$lvl,$opts); } /** * List all mediafiles in a namespace * $opts['depth'] recursion level, 0 for all * $opts['showmsg'] shows message if invalid media id is used * $opts['skipacl'] skip acl checking * $opts['pattern'] check given pattern * $opts['hash'] add hashes to result list * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $data * @param string $base * @param string $file * @param string $type * @param integer $lvl * @param array $opts * * @return bool */ function search_media(&$data,$base,$file,$type,$lvl,$opts){ //we do nothing with directories if($type == 'd') { if(empty($opts['depth'])) return true; // recurse forever $depth = substr_count($file,'/'); if($depth >= $opts['depth']) return false; // depth reached return true; } $info = array(); $info['id'] = pathID($file,true); if($info['id'] != cleanID($info['id'])){ if($opts['showmsg']) msg(hsc($info['id']).' is not a valid file name for DokuWiki - skipped',-1); return false; // skip non-valid files } //check ACL for namespace (we have no ACL for mediafiles) $info['perm'] = auth_quickaclcheck(getNS($info['id']).':*'); if(empty($opts['skipacl']) && $info['perm'] < AUTH_READ){ return false; } //check pattern filter if(!empty($opts['pattern']) && !@preg_match($opts['pattern'], $info['id'])){ return false; } $info['file'] = \dokuwiki\Utf8\PhpString::basename($file); $info['size'] = filesize($base.'/'.$file); $info['mtime'] = filemtime($base.'/'.$file); $info['writable'] = is_writable($base.'/'.$file); if(preg_match("/\.(jpe?g|gif|png)$/",$file)){ $info['isimg'] = true; $info['meta'] = new JpegMeta($base.'/'.$file); }else{ $info['isimg'] = false; } if(!empty($opts['hash'])){ $info['hash'] = md5(io_readFile(mediaFN($info['id']),false)); } $data[] = $info; return false; } /** * This function just lists documents (for RSS namespace export) * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $data * @param string $base * @param string $file * @param string $type * @param integer $lvl * @param array $opts * * @return bool */ function search_list(&$data,$base,$file,$type,$lvl,$opts){ //we do nothing with directories if($type == 'd') return false; //only search txt files if(substr($file,-4) == '.txt'){ //check ACL $id = pathID($file); if(auth_quickaclcheck($id) < AUTH_READ){ return false; } $data[]['id'] = $id; } return false; } /** * Quicksearch for searching matching pagenames * * $opts['query'] is the search query * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $data * @param string $base * @param string $file * @param string $type * @param integer $lvl * @param array $opts * * @return bool */ function search_pagename(&$data,$base,$file,$type,$lvl,$opts){ //we do nothing with directories if($type == 'd') return true; //only search txt files if(substr($file,-4) != '.txt') return true; //simple stringmatching if (!empty($opts['query'])){ if(strpos($file,$opts['query']) !== false){ //check ACL $id = pathID($file); if(auth_quickaclcheck($id) < AUTH_READ){ return false; } $data[]['id'] = $id; } } return true; } /** * Just lists all documents * * $opts['depth'] recursion level, 0 for all * $opts['hash'] do md5 sum of content? * $opts['skipacl'] list everything regardless of ACL * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $data * @param string $base * @param string $file * @param string $type * @param integer $lvl * @param array $opts * * @return bool */ function search_allpages(&$data,$base,$file,$type,$lvl,$opts){ if(isset($opts['depth']) && $opts['depth']){ $parts = explode('/',ltrim($file,'/')); if(($type == 'd' && count($parts) >= $opts['depth']) || ($type != 'd' && count($parts) > $opts['depth'])){ return false; // depth reached } } //we do nothing with directories if($type == 'd'){ return true; } //only search txt files if(substr($file,-4) != '.txt') return true; $item = array(); $item['id'] = pathID($file); if(empty($opts['skipacl']) && auth_quickaclcheck($item['id']) < AUTH_READ){ return false; } $item['rev'] = filemtime($base.'/'.$file); $item['mtime'] = $item['rev']; $item['size'] = filesize($base.'/'.$file); if(!empty($opts['hash'])){ $item['hash'] = md5(trim(rawWiki($item['id']))); } $data[] = $item; return true; } /* ------------- helper functions below -------------- */ /** * fulltext sort * * Callback sort function for use with usort to sort the data * structure created by search_fulltext. Sorts descending by count * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $a * @param array $b * * @return int */ function sort_search_fulltext($a,$b){ if($a['count'] > $b['count']){ return -1; }elseif($a['count'] < $b['count']){ return 1; }else{ return strcmp($a['id'],$b['id']); } } /** * translates a document path to an ID * * @author Andreas Gohr <andi@splitbrain.org> * @todo move to pageutils * * @param string $path * @param bool $keeptxt * * @return mixed|string */ function pathID($path,$keeptxt=false){ $id = utf8_decodeFN($path); $id = str_replace('/',':',$id); if(!$keeptxt) $id = preg_replace('#\.txt$#','',$id); $id = trim($id, ':'); return $id; } /** * This is a very universal callback for the search() function, replacing * many of the former individual functions at the cost of a more complex * setup. * * How the function behaves, depends on the options passed in the $opts * array, where the following settings can be used. * * depth int recursion depth. 0 for unlimited (default: 0) * keeptxt bool keep .txt extension for IDs (default: false) * listfiles bool include files in listing (default: false) * listdirs bool include namespaces in listing (default: false) * pagesonly bool restrict files to pages (default: false) * skipacl bool do not check for READ permission (default: false) * sneakyacl bool don't recurse into nonreadable dirs (default: false) * hash bool create MD5 hash for files (default: false) * meta bool return file metadata (default: false) * filematch string match files against this regexp (default: '', so accept everything) * idmatch string match full ID against this regexp (default: '', so accept everything) * dirmatch string match directory against this regexp when adding (default: '', so accept everything) * nsmatch string match namespace against this regexp when adding (default: '', so accept everything) * recmatch string match directory against this regexp when recursing (default: '', so accept everything) * showmsg bool warn about non-ID files (default: false) * showhidden bool show hidden files(e.g. by hidepages config) too (default: false) * firsthead bool return first heading for pages (default: false) * * @param array &$data - Reference to the result data structure * @param string $base - Base usually $conf['datadir'] * @param string $file - current file or directory relative to $base * @param string $type - Type either 'd' for directory or 'f' for file * @param int $lvl - Current recursion depht * @param array $opts - option array as given to search() * @return bool if this directory should be traversed (true) or not (false) * return value is ignored for files * * @author Andreas Gohr <gohr@cosmocode.de> */ function search_universal(&$data,$base,$file,$type,$lvl,$opts){ $item = array(); $return = true; // get ID and check if it is a valid one $item['id'] = pathID($file,($type == 'd' || !empty($opts['keeptxt']))); if($item['id'] != cleanID($item['id'])){ if(!empty($opts['showmsg'])){ msg(hsc($item['id']).' is not a valid file name for DokuWiki - skipped',-1); } return false; // skip non-valid files } $item['ns'] = getNS($item['id']); if($type == 'd') { // decide if to recursion into this directory is wanted if(empty($opts['depth'])){ $return = true; // recurse forever }else{ $depth = substr_count($file,'/'); if($depth >= $opts['depth']){ $return = false; // depth reached }else{ $return = true; } } if ($return) { $match = empty($opts['recmatch']) || preg_match('/'.$opts['recmatch'].'/',$file); if (!$match) { return false; // doesn't match } } } // check ACL if(empty($opts['skipacl'])){ if($type == 'd'){ $item['perm'] = auth_quickaclcheck($item['id'].':*'); }else{ $item['perm'] = auth_quickaclcheck($item['id']); //FIXME check namespace for media files } }else{ $item['perm'] = AUTH_DELETE; } // are we done here maybe? if($type == 'd'){ if(empty($opts['listdirs'])) return $return; //neither list nor recurse forbidden items: if(empty($opts['skipacl']) && !empty($opts['sneakyacl']) && $item['perm'] < AUTH_READ) return false; if(!empty($opts['dirmatch']) && !preg_match('/'.$opts['dirmatch'].'/',$file)) return $return; if(!empty($opts['nsmatch']) && !preg_match('/'.$opts['nsmatch'].'/',$item['ns'])) return $return; }else{ if(empty($opts['listfiles'])) return $return; if(empty($opts['skipacl']) && $item['perm'] < AUTH_READ) return $return; if(!empty($opts['pagesonly']) && (substr($file,-4) != '.txt')) return $return; if(empty($opts['showhidden']) && isHiddenPage($item['id'])) return $return; if(!empty($opts['filematch']) && !preg_match('/'.$opts['filematch'].'/',$file)) return $return; if(!empty($opts['idmatch']) && !preg_match('/'.$opts['idmatch'].'/',$item['id'])) return $return; } // still here? prepare the item $item['type'] = $type; $item['level'] = $lvl; $item['open'] = $return; if(!empty($opts['meta'])){ $item['file'] = \dokuwiki\Utf8\PhpString::basename($file); $item['size'] = filesize($base.'/'.$file); $item['mtime'] = filemtime($base.'/'.$file); $item['rev'] = $item['mtime']; $item['writable'] = is_writable($base.'/'.$file); $item['executable'] = is_executable($base.'/'.$file); } if($type == 'f'){ if(!empty($opts['hash'])) $item['hash'] = md5(io_readFile($base.'/'.$file,false)); if(!empty($opts['firsthead'])) $item['title'] = p_get_first_heading($item['id'],METADATA_DONT_RENDER); } // finally add the item $data[] = $item; return $return; } //Setup VIM: ex: et ts=4 : actions.php 0000644 00000003065 15233462216 0006725 0 ustar 00 <?php /** * DokuWiki Actions * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ use dokuwiki\Extension\Event; /** * All action processing starts here */ function act_dispatch(){ // always initialize on first dispatch (test request may dispatch mutliple times on one request) $router = \dokuwiki\ActionRouter::getInstance(true); $headers = array('Content-Type: text/html; charset=utf-8'); Event::createAndTrigger('ACTION_HEADERS_SEND',$headers,'act_sendheaders'); // clear internal variables unset($router); unset($headers); // make all globals available to the template extract($GLOBALS); include(template('main.php')); // output for the commands is now handled in inc/templates.php // in function tpl_content() } /** * Send the given headers using header() * * @param array $headers The headers that shall be sent */ function act_sendheaders($headers) { foreach ($headers as $hdr) header($hdr); } /** * Sanitize the action command * * @author Andreas Gohr <andi@splitbrain.org> * * @param array|string $act * @return string */ function act_clean($act){ // check if the action was given as array key if(is_array($act)){ list($act) = array_keys($act); } //remove all bad chars $act = strtolower($act); $act = preg_replace('/[^1-9a-z_]+/','',$act); if($act == 'export_html') $act = 'export_xhtml'; if($act == 'export_htmlbody') $act = 'export_xhtmlbody'; if($act === '') $act = 'show'; return $act; } SafeFN.class.php 0000644 00000014277 15233462216 0007502 0 ustar 00 <?php /** * Class to safely store UTF-8 in a Filename * * Encodes a utf8 string using only the following characters 0-9a-z_.-% * characters 0-9a-z in the original string are preserved, "plain". * all other characters are represented in a substring that starts * with '%' are "converted". * The transition from converted substrings to plain characters is * marked with a '.' * * @author Christopher Smith <chris@jalakai.co.uk> * @date 2010-04-02 */ class SafeFN { // 'safe' characters are a superset of $plain, $pre_indicator and $post_indicator private static $plain = '-./[_0123456789abcdefghijklmnopqrstuvwxyz'; // these characters aren't converted private static $pre_indicator = '%'; private static $post_indicator = ']'; /** * Convert an UTF-8 string to a safe ASCII String * * conversion process * - if codepoint is a plain or post_indicator character, * - if previous character was "converted", append post_indicator to output, clear "converted" flag * - append ascii byte for character to output * (continue to next character) * * - if codepoint is a pre_indicator character, * - append ascii byte for character to output, set "converted" flag * (continue to next character) * * (all remaining characters) * - reduce codepoint value for non-printable ASCII characters (0x00 - 0x1f). Space becomes our zero. * - convert reduced value to base36 (0-9a-z) * - append $pre_indicator characater followed by base36 string to output, set converted flag * (continue to next character) * * @param string $filename a utf8 string, should only include printable characters - not 0x00-0x1f * @return string an encoded representation of $filename using only 'safe' ASCII characters * * @author Christopher Smith <chris@jalakai.co.uk> */ public static function encode($filename) { return self::unicodeToSafe(\dokuwiki\Utf8\Unicode::fromUtf8($filename)); } /** * decoding process * - split the string into substrings at any occurrence of pre or post indicator characters * - check the first character of the substring * - if its not a pre_indicator character * - if previous character was converted, skip over post_indicator character * - copy codepoint values of remaining characters to the output array * - clear any converted flag * (continue to next substring) * * _ else (its a pre_indicator character) * - if string length is 1, copy the post_indicator character to the output array * (continue to next substring) * * - else (string length > 1) * - skip the pre-indicator character and convert remaining string from base36 to base10 * - increase codepoint value for non-printable ASCII characters (add 0x20) * - append codepoint to output array * (continue to next substring) * * @param string $filename a 'safe' encoded ASCII string, * @return string decoded utf8 representation of $filename * * @author Christopher Smith <chris@jalakai.co.uk> */ public static function decode($filename) { return \dokuwiki\Utf8\Unicode::toUtf8(self::safeToUnicode(strtolower($filename))); } public static function validatePrintableUtf8($printable_utf8) { return !preg_match('#[\x01-\x1f]#',$printable_utf8); } public static function validateSafe($safe) { return !preg_match('#[^'.self::$plain.self::$post_indicator.self::$pre_indicator.']#',$safe); } /** * convert an array of unicode codepoints into 'safe_filename' format * * @param array int $unicode an array of unicode codepoints * @return string the unicode represented in 'safe_filename' format * * @author Christopher Smith <chris@jalakai.co.uk> */ private static function unicodeToSafe($unicode) { $safe = ''; $converted = false; foreach ($unicode as $codepoint) { if ($codepoint < 127 && (strpos(self::$plain.self::$post_indicator,chr($codepoint))!==false)) { if ($converted) { $safe .= self::$post_indicator; $converted = false; } $safe .= chr($codepoint); } else if ($codepoint == ord(self::$pre_indicator)) { $safe .= self::$pre_indicator; $converted = true; } else { $safe .= self::$pre_indicator.base_convert((string)($codepoint-32),10,36); $converted = true; } } if($converted) $safe .= self::$post_indicator; return $safe; } /** * convert a 'safe_filename' string into an array of unicode codepoints * * @param string $safe a filename in 'safe_filename' format * @return array int an array of unicode codepoints * * @author Christopher Smith <chris@jalakai.co.uk> */ private static function safeToUnicode($safe) { $unicode = array(); $split = preg_split('#(?=['.self::$post_indicator.self::$pre_indicator.'])#',$safe,-1,PREG_SPLIT_NO_EMPTY); $converted = false; foreach ($split as $sub) { $len = strlen($sub); if ($sub[0] != self::$pre_indicator) { // plain (unconverted) characters, optionally starting with a post_indicator // set initial value to skip any post_indicator for ($i=($converted?1:0); $i < $len; $i++) { $unicode[] = ord($sub[$i]); } $converted = false; } else if ($len==1) { // a pre_indicator character in the real data $unicode[] = ord($sub); $converted = true; } else { // a single codepoint in base36, adjusted for initial 32 non-printable chars $unicode[] = 32 + (int)base_convert(substr($sub,1),36,10); $converted = true; } } return $unicode; } } infoutils.php 0000644 00000040515 15233462216 0007302 0 ustar 00 <?php /** * Information and debugging functions * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ use dokuwiki\HTTP\DokuHTTPClient; if(!defined('DOKU_MESSAGEURL')){ if(in_array('ssl', stream_get_transports())) { define('DOKU_MESSAGEURL','https://update.dokuwiki.org/check/'); }else{ define('DOKU_MESSAGEURL','http://update.dokuwiki.org/check/'); } } /** * Check for new messages from upstream * * @author Andreas Gohr <andi@splitbrain.org> */ function checkUpdateMessages(){ global $conf; global $INFO; global $updateVersion; if(!$conf['updatecheck']) return; if($conf['useacl'] && !$INFO['ismanager']) return; $cf = getCacheName($updateVersion, '.updmsg'); $lm = @filemtime($cf); $is_http = substr(DOKU_MESSAGEURL, 0, 5) != 'https'; // check if new messages needs to be fetched if($lm < time()-(60*60*24) || $lm < @filemtime(DOKU_INC.DOKU_SCRIPT)){ @touch($cf); dbglog("checkUpdateMessages(): downloading messages to ".$cf.($is_http?' (without SSL)':' (with SSL)')); $http = new DokuHTTPClient(); $http->timeout = 12; $resp = $http->get(DOKU_MESSAGEURL.$updateVersion); if(is_string($resp) && ($resp == "" || substr(trim($resp), -1) == '%')) { // basic sanity check that this is either an empty string response (ie "no messages") // or it looks like one of our messages, not WiFi login or other interposed response io_saveFile($cf,$resp); } else { dbglog("checkUpdateMessages(): unexpected HTTP response received"); } }else{ dbglog("checkUpdateMessages(): messages up to date"); } $data = io_readFile($cf); // show messages through the usual message mechanism $msgs = explode("\n%\n",$data); foreach($msgs as $msg){ if($msg) msg($msg,2); } } /** * Return DokuWiki's version (split up in date and type) * * @author Andreas Gohr <andi@splitbrain.org> */ function getVersionData(){ $version = array(); //import version string if(file_exists(DOKU_INC.'VERSION')){ //official release $version['date'] = trim(io_readFile(DOKU_INC.'VERSION')); $version['type'] = 'Release'; }elseif(is_dir(DOKU_INC.'.git')){ $version['type'] = 'Git'; $version['date'] = 'unknown'; $inventory = DOKU_INC.'.git/logs/HEAD'; if(is_file($inventory)){ $sz = filesize($inventory); $seek = max(0,$sz-2000); // read from back of the file $fh = fopen($inventory,'rb'); fseek($fh,$seek); $chunk = fread($fh,2000); fclose($fh); $chunk = trim($chunk); $chunk = @array_pop(explode("\n",$chunk)); //last log line $chunk = @array_shift(explode("\t",$chunk)); //strip commit msg $chunk = explode(" ",$chunk); array_pop($chunk); //strip timezone $date = date('Y-m-d',array_pop($chunk)); if($date) $version['date'] = $date; } }else{ global $updateVersion; $version['date'] = 'update version '.$updateVersion; $version['type'] = 'snapshot?'; } return $version; } /** * Return DokuWiki's version (as a string) * * @author Anika Henke <anika@selfthinker.org> */ function getVersion(){ $version = getVersionData(); return $version['type'].' '.$version['date']; } /** * Run a few sanity checks * * @author Andreas Gohr <andi@splitbrain.org> */ function check(){ global $conf; global $INFO; /* @var Input $INPUT */ global $INPUT; if ($INFO['isadmin'] || $INFO['ismanager']){ msg('DokuWiki version: '.getVersion(),1); if(version_compare(phpversion(),'5.6.0','<')){ msg('Your PHP version is too old ('.phpversion().' vs. 5.6.0+ needed)',-1); }else{ msg('PHP version '.phpversion(),1); } } else { if(version_compare(phpversion(),'5.6.0','<')){ msg('Your PHP version is too old',-1); } } $mem = (int) php_to_byte(ini_get('memory_limit')); if($mem){ if ($mem === -1) { msg('PHP memory is unlimited', 1); } else if ($mem < 16777216) { msg('PHP is limited to less than 16MB RAM (' . filesize_h($mem) . '). Increase memory_limit in php.ini', -1); } else if ($mem < 20971520) { msg('PHP is limited to less than 20MB RAM (' . filesize_h($mem) . '), you might encounter problems with bigger pages. Increase memory_limit in php.ini', -1); } else if ($mem < 33554432) { msg('PHP is limited to less than 32MB RAM (' . filesize_h($mem) . '), but that should be enough in most cases. If not, increase memory_limit in php.ini', 0); } else { msg('More than 32MB RAM (' . filesize_h($mem) . ') available.', 1); } } if(is_writable($conf['changelog'])){ msg('Changelog is writable',1); }else{ if (file_exists($conf['changelog'])) { msg('Changelog is not writable',-1); } } if (isset($conf['changelog_old']) && file_exists($conf['changelog_old'])) { msg('Old changelog exists', 0); } if (file_exists($conf['changelog'].'_failed')) { msg('Importing old changelog failed', -1); } else if (file_exists($conf['changelog'].'_importing')) { msg('Importing old changelog now.', 0); } else if (file_exists($conf['changelog'].'_import_ok')) { msg('Old changelog imported', 1); if (!plugin_isdisabled('importoldchangelog')) { msg('Importoldchangelog plugin not disabled after import', -1); } } if(is_writable(DOKU_CONF)){ msg('conf directory is writable',1); }else{ msg('conf directory is not writable',-1); } if($conf['authtype'] == 'plain'){ global $config_cascade; if(is_writable($config_cascade['plainauth.users']['default'])){ msg('conf/users.auth.php is writable',1); }else{ msg('conf/users.auth.php is not writable',0); } } if(function_exists('mb_strpos')){ if(defined('UTF8_NOMBSTRING')){ msg('mb_string extension is available but will not be used',0); }else{ msg('mb_string extension is available and will be used',1); if(ini_get('mbstring.func_overload') != 0){ msg('mb_string function overloading is enabled, this will cause problems and should be disabled',-1); } } }else{ msg('mb_string extension not available - PHP only replacements will be used',0); } if (!UTF8_PREGSUPPORT) { msg('PHP is missing UTF-8 support in Perl-Compatible Regular Expressions (PCRE)', -1); } if (!UTF8_PROPERTYSUPPORT) { msg('PHP is missing Unicode properties support in Perl-Compatible Regular Expressions (PCRE)', -1); } $loc = setlocale(LC_ALL, 0); if(!$loc){ msg('No valid locale is set for your PHP setup. You should fix this',-1); }elseif(stripos($loc,'utf') === false){ msg('Your locale <code>'.hsc($loc).'</code> seems not to be a UTF-8 locale, you should fix this if you encounter problems.',0); }else{ msg('Valid locale '.hsc($loc).' found.', 1); } if($conf['allowdebug']){ msg('Debugging support is enabled. If you don\'t need it you should set $conf[\'allowdebug\'] = 0',-1); }else{ msg('Debugging support is disabled',1); } if($INFO['userinfo']['name']){ msg('You are currently logged in as '.$INPUT->server->str('REMOTE_USER').' ('.$INFO['userinfo']['name'].')',0); msg('You are part of the groups '.join($INFO['userinfo']['grps'],', '),0); }else{ msg('You are currently not logged in',0); } msg('Your current permission for this page is '.$INFO['perm'],0); if (file_exists($INFO['filepath']) && is_writable($INFO['filepath'])) { msg('The current page is writable by the webserver', 1); } elseif (!file_exists($INFO['filepath']) && is_writable(dirname($INFO['filepath']))) { msg('The current page can be created by the webserver', 1); } else { msg('The current page is not writable by the webserver', -1); } if ($INFO['writable']) { msg('The current page is writable by you', 1); } else { msg('The current page is not writable by you', -1); } // Check for corrupted search index $lengths = idx_listIndexLengths(); $index_corrupted = false; foreach ($lengths as $length) { if (count(idx_getIndex('w', $length)) != count(idx_getIndex('i', $length))) { $index_corrupted = true; break; } } foreach (idx_getIndex('metadata', '') as $index) { if (count(idx_getIndex($index.'_w', '')) != count(idx_getIndex($index.'_i', ''))) { $index_corrupted = true; break; } } if($index_corrupted) { msg( 'The search index is corrupted. It might produce wrong results and most probably needs to be rebuilt. See <a href="http://www.dokuwiki.org/faq:searchindex">faq:searchindex</a> for ways to rebuild the search index.', -1 ); } elseif(!empty($lengths)) { msg('The search index seems to be working', 1); } else { msg( 'The search index is empty. See <a href="http://www.dokuwiki.org/faq:searchindex">faq:searchindex</a> for help on how to fix the search index. If the default indexer isn\'t used or the wiki is actually empty this is normal.' ); } // rough time check $http = new DokuHTTPClient(); $http->max_redirect = 0; $http->timeout = 3; $http->sendRequest('http://www.dokuwiki.org', '', 'HEAD'); $now = time(); if(isset($http->resp_headers['date'])) { $time = strtotime($http->resp_headers['date']); $diff = $time - $now; if(abs($diff) < 4) { msg("Server time seems to be okay. Diff: {$diff}s", 1); } else { msg("Your server's clock seems to be out of sync! Consider configuring a sync with a NTP server. Diff: {$diff}s"); } } } /** * Display a message to the user * * If HTTP headers were not sent yet the message is added * to the global message array else it's printed directly * using html_msgarea() * * Triggers INFOUTIL_MSG_SHOW * * @see html_msgarea() * @param string $message * @param int $lvl -1 = error, 0 = info, 1 = success, 2 = notify * @param string $line line number * @param string $file file number * @param int $allow who's allowed to see the message, see MSG_* constants */ function msg($message,$lvl=0,$line='',$file='',$allow=MSG_PUBLIC){ global $MSG, $MSG_shown; static $errors = [ -1 => 'error', 0 => 'info', 1 => 'success', 2 => 'notify', ]; $msgdata = [ 'msg' => $message, 'lvl' => $errors[$lvl], 'allow' => $allow, 'line' => $line, 'file' => $file, ]; $evt = new \dokuwiki\Extension\Event('INFOUTIL_MSG_SHOW', $msgdata); if ($evt->advise_before()) { /* Show msg normally - event could suppress message show */ if($msgdata['line'] || $msgdata['file']) { $basename = \dokuwiki\Utf8\PhpString::basename($msgdata['file']); $msgdata['msg'] .=' ['.$basename.':'.$msgdata['line'].']'; } if(!isset($MSG)) $MSG = array(); $MSG[] = $msgdata; if(isset($MSG_shown) || headers_sent()){ if(function_exists('html_msgarea')){ html_msgarea(); }else{ print "ERROR(".$msgdata['lvl'].") ".$msgdata['msg']."\n"; } unset($GLOBALS['MSG']); } } $evt->advise_after(); unset($evt); } /** * Determine whether the current user is allowed to view the message * in the $msg data structure * * @param $msg array dokuwiki msg structure * msg => string, the message * lvl => int, level of the message (see msg() function) * allow => int, flag used to determine who is allowed to see the message * see MSG_* constants * @return bool */ function info_msg_allowed($msg){ global $INFO, $auth; // is the message public? - everyone and anyone can see it if (empty($msg['allow']) || ($msg['allow'] == MSG_PUBLIC)) return true; // restricted msg, but no authentication if (empty($auth)) return false; switch ($msg['allow']){ case MSG_USERS_ONLY: return !empty($INFO['userinfo']); case MSG_MANAGERS_ONLY: return $INFO['ismanager']; case MSG_ADMINS_ONLY: return $INFO['isadmin']; default: trigger_error('invalid msg allow restriction. msg="'.$msg['msg'].'" allow='.$msg['allow'].'"', E_USER_WARNING); return $INFO['isadmin']; } return false; } /** * print debug messages * * little function to print the content of a var * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $msg * @param bool $hidden */ function dbg($msg,$hidden=false){ if($hidden){ echo "<!--\n"; print_r($msg); echo "\n-->"; }else{ echo '<pre class="dbg">'; echo hsc(print_r($msg,true)); echo '</pre>'; } } /** * Print info to a log file * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $msg * @param string $header */ function dbglog($msg,$header=''){ global $conf; /* @var Input $INPUT */ global $INPUT; // The debug log isn't automatically cleaned thus only write it when // debugging has been enabled by the user. if($conf['allowdebug'] !== 1) return; if(is_object($msg) || is_array($msg)){ $msg = print_r($msg,true); } if($header) $msg = "$header\n$msg"; $file = $conf['cachedir'].'/debug.log'; $fh = fopen($file,'a'); if($fh){ fwrite($fh,date('H:i:s ').$INPUT->server->str('REMOTE_ADDR').': '.$msg."\n"); fclose($fh); } } /** * Log accesses to deprecated fucntions to the debug log * * @param string $alternative The function or method that should be used instead * @triggers INFO_DEPRECATION_LOG */ function dbg_deprecated($alternative = '') { \dokuwiki\Debug\DebugHelper::dbgDeprecatedFunction($alternative, 2); } /** * Print a reversed, prettyprinted backtrace * * @author Gary Owen <gary_owen@bigfoot.com> */ function dbg_backtrace(){ // Get backtrace $backtrace = debug_backtrace(); // Unset call to debug_print_backtrace array_shift($backtrace); // Iterate backtrace $calls = array(); $depth = count($backtrace) - 1; foreach ($backtrace as $i => $call) { $location = $call['file'] . ':' . $call['line']; $function = (isset($call['class'])) ? $call['class'] . $call['type'] . $call['function'] : $call['function']; $params = array(); if (isset($call['args'])){ foreach($call['args'] as $arg){ if(is_object($arg)){ $params[] = '[Object '.get_class($arg).']'; }elseif(is_array($arg)){ $params[] = '[Array]'; }elseif(is_null($arg)){ $params[] = '[NULL]'; }else{ $params[] = (string) '"'.$arg.'"'; } } } $params = implode(', ',$params); $calls[$depth - $i] = sprintf('%s(%s) called at %s', $function, str_replace("\n", '\n', $params), $location); } ksort($calls); return implode("\n", $calls); } /** * Remove all data from an array where the key seems to point to sensitive data * * This is used to remove passwords, mail addresses and similar data from the * debug output * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $data */ function debug_guard(&$data){ foreach($data as $key => $value){ if(preg_match('/(notify|pass|auth|secret|ftp|userinfo|token|buid|mail|proxy)/i',$key)){ $data[$key] = '***'; continue; } if(is_array($value)) debug_guard($data[$key]); } } TaskRunner.php 0000644 00000016624 15233462216 0007366 0 ustar 00 <?php namespace dokuwiki; use dokuwiki\Extension\Event; use dokuwiki\Sitemap\Mapper; use dokuwiki\Subscriptions\BulkSubscriptionSender; /** * Class TaskRunner * * Run an asynchronous task. */ class TaskRunner { /** * Run the next task * * @todo refactor to remove dependencies on globals * @triggers INDEXER_TASKS_RUN */ public function run() { global $INPUT, $conf, $ID; // keep running after browser closes connection @ignore_user_abort(true); // check if user abort worked, if yes send output early $defer = !@ignore_user_abort() || $conf['broken_iua']; $output = $INPUT->has('debug') && $conf['allowdebug']; if(!$defer && !$output){ $this->sendGIF(); } $ID = cleanID($INPUT->str('id')); // Catch any possible output (e.g. errors) if(!$output) { ob_start(); } else { header('Content-Type: text/plain'); } // run one of the jobs $tmp = []; // No event data $evt = new Event('INDEXER_TASKS_RUN', $tmp); if ($evt->advise_before()) { $this->runIndexer() or $this->runSitemapper() or $this->sendDigest() or $this->runTrimRecentChanges() or $this->runTrimRecentChanges(true) or $evt->advise_after(); } if(!$output) { ob_end_clean(); if($defer) { $this->sendGIF(); } } } /** * Just send a 1x1 pixel blank gif to the browser * * @author Andreas Gohr <andi@splitbrain.org> * @author Harry Fuecks <fuecks@gmail.com> */ protected function sendGIF() { $img = base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7'); header('Content-Type: image/gif'); header('Content-Length: '.strlen($img)); header('Connection: Close'); print $img; tpl_flush(); // Browser should drop connection after this // Thinks it's got the whole image } /** * Trims the recent changes cache (or imports the old changelog) as needed. * * @param bool $media_changes If the media changelog shall be trimmed instead of * the page changelog * * @return bool * @triggers TASK_RECENTCHANGES_TRIM * @author Ben Coburn <btcoburn@silicodon.net> */ protected function runTrimRecentChanges($media_changes = false) { global $conf; echo "runTrimRecentChanges($media_changes): started" . NL; $fn = ($media_changes ? $conf['media_changelog'] : $conf['changelog']); // Trim the Recent Changes // Trims the recent changes cache to the last $conf['changes_days'] recent // changes or $conf['recent'] items, which ever is larger. // The trimming is only done once a day. if (file_exists($fn) && (@filemtime($fn . '.trimmed') + 86400) < time() && !file_exists($fn . '_tmp')) { @touch($fn . '.trimmed'); io_lock($fn); $lines = file($fn); if (count($lines) <= $conf['recent']) { // nothing to trim io_unlock($fn); echo "runTrimRecentChanges($media_changes): finished" . NL; return false; } io_saveFile($fn . '_tmp', ''); // presave tmp as 2nd lock $trim_time = time() - $conf['recent_days'] * 86400; $out_lines = []; $old_lines = []; for ($i = 0; $i < count($lines); $i++) { $log = parseChangelogLine($lines[$i]); if ($log === false) { continue; // discard junk } if ($log['date'] < $trim_time) { // keep old lines for now (append .$i to prevent key collisions) $old_lines[$log['date'] . ".$i"] = $lines[$i]; } else { // definitely keep these lines $out_lines[$log['date'] . ".$i"] = $lines[$i]; } } if (count($lines) == count($out_lines)) { // nothing to trim @unlink($fn . '_tmp'); io_unlock($fn); echo "runTrimRecentChanges($media_changes): finished" . NL; return false; } // sort the final result, it shouldn't be necessary, // however the extra robustness in making the changelog cache self-correcting is worth it ksort($out_lines); $extra = $conf['recent'] - count($out_lines); // do we need extra lines do bring us up to minimum if ($extra > 0) { ksort($old_lines); $out_lines = array_merge(array_slice($old_lines, -$extra), $out_lines); } $eventData = [ 'isMedia' => $media_changes, 'trimmedChangelogLines' => $out_lines, 'removedChangelogLines' => $extra > 0 ? array_slice($old_lines, 0, -$extra) : $old_lines, ]; Event::createAndTrigger('TASK_RECENTCHANGES_TRIM', $eventData); $out_lines = $eventData['trimmedChangelogLines']; // save trimmed changelog io_saveFile($fn . '_tmp', implode('', $out_lines)); @unlink($fn); if (!rename($fn . '_tmp', $fn)) { // rename failed so try another way... io_unlock($fn); io_saveFile($fn, implode('', $out_lines)); @unlink($fn . '_tmp'); } else { io_unlock($fn); } echo "runTrimRecentChanges($media_changes): finished" . NL; return true; } // nothing done echo "runTrimRecentChanges($media_changes): finished" . NL; return false; } /** * Runs the indexer for the current page * * @author Andreas Gohr <andi@splitbrain.org> */ protected function runIndexer() { global $ID; print 'runIndexer(): started' . NL; if ((string) $ID === '') { return false; } // do the work return idx_addPage($ID, true); } /** * Builds a Google Sitemap of all public pages known to the indexer * * The map is placed in the root directory named sitemap.xml.gz - This * file needs to be writable! * * @author Andreas Gohr * @link https://www.google.com/webmasters/sitemaps/docs/en/about.html */ protected function runSitemapper() { print 'runSitemapper(): started' . NL; $result = Mapper::generate() && Mapper::pingSearchEngines(); print 'runSitemapper(): finished' . NL; return $result; } /** * Send digest and list mails for all subscriptions which are in effect for the * current page * * @author Adrian Lang <lang@cosmocode.de> */ protected function sendDigest() { global $ID; echo 'sendDigest(): started' . NL; if (!actionOK('subscribe')) { echo 'sendDigest(): disabled' . NL; return false; } $sub = new BulkSubscriptionSender(); $sent = $sub->sendBulk($ID); echo "sendDigest(): sent $sent mails" . NL; echo 'sendDigest(): finished' . NL; return (bool)$sent; } } Parsing/Parser.php 0000644 00000006362 15233462216 0010127 0 ustar 00 <?php namespace dokuwiki\Parsing; use Doku_Handler; use dokuwiki\Parsing\Lexer\Lexer; use dokuwiki\Parsing\ParserMode\Base; use dokuwiki\Parsing\ParserMode\ModeInterface; /** * Sets up the Lexer with modes and points it to the Handler * For an intro to the Lexer see: wiki:parser */ class Parser { /** @var Doku_Handler */ protected $handler; /** @var Lexer $lexer */ protected $lexer; /** @var ModeInterface[] $modes */ protected $modes = array(); /** @var bool mode connections may only be set up once */ protected $connected = false; /** * dokuwiki\Parsing\Doku_Parser constructor. * * @param Doku_Handler $handler */ public function __construct(Doku_Handler $handler) { $this->handler = $handler; } /** * Adds the base mode and initialized the lexer * * @param Base $BaseMode */ protected function addBaseMode($BaseMode) { $this->modes['base'] = $BaseMode; if(!$this->lexer) { $this->lexer = new Lexer($this->handler, 'base', true); } $this->modes['base']->Lexer = $this->lexer; } /** * Add a new syntax element (mode) to the parser * * PHP preserves order of associative elements * Mode sequence is important * * @param string $name * @param ModeInterface $Mode */ public function addMode($name, ModeInterface $Mode) { if(!isset($this->modes['base'])) { $this->addBaseMode(new Base()); } $Mode->Lexer = $this->lexer; // FIXME should be done by setter $this->modes[$name] = $Mode; } /** * Connect all modes with each other * * This is the last step before actually parsing. */ protected function connectModes() { if($this->connected) { return; } foreach(array_keys($this->modes) as $mode) { // Base isn't connected to anything if($mode == 'base') { continue; } $this->modes[$mode]->preConnect(); foreach(array_keys($this->modes) as $cm) { if($this->modes[$cm]->accepts($mode)) { $this->modes[$mode]->connectTo($cm); } } $this->modes[$mode]->postConnect(); } $this->connected = true; } /** * Parses wiki syntax to instructions * * @param string $doc the wiki syntax text * @return array instructions */ public function parse($doc) { $this->connectModes(); // Normalize CRs and pad doc $doc = "\n" . str_replace("\r\n", "\n", $doc) . "\n"; $this->lexer->parse($doc); 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; } } Parsing/Lexer/StateStack.php 0000644 00000002530 15233462216 0012011 0 ustar 00 <?php /** * Lexer adapted from Simple Test: http://sourceforge.net/projects/simpletest/ * For an intro to the Lexer see: * https://web.archive.org/web/20120125041816/http://www.phppatterns.com/docs/develop/simple_test_lexer_notes * * @author Marcus Baker http://www.lastcraft.com */ namespace dokuwiki\Parsing\Lexer; /** * States for a stack machine. */ class StateStack { protected $stack; /** * Constructor. Starts in named state. * @param string $start Starting state name. */ public function __construct($start) { $this->stack = array($start); } /** * Accessor for current state. * @return string State. */ public function getCurrent() { return $this->stack[count($this->stack) - 1]; } /** * Adds a state to the stack and sets it to be the current state. * * @param string $state New state. */ public function enter($state) { array_push($this->stack, $state); } /** * Leaves the current state and reverts * to the previous one. * @return boolean false if we attempt to drop off the bottom of the list. */ public function leave() { if (count($this->stack) == 1) { return false; } array_pop($this->stack); return true; } } Parsing/Lexer/Lexer.php 0000644 00000026235 15233462216 0011032 0 ustar 00 <?php /** * Lexer adapted from Simple Test: http://sourceforge.net/projects/simpletest/ * For an intro to the Lexer see: * https://web.archive.org/web/20120125041816/http://www.phppatterns.com/docs/develop/simple_test_lexer_notes * * @author Marcus Baker http://www.lastcraft.com */ namespace dokuwiki\Parsing\Lexer; /** * Accepts text and breaks it into tokens. * * Some optimisation to make the sure the content is only scanned by the PHP regex * parser once. Lexer modes must not start with leading underscores. */ class Lexer { /** @var ParallelRegex[] */ protected $regexes; /** @var \Doku_Handler */ protected $handler; /** @var StateStack */ protected $modeStack; /** @var array mode "rewrites" */ protected $mode_handlers; /** @var bool case sensitive? */ protected $case; /** * Sets up the lexer in case insensitive matching by default. * * @param \Doku_Handler $handler Handling strategy by reference. * @param string $start Starting handler. * @param boolean $case True for case sensitive. */ public function __construct($handler, $start = "accept", $case = false) { $this->case = $case; $this->regexes = array(); $this->handler = $handler; $this->modeStack = new StateStack($start); $this->mode_handlers = array(); } /** * Adds a token search pattern for a particular parsing mode. * * The pattern does not change the current mode. * * @param string $pattern Perl style regex, but ( and ) * lose the usual meaning. * @param string $mode Should only apply this * pattern when dealing with * this type of input. */ public function addPattern($pattern, $mode = "accept") { if (! isset($this->regexes[$mode])) { $this->regexes[$mode] = new ParallelRegex($this->case); } $this->regexes[$mode]->addPattern($pattern); } /** * Adds a pattern that will enter a new parsing mode. * * Useful for entering parenthesis, strings, tags, etc. * * @param string $pattern Perl style regex, but ( and ) lose the usual meaning. * @param string $mode Should only apply this pattern when dealing with this type of input. * @param string $new_mode Change parsing to this new nested mode. */ public function addEntryPattern($pattern, $mode, $new_mode) { if (! isset($this->regexes[$mode])) { $this->regexes[$mode] = new ParallelRegex($this->case); } $this->regexes[$mode]->addPattern($pattern, $new_mode); } /** * Adds a pattern that will exit the current mode and re-enter the previous one. * * @param string $pattern Perl style regex, but ( and ) lose the usual meaning. * @param string $mode Mode to leave. */ public function addExitPattern($pattern, $mode) { if (! isset($this->regexes[$mode])) { $this->regexes[$mode] = new ParallelRegex($this->case); } $this->regexes[$mode]->addPattern($pattern, "__exit"); } /** * Adds a pattern that has a special mode. * * Acts as an entry and exit pattern in one go, effectively calling a special * parser handler for this token only. * * @param string $pattern Perl style regex, but ( and ) lose the usual meaning. * @param string $mode Should only apply this pattern when dealing with this type of input. * @param string $special Use this mode for this one token. */ public function addSpecialPattern($pattern, $mode, $special) { if (! isset($this->regexes[$mode])) { $this->regexes[$mode] = new ParallelRegex($this->case); } $this->regexes[$mode]->addPattern($pattern, "_$special"); } /** * Adds a mapping from a mode to another handler. * * @param string $mode Mode to be remapped. * @param string $handler New target handler. */ public function mapHandler($mode, $handler) { $this->mode_handlers[$mode] = $handler; } /** * Splits the page text into tokens. * * Will fail if the handlers report an error or if no content is consumed. If successful then each * unparsed and parsed token invokes a call to the held listener. * * @param string $raw Raw HTML text. * @return boolean True on success, else false. */ public function parse($raw) { if (! isset($this->handler)) { return false; } $initialLength = strlen($raw); $length = $initialLength; $pos = 0; while (is_array($parsed = $this->reduce($raw))) { list($unmatched, $matched, $mode) = $parsed; $currentLength = strlen($raw); $matchPos = $initialLength - $currentLength - strlen($matched); if (! $this->dispatchTokens($unmatched, $matched, $mode, $pos, $matchPos)) { return false; } if ($currentLength == $length) { return false; } $length = $currentLength; $pos = $initialLength - $currentLength; } if (!$parsed) { return false; } return $this->invokeHandler($raw, DOKU_LEXER_UNMATCHED, $pos); } /** * Gives plugins access to the mode stack * * @return StateStack */ public function getModeStack() { return $this->modeStack; } /** * Sends the matched token and any leading unmatched * text to the parser changing the lexer to a new * mode if one is listed. * * @param string $unmatched Unmatched leading portion. * @param string $matched Actual token match. * @param bool|string $mode Mode after match. A boolean false mode causes no change. * @param int $initialPos * @param int $matchPos Current byte index location in raw doc thats being parsed * @return boolean False if there was any error from the parser. */ protected function dispatchTokens($unmatched, $matched, $mode, $initialPos, $matchPos) { if (! $this->invokeHandler($unmatched, DOKU_LEXER_UNMATCHED, $initialPos)) { return false; } if ($this->isModeEnd($mode)) { if (! $this->invokeHandler($matched, DOKU_LEXER_EXIT, $matchPos)) { return false; } return $this->modeStack->leave(); } if ($this->isSpecialMode($mode)) { $this->modeStack->enter($this->decodeSpecial($mode)); if (! $this->invokeHandler($matched, DOKU_LEXER_SPECIAL, $matchPos)) { return false; } return $this->modeStack->leave(); } if (is_string($mode)) { $this->modeStack->enter($mode); return $this->invokeHandler($matched, DOKU_LEXER_ENTER, $matchPos); } return $this->invokeHandler($matched, DOKU_LEXER_MATCHED, $matchPos); } /** * Tests to see if the new mode is actually to leave the current mode and pop an item from the matching * mode stack. * * @param string $mode Mode to test. * @return boolean True if this is the exit mode. */ protected function isModeEnd($mode) { return ($mode === "__exit"); } /** * Test to see if the mode is one where this mode is entered for this token only and automatically * leaves immediately afterwoods. * * @param string $mode Mode to test. * @return boolean True if this is the exit mode. */ protected function isSpecialMode($mode) { return (strncmp($mode, "_", 1) == 0); } /** * Strips the magic underscore marking single token modes. * * @param string $mode Mode to decode. * @return string Underlying mode name. */ protected function decodeSpecial($mode) { return substr($mode, 1); } /** * Calls the parser method named after the current mode. * * Empty content will be ignored. The lexer has a parser handler for each mode in the lexer. * * @param string $content Text parsed. * @param boolean $is_match Token is recognised rather * than unparsed data. * @param int $pos Current byte index location in raw doc * thats being parsed * @return bool */ protected function invokeHandler($content, $is_match, $pos) { if (($content === "") || ($content === false)) { return true; } $handler = $this->modeStack->getCurrent(); if (isset($this->mode_handlers[$handler])) { $handler = $this->mode_handlers[$handler]; } // modes starting with plugin_ are all handled by the same // handler but with an additional parameter if (substr($handler, 0, 7)=='plugin_') { list($handler,$plugin) = explode('_', $handler, 2); return $this->handler->$handler($content, $is_match, $pos, $plugin); } return $this->handler->$handler($content, $is_match, $pos); } /** * Tries to match a chunk of text and if successful removes the recognised chunk and any leading * unparsed data. Empty strings will not be matched. * * @param string $raw The subject to parse. This is the content that will be eaten. * @return array|bool Three item list of unparsed content followed by the * recognised token and finally the action the parser is to take. * True if no match, false if there is a parsing error. */ protected function reduce(&$raw) { if (! isset($this->regexes[$this->modeStack->getCurrent()])) { return false; } if ($raw === "") { return true; } if ($action = $this->regexes[$this->modeStack->getCurrent()]->split($raw, $split)) { list($unparsed, $match, $raw) = $split; return array($unparsed, $match, $action); } return true; } /** * Escapes regex characters other than (, ) and / * * @param string $str * @return string */ public static function escape($str) { $chars = array( '/\\\\/', '/\./', '/\+/', '/\*/', '/\?/', '/\[/', '/\^/', '/\]/', '/\$/', '/\{/', '/\}/', '/\=/', '/\!/', '/\</', '/\>/', '/\|/', '/\:/' ); $escaped = array( '\\\\\\\\', '\.', '\+', '\*', '\?', '\[', '\^', '\]', '\$', '\{', '\}', '\=', '\!', '\<', '\>', '\|', '\:' ); return preg_replace($chars, $escaped, $str); } } Parsing/Lexer/ParallelRegex.php 0000644 00000015751 15233462216 0012503 0 ustar 00 <?php /** * Lexer adapted from Simple Test: http://sourceforge.net/projects/simpletest/ * For an intro to the Lexer see: * https://web.archive.org/web/20120125041816/http://www.phppatterns.com/docs/develop/simple_test_lexer_notes * * @author Marcus Baker http://www.lastcraft.com */ namespace dokuwiki\Parsing\Lexer; /** * Compounded regular expression. * * Any of the contained patterns could match and when one does it's label is returned. */ class ParallelRegex { /** @var string[] patterns to match */ protected $patterns; /** @var string[] labels for above patterns */ protected $labels; /** @var string the compound regex matching all patterns */ protected $regex; /** @var bool case sensitive matching? */ protected $case; /** * Constructor. Starts with no patterns. * * @param boolean $case True for case sensitive, false * for insensitive. */ public function __construct($case) { $this->case = $case; $this->patterns = array(); $this->labels = array(); $this->regex = null; } /** * Adds a pattern with an optional label. * * @param mixed $pattern Perl style regex. Must be UTF-8 * encoded. If its a string, the (, ) * lose their meaning unless they * form part of a lookahead or * lookbehind assertation. * @param bool|string $label Label of regex to be returned * on a match. Label must be ASCII */ public function addPattern($pattern, $label = true) { $count = count($this->patterns); $this->patterns[$count] = $pattern; $this->labels[$count] = $label; $this->regex = null; } /** * Attempts to match all patterns at once against a string. * * @param string $subject String to match against. * @param string $match First matched portion of * subject. * @return bool|string False if no match found, label if label exists, true if not */ public function match($subject, &$match) { if (count($this->patterns) == 0) { return false; } if (! preg_match($this->getCompoundedRegex(), $subject, $matches)) { $match = ""; return false; } $match = $matches[0]; $size = count($matches); // FIXME this could be made faster by storing the labels as keys in a hashmap for ($i = 1; $i < $size; $i++) { if ($matches[$i] && isset($this->labels[$i - 1])) { return $this->labels[$i - 1]; } } return true; } /** * Attempts to split the string against all patterns at once * * @param string $subject String to match against. * @param array $split The split result: array containing, pre-match, match & post-match strings * @return boolean True on success. * * @author Christopher Smith <chris@jalakai.co.uk> */ public function split($subject, &$split) { if (count($this->patterns) == 0) { return false; } if (! preg_match($this->getCompoundedRegex(), $subject, $matches)) { if (function_exists('preg_last_error')) { $err = preg_last_error(); switch ($err) { case PREG_BACKTRACK_LIMIT_ERROR: msg('A PCRE backtrack error occured. Try to increase the pcre.backtrack_limit in php.ini', -1); break; case PREG_RECURSION_LIMIT_ERROR: msg('A PCRE recursion error occured. Try to increase the pcre.recursion_limit in php.ini', -1); break; case PREG_BAD_UTF8_ERROR: msg('A PCRE UTF-8 error occured. This might be caused by a faulty plugin', -1); break; case PREG_INTERNAL_ERROR: msg('A PCRE internal error occured. This might be caused by a faulty plugin', -1); break; } } $split = array($subject, "", ""); return false; } $idx = count($matches)-2; list($pre, $post) = preg_split($this->patterns[$idx].$this->getPerlMatchingFlags(), $subject, 2); $split = array($pre, $matches[0], $post); return isset($this->labels[$idx]) ? $this->labels[$idx] : true; } /** * Compounds the patterns into a single * regular expression separated with the * "or" operator. Caches the regex. * Will automatically escape (, ) and / tokens. * * @return null|string */ protected function getCompoundedRegex() { if ($this->regex == null) { $cnt = count($this->patterns); for ($i = 0; $i < $cnt; $i++) { /* * decompose the input pattern into "(", "(?", ")", * "[...]", "[]..]", "[^]..]", "[...[:...:]..]", "\x"... * elements. */ preg_match_all('/\\\\.|' . '\(\?|' . '[()]|' . '\[\^?\]?(?:\\\\.|\[:[^]]*:\]|[^]\\\\])*\]|' . '[^[()\\\\]+/', $this->patterns[$i], $elts); $pattern = ""; $level = 0; foreach ($elts[0] as $elt) { /* * for "(", ")" remember the nesting level, add "\" * only to the non-"(?" ones. */ switch ($elt) { case '(': $pattern .= '\('; break; case ')': if ($level > 0) $level--; /* closing (? */ else $pattern .= '\\'; $pattern .= ')'; break; case '(?': $level++; $pattern .= '(?'; break; default: if (substr($elt, 0, 1) == '\\') $pattern .= $elt; else $pattern .= str_replace('/', '\/', $elt); } } $this->patterns[$i] = "($pattern)"; } $this->regex = "/" . implode("|", $this->patterns) . "/" . $this->getPerlMatchingFlags(); } return $this->regex; } /** * Accessor for perl regex mode flags to use. * @return string Perl regex flags. */ protected function getPerlMatchingFlags() { return ($this->case ? "msS" : "msSi"); } } Parsing/Handler/Quote.php 0000644 00000005130 15233462216 0011335 0 ustar 00 <?php namespace dokuwiki\Parsing\Handler; class Quote extends AbstractRewriter { protected $quoteCalls = array(); /** @inheritdoc */ public function finalise() { $last_call = end($this->calls); $this->writeCall(array('quote_end',array(), $last_call[2])); $this->process(); $this->callWriter->finalise(); unset($this->callWriter); } /** @inheritdoc */ public function process() { $quoteDepth = 1; foreach ($this->calls as $call) { switch ($call[0]) { /** @noinspection PhpMissingBreakStatementInspection */ case 'quote_start': $this->quoteCalls[] = array('quote_open',array(),$call[2]); // fallthrough case 'quote_newline': $quoteLength = $this->getDepth($call[1][0]); if ($quoteLength > $quoteDepth) { $quoteDiff = $quoteLength - $quoteDepth; for ($i = 1; $i <= $quoteDiff; $i++) { $this->quoteCalls[] = array('quote_open',array(),$call[2]); } } elseif ($quoteLength < $quoteDepth) { $quoteDiff = $quoteDepth - $quoteLength; for ($i = 1; $i <= $quoteDiff; $i++) { $this->quoteCalls[] = array('quote_close',array(),$call[2]); } } else { if ($call[0] != 'quote_start') $this->quoteCalls[] = array('linebreak',array(),$call[2]); } $quoteDepth = $quoteLength; break; case 'quote_end': if ($quoteDepth > 1) { $quoteDiff = $quoteDepth - 1; for ($i = 1; $i <= $quoteDiff; $i++) { $this->quoteCalls[] = array('quote_close',array(),$call[2]); } } $this->quoteCalls[] = array('quote_close',array(),$call[2]); $this->callWriter->writeCalls($this->quoteCalls); break; default: $this->quoteCalls[] = $call; break; } } return $this->callWriter; } /** * @param string $marker * @return int */ protected function getDepth($marker) { preg_match('/>{1,}/', $marker, $matches); $quoteLength = strlen($matches[0]); return $quoteLength; } } Parsing/Handler/CallWriterInterface.php 0000644 00000001213 15233462216 0014127 0 ustar 00 <?php namespace dokuwiki\Parsing\Handler; interface CallWriterInterface { /** * Add a call to our call list * * @param array $call the call to be added */ public function writeCall($call); /** * Append a list of calls to our call list * * @param array[] $calls list of calls to be appended */ public function writeCalls($calls); /** * Explicit request to finish up and clean up NOW! * (probably because document end has been reached) * * If part of a CallWriter chain, call finalise on * the original call writer * */ public function finalise(); } Parsing/Handler/ReWriterInterface.php 0000644 00000002006 15233462216 0013623 0 ustar 00 <?php namespace dokuwiki\Parsing\Handler; /** * A ReWriter takes over from the orignal call writer and handles all new calls itself until * the process method is called and control is given back to the original writer. * * @property array[] $calls The list of current calls */ interface ReWriterInterface extends CallWriterInterface { /** * ReWriterInterface constructor. * * This rewriter will be registered as the new call writer in the Handler. * The original is passed as parameter * * @param CallWriterInterface $callWriter the original callwriter */ public function __construct(CallWriterInterface $callWriter); /** * Process any calls that have been added and add them to the * original call writer * * @return CallWriterInterface the orignal call writer */ public function process(); /** * Accessor for this rewriter's original CallWriter * * @return CallWriterInterface */ public function getCallWriter(); } Parsing/Handler/Block.php 0000644 00000015463 15233462216 0011304 0 ustar 00 <?php namespace dokuwiki\Parsing\Handler; /** * Handler for paragraphs * * @author Harry Fuecks <hfuecks@gmail.com> */ class Block { protected $calls = array(); protected $skipEol = false; protected $inParagraph = false; // Blocks these should not be inside paragraphs protected $blockOpen = array( 'header', 'listu_open','listo_open','listitem_open','listcontent_open', 'table_open','tablerow_open','tablecell_open','tableheader_open','tablethead_open', 'quote_open', 'code','file','hr','preformatted','rss', 'htmlblock','phpblock', 'footnote_open', ); protected $blockClose = array( 'header', 'listu_close','listo_close','listitem_close','listcontent_close', 'table_close','tablerow_close','tablecell_close','tableheader_close','tablethead_close', 'quote_close', 'code','file','hr','preformatted','rss', 'htmlblock','phpblock', 'footnote_close', ); // Stacks can contain paragraphs protected $stackOpen = array( 'section_open', ); protected $stackClose = array( 'section_close', ); /** * Constructor. Adds loaded syntax plugins to the block and stack * arrays * * @author Andreas Gohr <andi@splitbrain.org> */ public function __construct() { global $DOKU_PLUGINS; //check if syntax plugins were loaded if (empty($DOKU_PLUGINS['syntax'])) return; foreach ($DOKU_PLUGINS['syntax'] as $n => $p) { $ptype = $p->getPType(); if ($ptype == 'block') { $this->blockOpen[] = 'plugin_'.$n; $this->blockClose[] = 'plugin_'.$n; } elseif ($ptype == 'stack') { $this->stackOpen[] = 'plugin_'.$n; $this->stackClose[] = 'plugin_'.$n; } } } protected function openParagraph($pos) { if ($this->inParagraph) return; $this->calls[] = array('p_open',array(), $pos); $this->inParagraph = true; $this->skipEol = true; } /** * Close a paragraph if needed * * This function makes sure there are no empty paragraphs on the stack * * @author Andreas Gohr <andi@splitbrain.org> * * @param string|integer $pos */ protected function closeParagraph($pos) { if (!$this->inParagraph) return; // look back if there was any content - we don't want empty paragraphs $content = ''; $ccount = count($this->calls); for ($i=$ccount-1; $i>=0; $i--) { if ($this->calls[$i][0] == 'p_open') { break; } elseif ($this->calls[$i][0] == 'cdata') { $content .= $this->calls[$i][1][0]; } else { $content = 'found markup'; break; } } if (trim($content)=='') { //remove the whole paragraph //array_splice($this->calls,$i); // <- this is much slower than the loop below for ($x=$ccount; $x>$i; $x--) array_pop($this->calls); } else { // remove ending linebreaks in the paragraph $i=count($this->calls)-1; if ($this->calls[$i][0] == 'cdata') $this->calls[$i][1][0] = rtrim($this->calls[$i][1][0], "\n"); $this->calls[] = array('p_close',array(), $pos); } $this->inParagraph = false; $this->skipEol = true; } protected function addCall($call) { $key = count($this->calls); if ($key and ($call[0] == 'cdata') and ($this->calls[$key-1][0] == 'cdata')) { $this->calls[$key-1][1][0] .= $call[1][0]; } else { $this->calls[] = $call; } } // simple version of addCall, without checking cdata protected function storeCall($call) { $this->calls[] = $call; } /** * Processes the whole instruction stack to open and close paragraphs * * @author Harry Fuecks <hfuecks@gmail.com> * @author Andreas Gohr <andi@splitbrain.org> * * @param array $calls * * @return array */ public function process($calls) { // open first paragraph $this->openParagraph(0); foreach ($calls as $key => $call) { $cname = $call[0]; if ($cname == 'plugin') { $cname='plugin_'.$call[1][0]; $plugin = true; $plugin_open = (($call[1][2] == DOKU_LEXER_ENTER) || ($call[1][2] == DOKU_LEXER_SPECIAL)); $plugin_close = (($call[1][2] == DOKU_LEXER_EXIT) || ($call[1][2] == DOKU_LEXER_SPECIAL)); } else { $plugin = false; } /* stack */ if (in_array($cname, $this->stackClose) && (!$plugin || $plugin_close)) { $this->closeParagraph($call[2]); $this->storeCall($call); $this->openParagraph($call[2]); continue; } if (in_array($cname, $this->stackOpen) && (!$plugin || $plugin_open)) { $this->closeParagraph($call[2]); $this->storeCall($call); $this->openParagraph($call[2]); continue; } /* block */ // If it's a substition it opens and closes at the same call. // To make sure next paragraph is correctly started, let close go first. if (in_array($cname, $this->blockClose) && (!$plugin || $plugin_close)) { $this->closeParagraph($call[2]); $this->storeCall($call); $this->openParagraph($call[2]); continue; } if (in_array($cname, $this->blockOpen) && (!$plugin || $plugin_open)) { $this->closeParagraph($call[2]); $this->storeCall($call); continue; } /* eol */ if ($cname == 'eol') { // Check this isn't an eol instruction to skip... if (!$this->skipEol) { // Next is EOL => double eol => mark as paragraph if (isset($calls[$key+1]) && $calls[$key+1][0] == 'eol') { $this->closeParagraph($call[2]); $this->openParagraph($call[2]); } else { //if this is just a single eol make a space from it $this->addCall(array('cdata',array("\n"), $call[2])); } } continue; } /* normal */ $this->addCall($call); $this->skipEol = false; } // close last paragraph $call = end($this->calls); $this->closeParagraph($call[2]); return $this->calls; } } Parsing/Handler/Nest.php 0000644 00000004375 15233462216 0011163 0 ustar 00 <?php namespace dokuwiki\Parsing\Handler; /** * Generic call writer class to handle nesting of rendering instructions * within a render instruction. Also see nest() method of renderer base class * * @author Chris Smith <chris@jalakai.co.uk> */ class Nest extends AbstractRewriter { protected $closingInstruction; /** * @inheritdoc * * @param CallWriterInterface $CallWriter the parser's current call writer, i.e. the one above us in the chain * @param string $close closing instruction name, this is required to properly terminate the * syntax mode if the document ends without a closing pattern */ public function __construct(CallWriterInterface $CallWriter, $close = "nest_close") { parent::__construct($CallWriter); $this->closingInstruction = $close; } /** @inheritdoc */ public function writeCall($call) { $this->calls[] = $call; } /** @inheritdoc */ public function writeCalls($calls) { $this->calls = array_merge($this->calls, $calls); } /** @inheritdoc */ public function finalise() { $last_call = end($this->calls); $this->writeCall(array($this->closingInstruction,array(), $last_call[2])); $this->process(); $this->callWriter->finalise(); unset($this->callWriter); } /** @inheritdoc */ public function process() { // merge consecutive cdata $unmerged_calls = $this->calls; $this->calls = array(); foreach ($unmerged_calls as $call) $this->addCall($call); $first_call = reset($this->calls); $this->callWriter->writeCall(array("nest", array($this->calls), $first_call[2])); return $this->callWriter; } /** * @param array $call */ protected function addCall($call) { $key = count($this->calls); if ($key and ($call[0] == 'cdata') and ($this->calls[$key-1][0] == 'cdata')) { $this->calls[$key-1][1][0] .= $call[1][0]; } elseif ($call[0] == 'eol') { // do nothing (eol shouldn't be allowed, to counter preformatted fix in #1652 & #1699) } else { $this->calls[] = $call; } } } Parsing/Handler/AbstractRewriter.php 0000644 00000001504 15233462216 0013530 0 ustar 00 <?php namespace dokuwiki\Parsing\Handler; /** * Basic implementation of the rewriter interface to be specialized by children */ abstract class AbstractRewriter implements ReWriterInterface { /** @var CallWriterInterface original CallWriter */ protected $callWriter; /** @var array[] list of calls */ public $calls = array(); /** @inheritdoc */ public function __construct(CallWriterInterface $callWriter) { $this->callWriter = $callWriter; } /** @inheritdoc */ public function writeCall($call) { $this->calls[] = $call; } /** * @inheritdoc */ public function writeCalls($calls) { $this->calls = array_merge($this->calls, $calls); } /** @inheritDoc */ public function getCallWriter() { return $this->callWriter; } } Parsing/Handler/Lists.php 0000644 00000015701 15233462216 0011343 0 ustar 00 <?php namespace dokuwiki\Parsing\Handler; class Lists extends AbstractRewriter { protected $listCalls = array(); protected $listStack = array(); protected $initialDepth = 0; const NODE = 1; /** @inheritdoc */ public function finalise() { $last_call = end($this->calls); $this->writeCall(array('list_close',array(), $last_call[2])); $this->process(); $this->callWriter->finalise(); unset($this->callWriter); } /** @inheritdoc */ public function process() { foreach ($this->calls as $call) { switch ($call[0]) { case 'list_item': $this->listOpen($call); break; case 'list_open': $this->listStart($call); break; case 'list_close': $this->listEnd($call); break; default: $this->listContent($call); break; } } $this->callWriter->writeCalls($this->listCalls); return $this->callWriter; } protected function listStart($call) { $depth = $this->interpretSyntax($call[1][0], $listType); $this->initialDepth = $depth; // array(list type, current depth, index of current listitem_open) $this->listStack[] = array($listType, $depth, 1); $this->listCalls[] = array('list'.$listType.'_open',array(),$call[2]); $this->listCalls[] = array('listitem_open',array(1),$call[2]); $this->listCalls[] = array('listcontent_open',array(),$call[2]); } protected function listEnd($call) { $closeContent = true; while ($list = array_pop($this->listStack)) { if ($closeContent) { $this->listCalls[] = array('listcontent_close',array(),$call[2]); $closeContent = false; } $this->listCalls[] = array('listitem_close',array(),$call[2]); $this->listCalls[] = array('list'.$list[0].'_close', array(), $call[2]); } } protected function listOpen($call) { $depth = $this->interpretSyntax($call[1][0], $listType); $end = end($this->listStack); $key = key($this->listStack); // Not allowed to be shallower than initialDepth if ($depth < $this->initialDepth) { $depth = $this->initialDepth; } if ($depth == $end[1]) { // Just another item in the list... if ($listType == $end[0]) { $this->listCalls[] = array('listcontent_close',array(),$call[2]); $this->listCalls[] = array('listitem_close',array(),$call[2]); $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]); $this->listCalls[] = array('listcontent_open',array(),$call[2]); // new list item, update list stack's index into current listitem_open $this->listStack[$key][2] = count($this->listCalls) - 2; // Switched list type... } else { $this->listCalls[] = array('listcontent_close',array(),$call[2]); $this->listCalls[] = array('listitem_close',array(),$call[2]); $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]); $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); $this->listCalls[] = array('listcontent_open',array(),$call[2]); array_pop($this->listStack); $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2); } } elseif ($depth > $end[1]) { // Getting deeper... $this->listCalls[] = array('listcontent_close',array(),$call[2]); $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); $this->listCalls[] = array('listcontent_open',array(),$call[2]); // set the node/leaf state of this item's parent listitem_open to NODE $this->listCalls[$this->listStack[$key][2]][1][1] = self::NODE; $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2); } else { // Getting shallower ( $depth < $end[1] ) $this->listCalls[] = array('listcontent_close',array(),$call[2]); $this->listCalls[] = array('listitem_close',array(),$call[2]); $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]); // Throw away the end - done array_pop($this->listStack); while (1) { $end = end($this->listStack); $key = key($this->listStack); if ($end[1] <= $depth) { // Normalize depths $depth = $end[1]; $this->listCalls[] = array('listitem_close',array(),$call[2]); if ($end[0] == $listType) { $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]); $this->listCalls[] = array('listcontent_open',array(),$call[2]); // new list item, update list stack's index into current listitem_open $this->listStack[$key][2] = count($this->listCalls) - 2; } else { // Switching list type... $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]); $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); $this->listCalls[] = array('listcontent_open',array(),$call[2]); array_pop($this->listStack); $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2); } break; // Haven't dropped down far enough yet.... ( $end[1] > $depth ) } else { $this->listCalls[] = array('listitem_close',array(),$call[2]); $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]); array_pop($this->listStack); } } } } protected function listContent($call) { $this->listCalls[] = $call; } protected function interpretSyntax($match, & $type) { if (substr($match, -1) == '*') { $type = 'u'; } else { $type = 'o'; } // Is the +1 needed? It used to be count(explode(...)) // but I don't think the number is seen outside this handler return substr_count(str_replace("\t", ' ', $match), ' ') + 1; } } Parsing/Handler/Table.php 0000644 00000027321 15233462216 0011275 0 ustar 00 <?php namespace dokuwiki\Parsing\Handler; class Table extends AbstractRewriter { protected $tableCalls = array(); protected $maxCols = 0; protected $maxRows = 1; protected $currentCols = 0; protected $firstCell = false; protected $lastCellType = 'tablecell'; protected $inTableHead = true; protected $currentRow = array('tableheader' => 0, 'tablecell' => 0); protected $countTableHeadRows = 0; /** @inheritdoc */ public function finalise() { $last_call = end($this->calls); $this->writeCall(array('table_end',array(), $last_call[2])); $this->process(); $this->callWriter->finalise(); unset($this->callWriter); } /** @inheritdoc */ public function process() { foreach ($this->calls as $call) { switch ($call[0]) { case 'table_start': $this->tableStart($call); break; case 'table_row': $this->tableRowClose($call); $this->tableRowOpen(array('tablerow_open',$call[1],$call[2])); break; case 'tableheader': case 'tablecell': $this->tableCell($call); break; case 'table_end': $this->tableRowClose($call); $this->tableEnd($call); break; default: $this->tableDefault($call); break; } } $this->callWriter->writeCalls($this->tableCalls); return $this->callWriter; } protected function tableStart($call) { $this->tableCalls[] = array('table_open',$call[1],$call[2]); $this->tableCalls[] = array('tablerow_open',array(),$call[2]); $this->firstCell = true; } protected function tableEnd($call) { $this->tableCalls[] = array('table_close',$call[1],$call[2]); $this->finalizeTable(); } protected function tableRowOpen($call) { $this->tableCalls[] = $call; $this->currentCols = 0; $this->firstCell = true; $this->lastCellType = 'tablecell'; $this->maxRows++; if ($this->inTableHead) { $this->currentRow = array('tablecell' => 0, 'tableheader' => 0); } } protected function tableRowClose($call) { if ($this->inTableHead && ($this->inTableHead = $this->isTableHeadRow())) { $this->countTableHeadRows++; } // Strip off final cell opening and anything after it while ($discard = array_pop($this->tableCalls)) { if ($discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') { break; } if (!empty($this->currentRow[$discard[0]])) { $this->currentRow[$discard[0]]--; } } $this->tableCalls[] = array('tablerow_close', array(), $call[2]); if ($this->currentCols > $this->maxCols) { $this->maxCols = $this->currentCols; } } protected function isTableHeadRow() { $td = $this->currentRow['tablecell']; $th = $this->currentRow['tableheader']; if (!$th || $td > 2) return false; if (2*$td > $th) return false; return true; } protected function tableCell($call) { if ($this->inTableHead) { $this->currentRow[$call[0]]++; } if (!$this->firstCell) { // Increase the span $lastCall = end($this->tableCalls); // A cell call which follows an open cell means an empty cell so span if ($lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open') { $this->tableCalls[] = array('colspan',array(),$call[2]); } $this->tableCalls[] = array($this->lastCellType.'_close',array(),$call[2]); $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]); $this->lastCellType = $call[0]; } else { $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]); $this->lastCellType = $call[0]; $this->firstCell = false; } $this->currentCols++; } protected function tableDefault($call) { $this->tableCalls[] = $call; } protected function finalizeTable() { // Add the max cols and rows to the table opening if ($this->tableCalls[0][0] == 'table_open') { // Adjust to num cols not num col delimeters $this->tableCalls[0][1][] = $this->maxCols - 1; $this->tableCalls[0][1][] = $this->maxRows; $this->tableCalls[0][1][] = array_shift($this->tableCalls[0][1]); } else { trigger_error('First element in table call list is not table_open'); } $lastRow = 0; $lastCell = 0; $cellKey = array(); $toDelete = array(); // if still in tableheader, then there can be no table header // as all rows can't be within <THEAD> if ($this->inTableHead) { $this->inTableHead = false; $this->countTableHeadRows = 0; } // Look for the colspan elements and increment the colspan on the // previous non-empty opening cell. Once done, delete all the cells // that contain colspans for ($key = 0; $key < count($this->tableCalls); ++$key) { $call = $this->tableCalls[$key]; switch ($call[0]) { case 'table_open': if ($this->countTableHeadRows) { array_splice($this->tableCalls, $key+1, 0, array( array('tablethead_open', array(), $call[2]))); } break; case 'tablerow_open': $lastRow++; $lastCell = 0; break; case 'tablecell_open': case 'tableheader_open': $lastCell++; $cellKey[$lastRow][$lastCell] = $key; break; case 'table_align': $prev = in_array($this->tableCalls[$key-1][0], array('tablecell_open', 'tableheader_open')); $next = in_array($this->tableCalls[$key+1][0], array('tablecell_close', 'tableheader_close')); // If the cell is empty, align left if ($prev && $next) { $this->tableCalls[$key-1][1][1] = 'left'; // If the previous element was a cell open, align right } elseif ($prev) { $this->tableCalls[$key-1][1][1] = 'right'; // If the next element is the close of an element, align either center or left } elseif ($next) { if ($this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] == 'right') { $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'center'; } else { $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'left'; } } // Now convert the whitespace back to cdata $this->tableCalls[$key][0] = 'cdata'; break; case 'colspan': $this->tableCalls[$key-1][1][0] = false; for ($i = $key-2; $i >= $cellKey[$lastRow][1]; $i--) { if ($this->tableCalls[$i][0] == 'tablecell_open' || $this->tableCalls[$i][0] == 'tableheader_open' ) { if (false !== $this->tableCalls[$i][1][0]) { $this->tableCalls[$i][1][0]++; break; } } } $toDelete[] = $key-1; $toDelete[] = $key; $toDelete[] = $key+1; break; case 'rowspan': if ($this->tableCalls[$key-1][0] == 'cdata') { // ignore rowspan if previous call was cdata (text mixed with :::) // we don't have to check next call as that wont match regex $this->tableCalls[$key][0] = 'cdata'; } else { $spanning_cell = null; // can't cross thead/tbody boundary if (!$this->countTableHeadRows || ($lastRow-1 != $this->countTableHeadRows)) { for ($i = $lastRow-1; $i > 0; $i--) { if ($this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tablecell_open' || $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tableheader_open' ) { if ($this->tableCalls[$cellKey[$i][$lastCell]][1][2] >= $lastRow - $i) { $spanning_cell = $i; break; } } } } if (is_null($spanning_cell)) { // No spanning cell found, so convert this cell to // an empty one to avoid broken tables $this->tableCalls[$key][0] = 'cdata'; $this->tableCalls[$key][1][0] = ''; break; } $this->tableCalls[$cellKey[$spanning_cell][$lastCell]][1][2]++; $this->tableCalls[$key-1][1][2] = false; $toDelete[] = $key-1; $toDelete[] = $key; $toDelete[] = $key+1; } break; case 'tablerow_close': // Fix broken tables by adding missing cells $moreCalls = array(); while (++$lastCell < $this->maxCols) { $moreCalls[] = array('tablecell_open', array(1, null, 1), $call[2]); $moreCalls[] = array('cdata', array(''), $call[2]); $moreCalls[] = array('tablecell_close', array(), $call[2]); } $moreCallsLength = count($moreCalls); if ($moreCallsLength) { array_splice($this->tableCalls, $key, 0, $moreCalls); $key += $moreCallsLength; } if ($this->countTableHeadRows == $lastRow) { array_splice($this->tableCalls, $key+1, 0, array( array('tablethead_close', array(), $call[2]))); } break; } } // condense cdata $cnt = count($this->tableCalls); for ($key = 0; $key < $cnt; $key++) { if ($this->tableCalls[$key][0] == 'cdata') { $ckey = $key; $key++; while ($this->tableCalls[$key][0] == 'cdata') { $this->tableCalls[$ckey][1][0] .= $this->tableCalls[$key][1][0]; $toDelete[] = $key; $key++; } continue; } } foreach ($toDelete as $delete) { unset($this->tableCalls[$delete]); } $this->tableCalls = array_values($this->tableCalls); } } Parsing/Handler/CallWriter.php 0000644 00000001463 15233462216 0012315 0 ustar 00 <?php namespace dokuwiki\Parsing\Handler; class CallWriter implements CallWriterInterface { /** @var \Doku_Handler $Handler */ protected $Handler; /** * @param \Doku_Handler $Handler */ public function __construct(\Doku_Handler $Handler) { $this->Handler = $Handler; } /** @inheritdoc */ public function writeCall($call) { $this->Handler->calls[] = $call; } /** @inheritdoc */ public function writeCalls($calls) { $this->Handler->calls = array_merge($this->Handler->calls, $calls); } /** * @inheritdoc * function is required, but since this call writer is first/highest in * the chain it is not required to do anything */ public function finalise() { unset($this->Handler); } } Parsing/Handler/Preformatted.php 0000644 00000002731 15233462216 0012700 0 ustar 00 <?php namespace dokuwiki\Parsing\Handler; class Preformatted extends AbstractRewriter { protected $pos; protected $text =''; /** @inheritdoc */ public function finalise() { $last_call = end($this->calls); $this->writeCall(array('preformatted_end',array(), $last_call[2])); $this->process(); $this->callWriter->finalise(); unset($this->callWriter); } /** @inheritdoc */ public function process() { foreach ($this->calls as $call) { switch ($call[0]) { case 'preformatted_start': $this->pos = $call[2]; break; case 'preformatted_newline': $this->text .= "\n"; break; case 'preformatted_content': $this->text .= $call[1][0]; break; case 'preformatted_end': if (trim($this->text)) { $this->callWriter->writeCall(array('preformatted', array($this->text), $this->pos)); } // see FS#1699 & FS#1652, add 'eol' instructions to ensure proper triggering of following p_open $this->callWriter->writeCall(array('eol', array(), $this->pos)); $this->callWriter->writeCall(array('eol', array(), $this->pos)); break; } } return $this->callWriter; } } Parsing/ParserMode/Externallink.php 0000644 00000002211 15233462216 0013361 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Externallink extends AbstractMode { protected $schemes = array(); protected $patterns = array(); /** @inheritdoc */ public function preConnect() { if (count($this->patterns)) return; $ltrs = '\w'; $gunk = '/\#~:.?+=&%@!\-\[\]'; $punc = '.:?\-;,'; $host = $ltrs.$punc; $any = $ltrs.$gunk.$punc; $this->schemes = getSchemes(); foreach ($this->schemes as $scheme) { $this->patterns[] = '\b(?i)'.$scheme.'(?-i)://['.$any.']+?(?=['.$punc.']*[^'.$any.'])'; } $this->patterns[] = '(?<=\s)(?i)www?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])'; $this->patterns[] = '(?<=\s)(?i)ftp?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])'; } /** @inheritdoc */ public function connectTo($mode) { foreach ($this->patterns as $pattern) { $this->Lexer->addSpecialPattern($pattern, $mode, 'externallink'); } } /** @inheritdoc */ public function getSort() { return 330; } } Parsing/ParserMode/Footnote.php 0000644 00000001753 15233462216 0012530 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Footnote extends AbstractMode { /** * Footnote constructor. */ public function __construct() { global $PARSER_MODES; $this->allowedModes = array_merge( $PARSER_MODES['container'], $PARSER_MODES['formatting'], $PARSER_MODES['substition'], $PARSER_MODES['protected'], $PARSER_MODES['disabled'] ); unset($this->allowedModes[array_search('footnote', $this->allowedModes)]); } /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addEntryPattern( '\x28\x28(?=.*\x29\x29)', $mode, 'footnote' ); } /** @inheritdoc */ public function postConnect() { $this->Lexer->addExitPattern( '\x29\x29', 'footnote' ); } /** @inheritdoc */ public function getSort() { return 150; } } Parsing/ParserMode/Quote.php 0000644 00000001464 15233462216 0012027 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Quote extends AbstractMode { /** * Quote constructor. */ public function __construct() { global $PARSER_MODES; $this->allowedModes = array_merge( $PARSER_MODES['formatting'], $PARSER_MODES['substition'], $PARSER_MODES['disabled'], $PARSER_MODES['protected'] ); } /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addEntryPattern('\n>{1,}', $mode, 'quote'); } /** @inheritdoc */ public function postConnect() { $this->Lexer->addPattern('\n>{1,}', 'quote'); $this->Lexer->addExitPattern('\n', 'quote'); } /** @inheritdoc */ public function getSort() { return 220; } } Parsing/ParserMode/Camelcaselink.php 0000644 00000000614 15233462216 0013461 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Camelcaselink extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addSpecialPattern( '\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b', $mode, 'camelcaselink' ); } /** @inheritdoc */ public function getSort() { return 290; } } Parsing/ParserMode/Smiley.php 0000644 00000001752 15233462216 0012174 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; use dokuwiki\Parsing\Lexer\Lexer; class Smiley extends AbstractMode { protected $smileys = array(); protected $pattern = ''; /** * Smiley constructor. * @param string[] $smileys */ public function __construct($smileys) { $this->smileys = $smileys; } /** @inheritdoc */ public function preConnect() { if (!count($this->smileys) || $this->pattern != '') return; $sep = ''; foreach ($this->smileys as $smiley) { $this->pattern .= $sep.'(?<=\W|^)'. Lexer::escape($smiley).'(?=\W|$)'; $sep = '|'; } } /** @inheritdoc */ public function connectTo($mode) { if (!count($this->smileys)) return; if (strlen($this->pattern) > 0) { $this->Lexer->addSpecialPattern($this->pattern, $mode, 'smiley'); } } /** @inheritdoc */ public function getSort() { return 230; } } Parsing/ParserMode/Base.php 0000644 00000001154 15233462216 0011600 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Base extends AbstractMode { /** * Base constructor. */ public function __construct() { global $PARSER_MODES; $this->allowedModes = array_merge( $PARSER_MODES['container'], $PARSER_MODES['baseonly'], $PARSER_MODES['paragraphs'], $PARSER_MODES['formatting'], $PARSER_MODES['substition'], $PARSER_MODES['protected'], $PARSER_MODES['disabled'] ); } /** @inheritdoc */ public function getSort() { return 0; } } Parsing/ParserMode/Nocache.php 0000644 00000000476 15233462216 0012274 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Nocache extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addSpecialPattern('~~NOCACHE~~', $mode, 'nocache'); } /** @inheritdoc */ public function getSort() { return 40; } } Parsing/ParserMode/AbstractMode.php 0000644 00000001535 15233462216 0013301 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; /** * This class and all the subclasses below are used to reduce the effort required to register * modes with the Lexer. * * @author Harry Fuecks <hfuecks@gmail.com> */ abstract class AbstractMode implements ModeInterface { /** @var \dokuwiki\Parsing\Lexer\Lexer $Lexer will be injected on loading FIXME this should be done by setter */ public $Lexer; protected $allowedModes = array(); /** @inheritdoc */ abstract public function getSort(); /** @inheritdoc */ public function preConnect() { } /** @inheritdoc */ public function connectTo($mode) { } /** @inheritdoc */ public function postConnect() { } /** @inheritdoc */ public function accepts($mode) { return in_array($mode, (array) $this->allowedModes); } } Parsing/ParserMode/Media.php 0000644 00000000552 15233462216 0011746 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Media extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { // Word boundaries? $this->Lexer->addSpecialPattern("\{\{(?:[^\}]|(?:\}[^\}]))+\}\}", $mode, 'media'); } /** @inheritdoc */ public function getSort() { return 320; } } Parsing/ParserMode/Code.php 0000644 00000000677 15233462216 0011611 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Code extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addEntryPattern('<code\b(?=.*</code>)', $mode, 'code'); } /** @inheritdoc */ public function postConnect() { $this->Lexer->addExitPattern('</code>', 'code'); } /** @inheritdoc */ public function getSort() { return 200; } } Parsing/ParserMode/Plugin.php 0000644 00000000307 15233462216 0012163 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; /** * @fixme do we need this anymore or could the syntax plugin inherit directly from abstract mode? */ abstract class Plugin extends AbstractMode {} Parsing/ParserMode/Notoc.php 0000644 00000000470 15233462216 0012010 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Notoc extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addSpecialPattern('~~NOTOC~~', $mode, 'notoc'); } /** @inheritdoc */ public function getSort() { return 30; } } Parsing/ParserMode/Emaillink.php 0000644 00000000612 15233462216 0012631 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Emaillink extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { // pattern below is defined in inc/mail.php $this->Lexer->addSpecialPattern('<'.PREG_PATTERN_VALID_EMAIL.'>', $mode, 'emaillink'); } /** @inheritdoc */ public function getSort() { return 340; } } Parsing/ParserMode/Eol.php 0000644 00000001076 15233462216 0011450 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Eol extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $badModes = array('listblock','table'); if (in_array($mode, $badModes)) { return; } // see FS#1652, pattern extended to swallow preceding whitespace to avoid // issues with lines that only contain whitespace $this->Lexer->addSpecialPattern('(?:^[ \t]*)?\n', $mode, 'eol'); } /** @inheritdoc */ public function getSort() { return 370; } } Parsing/ParserMode/Header.php 0000644 00000000702 15233462216 0012114 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Header extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { //we're not picky about the closing ones, two are enough $this->Lexer->addSpecialPattern( '[ \t]*={2,}[^\n]+={2,}[ \t]*(?=\n)', $mode, 'header' ); } /** @inheritdoc */ public function getSort() { return 50; } } Parsing/ParserMode/Formatting.php 0000644 00000005053 15233462216 0013042 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; /** * This class sets the markup for bold (=strong), * italic (=emphasis), underline etc. */ class Formatting extends AbstractMode { protected $type; protected $formatting = array( 'strong' => array( 'entry' => '\*\*(?=.*\*\*)', 'exit' => '\*\*', 'sort' => 70 ), 'emphasis' => array( 'entry' => '//(?=[^\x00]*[^:])', //hack for bugs #384 #763 #1468 'exit' => '//', 'sort' => 80 ), 'underline' => array( 'entry' => '__(?=.*__)', 'exit' => '__', 'sort' => 90 ), 'monospace' => array( 'entry' => '\x27\x27(?=.*\x27\x27)', 'exit' => '\x27\x27', 'sort' => 100 ), 'subscript' => array( 'entry' => '<sub>(?=.*</sub>)', 'exit' => '</sub>', 'sort' => 110 ), 'superscript' => array( 'entry' => '<sup>(?=.*</sup>)', 'exit' => '</sup>', 'sort' => 120 ), 'deleted' => array( 'entry' => '<del>(?=.*</del>)', 'exit' => '</del>', 'sort' => 130 ), ); /** * @param string $type */ public function __construct($type) { global $PARSER_MODES; if (!array_key_exists($type, $this->formatting)) { trigger_error('Invalid formatting type ' . $type, E_USER_WARNING); } $this->type = $type; // formatting may contain other formatting but not it self $modes = $PARSER_MODES['formatting']; $key = array_search($type, $modes); if (is_int($key)) { unset($modes[$key]); } $this->allowedModes = array_merge( $modes, $PARSER_MODES['substition'], $PARSER_MODES['disabled'] ); } /** @inheritdoc */ public function connectTo($mode) { // Can't nest formatting in itself if ($mode == $this->type) { return; } $this->Lexer->addEntryPattern( $this->formatting[$this->type]['entry'], $mode, $this->type ); } /** @inheritdoc */ public function postConnect() { $this->Lexer->addExitPattern( $this->formatting[$this->type]['exit'], $this->type ); } /** @inheritdoc */ public function getSort() { return $this->formatting[$this->type]['sort']; } } Parsing/ParserMode/File.php 0000644 00000000677 15233462216 0011616 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class File extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addEntryPattern('<file\b(?=.*</file>)', $mode, 'file'); } /** @inheritdoc */ public function postConnect() { $this->Lexer->addExitPattern('</file>', 'file'); } /** @inheritdoc */ public function getSort() { return 210; } } Parsing/ParserMode/Acronym.php 0000644 00000002725 15233462216 0012343 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Acronym extends AbstractMode { // A list protected $acronyms = array(); protected $pattern = ''; /** * Acronym constructor. * * @param string[] $acronyms */ public function __construct($acronyms) { usort($acronyms, array($this,'compare')); $this->acronyms = $acronyms; } /** @inheritdoc */ public function preConnect() { if (!count($this->acronyms)) return; $bound = '[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]'; $acronyms = array_map(['\\dokuwiki\\Parsing\\Lexer\\Lexer', 'escape'], $this->acronyms); $this->pattern = '(?<=^|'.$bound.')(?:'.join('|', $acronyms).')(?='.$bound.')'; } /** @inheritdoc */ public function connectTo($mode) { if (!count($this->acronyms)) return; if (strlen($this->pattern) > 0) { $this->Lexer->addSpecialPattern($this->pattern, $mode, 'acronym'); } } /** @inheritdoc */ public function getSort() { return 240; } /** * sort callback to order by string length descending * * @param string $a * @param string $b * * @return int */ protected function compare($a, $b) { $a_len = strlen($a); $b_len = strlen($b); if ($a_len > $b_len) { return -1; } elseif ($a_len < $b_len) { return 1; } return 0; } } Parsing/ParserMode/ModeInterface.php 0000644 00000001466 15233462216 0013441 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; /** * Defines a mode (syntax component) in the Parser */ interface ModeInterface { /** * returns a number used to determine in which order modes are added * * @return int; */ public function getSort(); /** * Called before any calls to connectTo * * @return void */ public function preConnect(); /** * Connects the mode * * @param string $mode * @return void */ public function connectTo($mode); /** * Called after all calls to connectTo * * @return void */ public function postConnect(); /** * Check if given mode is accepted inside this mode * * @param string $mode * @return bool */ public function accepts($mode); } Parsing/ParserMode/Unformatted.php 0000644 00000001250 15233462216 0013213 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Unformatted extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addEntryPattern('<nowiki>(?=.*</nowiki>)', $mode, 'unformatted'); $this->Lexer->addEntryPattern('%%(?=.*%%)', $mode, 'unformattedalt'); } /** @inheritdoc */ public function postConnect() { $this->Lexer->addExitPattern('</nowiki>', 'unformatted'); $this->Lexer->addExitPattern('%%', 'unformattedalt'); $this->Lexer->mapHandler('unformattedalt', 'unformatted'); } /** @inheritdoc */ public function getSort() { return 170; } } Parsing/ParserMode/Internallink.php 0000644 00000000553 15233462216 0013362 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Internallink extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { // Word boundaries? $this->Lexer->addSpecialPattern("\[\[.*?\]\](?!\])", $mode, 'internallink'); } /** @inheritdoc */ public function getSort() { return 300; } } Parsing/ParserMode/Table.php 0000644 00000002212 15233462216 0011751 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Table extends AbstractMode { /** * Table constructor. */ public function __construct() { global $PARSER_MODES; $this->allowedModes = array_merge( $PARSER_MODES['formatting'], $PARSER_MODES['substition'], $PARSER_MODES['disabled'], $PARSER_MODES['protected'] ); } /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addEntryPattern('[\t ]*\n\^', $mode, 'table'); $this->Lexer->addEntryPattern('[\t ]*\n\|', $mode, 'table'); } /** @inheritdoc */ public function postConnect() { $this->Lexer->addPattern('\n\^', 'table'); $this->Lexer->addPattern('\n\|', 'table'); $this->Lexer->addPattern('[\t ]*:::[\t ]*(?=[\|\^])', 'table'); $this->Lexer->addPattern('[\t ]+', 'table'); $this->Lexer->addPattern('\^', 'table'); $this->Lexer->addPattern('\|', 'table'); $this->Lexer->addExitPattern('\n', 'table'); } /** @inheritdoc */ public function getSort() { return 60; } } Parsing/ParserMode/Php.php 0000644 00000001103 15233462216 0011447 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Php extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addEntryPattern('<php>(?=.*</php>)', $mode, 'php'); $this->Lexer->addEntryPattern('<PHP>(?=.*</PHP>)', $mode, 'phpblock'); } /** @inheritdoc */ public function postConnect() { $this->Lexer->addExitPattern('</php>', 'php'); $this->Lexer->addExitPattern('</PHP>', 'phpblock'); } /** @inheritdoc */ public function getSort() { return 180; } } Parsing/ParserMode/Windowssharelink.php 0000644 00000001030 15233462216 0014252 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Windowssharelink extends AbstractMode { protected $pattern; /** @inheritdoc */ public function preConnect() { $this->pattern = "\\\\\\\\\w+?(?:\\\\[\w\-$]+)+"; } /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addSpecialPattern( $this->pattern, $mode, 'windowssharelink' ); } /** @inheritdoc */ public function getSort() { return 350; } } Parsing/ParserMode/Linebreak.php 0000644 00000000517 15233462216 0012624 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Linebreak extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addSpecialPattern('\x5C{2}(?:[ \t]|(?=\n))', $mode, 'linebreak'); } /** @inheritdoc */ public function getSort() { return 140; } } Parsing/ParserMode/Listblock.php 0000644 00000001756 15233462216 0012664 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Listblock extends AbstractMode { /** * Listblock constructor. */ public function __construct() { global $PARSER_MODES; $this->allowedModes = array_merge( $PARSER_MODES['formatting'], $PARSER_MODES['substition'], $PARSER_MODES['disabled'], $PARSER_MODES['protected'] ); } /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addEntryPattern('[ \t]*\n {2,}[\-\*]', $mode, 'listblock'); $this->Lexer->addEntryPattern('[ \t]*\n\t{1,}[\-\*]', $mode, 'listblock'); $this->Lexer->addPattern('\n {2,}[\-\*]', 'listblock'); $this->Lexer->addPattern('\n\t{1,}[\-\*]', 'listblock'); } /** @inheritdoc */ public function postConnect() { $this->Lexer->addExitPattern('\n', 'listblock'); } /** @inheritdoc */ public function getSort() { return 10; } } Parsing/ParserMode/Filelink.php 0000644 00000001303 15233462216 0012457 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Filelink extends AbstractMode { protected $pattern; /** @inheritdoc */ public function preConnect() { $ltrs = '\w'; $gunk = '/\#~:.?+=&%@!\-'; $punc = '.:?\-;,'; $host = $ltrs.$punc; $any = $ltrs.$gunk.$punc; $this->pattern = '\b(?i)file(?-i)://['.$any.']+?['. $punc.']*[^'.$any.']'; } /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addSpecialPattern( $this->pattern, $mode, 'filelink' ); } /** @inheritdoc */ public function getSort() { return 360; } } Parsing/ParserMode/Hr.php 0000644 00000000503 15233462216 0011274 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Hr extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addSpecialPattern('\n[ \t]*-{4,}[ \t]*(?=\n)', $mode, 'hr'); } /** @inheritdoc */ public function getSort() { return 160; } } Parsing/ParserMode/Rss.php 0000644 00000000476 15233462216 0011503 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Rss extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addSpecialPattern("\{\{rss>[^\}]+\}\}", $mode, 'rss'); } /** @inheritdoc */ public function getSort() { return 310; } } Parsing/ParserMode/Quotes.php 0000644 00000002261 15233462216 0012206 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Quotes extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { global $conf; $ws = '\s/\#~:+=&%@\-\x28\x29\]\[{}><"\''; // whitespace $punc = ';,\.?!'; if ($conf['typography'] == 2) { $this->Lexer->addSpecialPattern( "(?<=^|[$ws])'(?=[^$ws$punc])", $mode, 'singlequoteopening' ); $this->Lexer->addSpecialPattern( "(?<=^|[^$ws]|[$punc])'(?=$|[$ws$punc])", $mode, 'singlequoteclosing' ); $this->Lexer->addSpecialPattern( "(?<=^|[^$ws$punc])'(?=$|[^$ws$punc])", $mode, 'apostrophe' ); } $this->Lexer->addSpecialPattern( "(?<=^|[$ws])\"(?=[^$ws$punc])", $mode, 'doublequoteopening' ); $this->Lexer->addSpecialPattern( "\"", $mode, 'doublequoteclosing' ); } /** @inheritdoc */ public function getSort() { return 280; } } Parsing/ParserMode/Multiplyentity.php 0000644 00000000703 15233462216 0014001 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; /** * Implements the 640x480 replacement */ class Multiplyentity extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addSpecialPattern( '(?<=\b)(?:[1-9]|\d{2,})[xX]\d+(?=\b)', $mode, 'multiplyentity' ); } /** @inheritdoc */ public function getSort() { return 270; } } Parsing/ParserMode/Entity.php 0000644 00000001735 15233462216 0012207 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; use dokuwiki\Parsing\Lexer\Lexer; class Entity extends AbstractMode { protected $entities = array(); protected $pattern = ''; /** * Entity constructor. * @param string[] $entities */ public function __construct($entities) { $this->entities = $entities; } /** @inheritdoc */ public function preConnect() { if (!count($this->entities) || $this->pattern != '') return; $sep = ''; foreach ($this->entities as $entity) { $this->pattern .= $sep. Lexer::escape($entity); $sep = '|'; } } /** @inheritdoc */ public function connectTo($mode) { if (!count($this->entities)) return; if (strlen($this->pattern) > 0) { $this->Lexer->addSpecialPattern($this->pattern, $mode, 'entity'); } } /** @inheritdoc */ public function getSort() { return 260; } } Parsing/ParserMode/Preformatted.php 0000644 00000001367 15233462216 0013370 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Preformatted extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { // Has hard coded awareness of lists... $this->Lexer->addEntryPattern('\n (?![\*\-])', $mode, 'preformatted'); $this->Lexer->addEntryPattern('\n\t(?![\*\-])', $mode, 'preformatted'); // How to effect a sub pattern with the Lexer! $this->Lexer->addPattern('\n ', 'preformatted'); $this->Lexer->addPattern('\n\t', 'preformatted'); } /** @inheritdoc */ public function postConnect() { $this->Lexer->addExitPattern('\n', 'preformatted'); } /** @inheritdoc */ public function getSort() { return 20; } } Parsing/ParserMode/Wordblock.php 0000644 00000002021 15233462216 0012646 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; use dokuwiki\Parsing\Lexer\Lexer; /** * @fixme is this actually used? */ class Wordblock extends AbstractMode { protected $badwords = array(); protected $pattern = ''; /** * Wordblock constructor. * @param $badwords */ public function __construct($badwords) { $this->badwords = $badwords; } /** @inheritdoc */ public function preConnect() { if (count($this->badwords) == 0 || $this->pattern != '') { return; } $sep = ''; foreach ($this->badwords as $badword) { $this->pattern .= $sep.'(?<=\b)(?i)'. Lexer::escape($badword).'(?-i)(?=\b)'; $sep = '|'; } } /** @inheritdoc */ public function connectTo($mode) { if (strlen($this->pattern) > 0) { $this->Lexer->addSpecialPattern($this->pattern, $mode, 'wordblock'); } } /** @inheritdoc */ public function getSort() { return 250; } } Parsing/ParserMode/Html.php 0000644 00000001116 15233462216 0011630 0 ustar 00 <?php namespace dokuwiki\Parsing\ParserMode; class Html extends AbstractMode { /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addEntryPattern('<html>(?=.*</html>)', $mode, 'html'); $this->Lexer->addEntryPattern('<HTML>(?=.*</HTML>)', $mode, 'htmlblock'); } /** @inheritdoc */ public function postConnect() { $this->Lexer->addExitPattern('</html>', 'html'); $this->Lexer->addExitPattern('</HTML>', 'htmlblock'); } /** @inheritdoc */ public function getSort() { return 190; } } io.php 0000644 00000055057 15233462216 0005704 0 ustar 00 <?php /** * File IO functions * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ use dokuwiki\HTTP\DokuHTTPClient; use dokuwiki\Extension\Event; /** * Removes empty directories * * Sends IO_NAMESPACE_DELETED events for 'pages' and 'media' namespaces. * Event data: * $data[0] ns: The colon separated namespace path minus the trailing page name. * $data[1] ns_type: 'pages' or 'media' namespace tree. * * @param string $id - a pageid, the namespace of that id will be tried to deleted * @param string $basedir - the config name of the type to delete (datadir or mediadir usally) * @return bool - true if at least one namespace was deleted * * @author Andreas Gohr <andi@splitbrain.org> * @author Ben Coburn <btcoburn@silicodon.net> */ function io_sweepNS($id,$basedir='datadir'){ global $conf; $types = array ('datadir'=>'pages', 'mediadir'=>'media'); $ns_type = (isset($types[$basedir])?$types[$basedir]:false); $delone = false; //scan all namespaces while(($id = getNS($id)) !== false){ $dir = $conf[$basedir].'/'.utf8_encodeFN(str_replace(':','/',$id)); //try to delete dir else return if(@rmdir($dir)) { if ($ns_type!==false) { $data = array($id, $ns_type); $delone = true; // we deleted at least one dir Event::createAndTrigger('IO_NAMESPACE_DELETED', $data); } } else { return $delone; } } return $delone; } /** * Used to read in a DokuWiki page from file, and send IO_WIKIPAGE_READ events. * * Generates the action event which delegates to io_readFile(). * Action plugins are allowed to modify the page content in transit. * The file path should not be changed. * * Event data: * $data[0] The raw arguments for io_readFile as an array. * $data[1] ns: The colon separated namespace path minus the trailing page name. (false if root ns) * $data[2] page_name: The wiki page name. * $data[3] rev: The page revision, false for current wiki pages. * * @author Ben Coburn <btcoburn@silicodon.net> * * @param string $file filename * @param string $id page id * @param bool|int $rev revision timestamp * @return string */ function io_readWikiPage($file, $id, $rev=false) { if (empty($rev)) { $rev = false; } $data = array(array($file, true), getNS($id), noNS($id), $rev); return Event::createAndTrigger('IO_WIKIPAGE_READ', $data, '_io_readWikiPage_action', false); } /** * Callback adapter for io_readFile(). * * @author Ben Coburn <btcoburn@silicodon.net> * * @param array $data event data * @return string */ function _io_readWikiPage_action($data) { if (is_array($data) && is_array($data[0]) && count($data[0])===2) { return call_user_func_array('io_readFile', $data[0]); } else { return ''; //callback error } } /** * Returns content of $file as cleaned string. * * Uses gzip if extension is .gz * * If you want to use the returned value in unserialize * be sure to set $clean to false! * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $file filename * @param bool $clean * @return string|bool the file contents or false on error */ function io_readFile($file,$clean=true){ $ret = ''; if(file_exists($file)){ if(substr($file,-3) == '.gz'){ if(!DOKU_HAS_GZIP) return false; $ret = gzfile($file); if(is_array($ret)) $ret = join('', $ret); }else if(substr($file,-4) == '.bz2'){ if(!DOKU_HAS_BZIP) return false; $ret = bzfile($file); }else{ $ret = file_get_contents($file); } } if($ret === null) return false; if($ret !== false && $clean){ return cleanText($ret); }else{ return $ret; } } /** * Returns the content of a .bz2 compressed file as string * * @author marcel senf <marcel@rucksackreinigung.de> * @author Andreas Gohr <andi@splitbrain.org> * * @param string $file filename * @param bool $array return array of lines * @return string|array|bool content or false on error */ function bzfile($file, $array=false) { $bz = bzopen($file,"r"); if($bz === false) return false; if($array) $lines = array(); $str = ''; while (!feof($bz)) { //8192 seems to be the maximum buffersize? $buffer = bzread($bz,8192); if(($buffer === false) || (bzerrno($bz) !== 0)) { return false; } $str = $str . $buffer; if($array) { $pos = strpos($str, "\n"); while($pos !== false) { $lines[] = substr($str, 0, $pos+1); $str = substr($str, $pos+1); $pos = strpos($str, "\n"); } } } bzclose($bz); if($array) { if($str !== '') $lines[] = $str; return $lines; } return $str; } /** * Used to write out a DokuWiki page to file, and send IO_WIKIPAGE_WRITE events. * * This generates an action event and delegates to io_saveFile(). * Action plugins are allowed to modify the page content in transit. * The file path should not be changed. * (The append parameter is set to false.) * * Event data: * $data[0] The raw arguments for io_saveFile as an array. * $data[1] ns: The colon separated namespace path minus the trailing page name. (false if root ns) * $data[2] page_name: The wiki page name. * $data[3] rev: The page revision, false for current wiki pages. * * @author Ben Coburn <btcoburn@silicodon.net> * * @param string $file filename * @param string $content * @param string $id page id * @param int|bool $rev timestamp of revision * @return bool */ function io_writeWikiPage($file, $content, $id, $rev=false) { if (empty($rev)) { $rev = false; } if ($rev===false) { io_createNamespace($id); } // create namespaces as needed $data = array(array($file, $content, false), getNS($id), noNS($id), $rev); return Event::createAndTrigger('IO_WIKIPAGE_WRITE', $data, '_io_writeWikiPage_action', false); } /** * Callback adapter for io_saveFile(). * @author Ben Coburn <btcoburn@silicodon.net> * * @param array $data event data * @return bool */ function _io_writeWikiPage_action($data) { if (is_array($data) && is_array($data[0]) && count($data[0])===3) { $ok = call_user_func_array('io_saveFile', $data[0]); // for attic files make sure the file has the mtime of the revision if($ok && is_int($data[3]) && $data[3] > 0) { @touch($data[0][0], $data[3]); } return $ok; } else { return false; //callback error } } /** * Internal function to save contents to a file. * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $file filename path to file * @param string $content * @param bool $append * @return bool true on success, otherwise false */ function _io_saveFile($file, $content, $append) { global $conf; $mode = ($append) ? 'ab' : 'wb'; $fileexists = file_exists($file); if(substr($file,-3) == '.gz'){ if(!DOKU_HAS_GZIP) return false; $fh = @gzopen($file,$mode.'9'); if(!$fh) return false; gzwrite($fh, $content); gzclose($fh); }else if(substr($file,-4) == '.bz2'){ if(!DOKU_HAS_BZIP) return false; if($append) { $bzcontent = bzfile($file); if($bzcontent === false) return false; $content = $bzcontent.$content; } $fh = @bzopen($file,'w'); if(!$fh) return false; bzwrite($fh, $content); bzclose($fh); }else{ $fh = @fopen($file,$mode); if(!$fh) return false; fwrite($fh, $content); fclose($fh); } if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']); return true; } /** * Saves $content to $file. * * If the third parameter is set to true the given content * will be appended. * * Uses gzip if extension is .gz * and bz2 if extension is .bz2 * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $file filename path to file * @param string $content * @param bool $append * @return bool true on success, otherwise false */ function io_saveFile($file, $content, $append=false) { io_makeFileDir($file); io_lock($file); if(!_io_saveFile($file, $content, $append)) { msg("Writing $file failed",-1); io_unlock($file); return false; } io_unlock($file); return true; } /** * Replace one or more occurrences of a line in a file. * * The default, when $maxlines is 0 is to delete all matching lines then append a single line. * A regex that matches any part of the line will remove the entire line in this mode. * Captures in $newline are not available. * * Otherwise each line is matched and replaced individually, up to the first $maxlines lines * or all lines if $maxlines is -1. If $regex is true then captures can be used in $newline. * * Be sure to include the trailing newline in $oldline when replacing entire lines. * * Uses gzip if extension is .gz * and bz2 if extension is .bz2 * * @author Steven Danz <steven-danz@kc.rr.com> * @author Christopher Smith <chris@jalakai.co.uk> * @author Patrick Brown <ptbrown@whoopdedo.org> * * @param string $file filename * @param string $oldline exact linematch to remove * @param string $newline new line to insert * @param bool $regex use regexp? * @param int $maxlines number of occurrences of the line to replace * @return bool true on success */ function io_replaceInFile($file, $oldline, $newline, $regex=false, $maxlines=0) { if ((string)$oldline === '') { trigger_error('$oldline parameter cannot be empty in io_replaceInFile()', E_USER_WARNING); return false; } if (!file_exists($file)) return true; io_lock($file); // load into array if(substr($file,-3) == '.gz'){ if(!DOKU_HAS_GZIP) return false; $lines = gzfile($file); }else if(substr($file,-4) == '.bz2'){ if(!DOKU_HAS_BZIP) return false; $lines = bzfile($file, true); }else{ $lines = file($file); } // make non-regexes into regexes $pattern = $regex ? $oldline : '/^'.preg_quote($oldline,'/').'$/'; $replace = $regex ? $newline : addcslashes($newline, '\$'); // remove matching lines if ($maxlines > 0) { $count = 0; $matched = 0; foreach($lines as $i => $line) { if($count >= $maxlines) break; // $matched will be set to 0|1 depending on whether pattern is matched and line replaced $lines[$i] = preg_replace($pattern, $replace, $line, -1, $matched); if ($matched) $count++; } } else if ($maxlines == 0) { $lines = preg_grep($pattern, $lines, PREG_GREP_INVERT); if ((string)$newline !== ''){ $lines[] = $newline; } } else { $lines = preg_replace($pattern, $replace, $lines); } if(count($lines)){ if(!_io_saveFile($file, join('',$lines), false)) { msg("Removing content from $file failed",-1); io_unlock($file); return false; } }else{ @unlink($file); } io_unlock($file); return true; } /** * Delete lines that match $badline from $file. * * Be sure to include the trailing newline in $badline * * @author Patrick Brown <ptbrown@whoopdedo.org> * * @param string $file filename * @param string $badline exact linematch to remove * @param bool $regex use regexp? * @return bool true on success */ function io_deleteFromFile($file,$badline,$regex=false){ return io_replaceInFile($file,$badline,null,$regex,0); } /** * Tries to lock a file * * Locking is only done for io_savefile and uses directories * inside $conf['lockdir'] * * It waits maximal 3 seconds for the lock, after this time * the lock is assumed to be stale and the function goes on * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $file filename */ function io_lock($file){ global $conf; $lockDir = $conf['lockdir'].'/'.md5($file); @ignore_user_abort(1); $timeStart = time(); do { //waited longer than 3 seconds? -> stale lock if ((time() - $timeStart) > 3) break; $locked = @mkdir($lockDir, $conf['dmode']); if($locked){ if(!empty($conf['dperm'])) chmod($lockDir, $conf['dperm']); break; } usleep(50); } while ($locked === false); } /** * Unlocks a file * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $file filename */ function io_unlock($file){ global $conf; $lockDir = $conf['lockdir'].'/'.md5($file); @rmdir($lockDir); @ignore_user_abort(0); } /** * Create missing namespace directories and send the IO_NAMESPACE_CREATED events * in the order of directory creation. (Parent directories first.) * * Event data: * $data[0] ns: The colon separated namespace path minus the trailing page name. * $data[1] ns_type: 'pages' or 'media' namespace tree. * * @author Ben Coburn <btcoburn@silicodon.net> * * @param string $id page id * @param string $ns_type 'pages' or 'media' */ function io_createNamespace($id, $ns_type='pages') { // verify ns_type $types = array('pages'=>'wikiFN', 'media'=>'mediaFN'); if (!isset($types[$ns_type])) { trigger_error('Bad $ns_type parameter for io_createNamespace().'); return; } // make event list $missing = array(); $ns_stack = explode(':', $id); $ns = $id; $tmp = dirname( $file = call_user_func($types[$ns_type], $ns) ); while (!@is_dir($tmp) && !(file_exists($tmp) && !is_dir($tmp))) { array_pop($ns_stack); $ns = implode(':', $ns_stack); if (strlen($ns)==0) { break; } $missing[] = $ns; $tmp = dirname(call_user_func($types[$ns_type], $ns)); } // make directories io_makeFileDir($file); // send the events $missing = array_reverse($missing); // inside out foreach ($missing as $ns) { $data = array($ns, $ns_type); Event::createAndTrigger('IO_NAMESPACE_CREATED', $data); } } /** * Create the directory needed for the given file * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $file file name */ function io_makeFileDir($file){ $dir = dirname($file); if(!@is_dir($dir)){ io_mkdir_p($dir) || msg("Creating directory $dir failed",-1); } } /** * Creates a directory hierachy. * * @link http://php.net/manual/en/function.mkdir.php * @author <saint@corenova.com> * @author Andreas Gohr <andi@splitbrain.org> * * @param string $target filename * @return bool|int|string */ function io_mkdir_p($target){ global $conf; if (@is_dir($target)||empty($target)) return 1; // best case check first if (file_exists($target) && !is_dir($target)) return 0; //recursion if (io_mkdir_p(substr($target,0,strrpos($target,'/')))){ $ret = @mkdir($target,$conf['dmode']); // crawl back up & create dir tree if($ret && !empty($conf['dperm'])) chmod($target, $conf['dperm']); return $ret; } return 0; } /** * Recursively delete a directory * * @author Andreas Gohr <andi@splitbrain.org> * @param string $path * @param bool $removefiles defaults to false which will delete empty directories only * @return bool */ function io_rmdir($path, $removefiles = false) { if(!is_string($path) || $path == "") return false; if(!file_exists($path)) return true; // it's already gone or was never there, count as success if(is_dir($path) && !is_link($path)) { $dirs = array(); $files = array(); if(!$dh = @opendir($path)) return false; while(false !== ($f = readdir($dh))) { if($f == '..' || $f == '.') continue; // collect dirs and files first if(is_dir("$path/$f") && !is_link("$path/$f")) { $dirs[] = "$path/$f"; } else if($removefiles) { $files[] = "$path/$f"; } else { return false; // abort when non empty } } closedir($dh); // now traverse into directories first foreach($dirs as $dir) { if(!io_rmdir($dir, $removefiles)) return false; // abort on any error } // now delete files foreach($files as $file) { if(!@unlink($file)) return false; //abort on any error } // remove self return @rmdir($path); } else if($removefiles) { return @unlink($path); } return false; } /** * Creates a unique temporary directory and returns * its path. * * @author Michael Klier <chi@chimeric.de> * * @return false|string path to new directory or false */ function io_mktmpdir() { global $conf; $base = $conf['tmpdir']; $dir = md5(uniqid(mt_rand(), true)); $tmpdir = $base.'/'.$dir; if(io_mkdir_p($tmpdir)) { return($tmpdir); } else { return false; } } /** * downloads a file from the net and saves it * * if $useAttachment is false, * - $file is the full filename to save the file, incl. path * - if successful will return true, false otherwise * * if $useAttachment is true, * - $file is the directory where the file should be saved * - if successful will return the name used for the saved file, false otherwise * * @author Andreas Gohr <andi@splitbrain.org> * @author Chris Smith <chris@jalakai.co.uk> * * @param string $url url to download * @param string $file path to file or directory where to save * @param bool $useAttachment true: try to use name of download, uses otherwise $defaultName * false: uses $file as path to file * @param string $defaultName fallback for if using $useAttachment * @param int $maxSize maximum file size * @return bool|string if failed false, otherwise true or the name of the file in the given dir */ function io_download($url,$file,$useAttachment=false,$defaultName='',$maxSize=2097152){ global $conf; $http = new DokuHTTPClient(); $http->max_bodysize = $maxSize; $http->timeout = 25; //max. 25 sec $http->keep_alive = false; // we do single ops here, no need for keep-alive $data = $http->get($url); if(!$data) return false; $name = ''; if ($useAttachment) { if (isset($http->resp_headers['content-disposition'])) { $content_disposition = $http->resp_headers['content-disposition']; $match=array(); if (is_string($content_disposition) && preg_match('/attachment;\s*filename\s*=\s*"([^"]*)"/i', $content_disposition, $match)) { $name = \dokuwiki\Utf8\PhpString::basename($match[1]); } } if (!$name) { if (!$defaultName) return false; $name = $defaultName; } $file = $file.$name; } $fileexists = file_exists($file); $fp = @fopen($file,"w"); if(!$fp) return false; fwrite($fp,$data); fclose($fp); if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']); if ($useAttachment) return $name; return true; } /** * Windows compatible rename * * rename() can not overwrite existing files on Windows * this function will use copy/unlink instead * * @param string $from * @param string $to * @return bool succes or fail */ function io_rename($from,$to){ global $conf; if(!@rename($from,$to)){ if(@copy($from,$to)){ if($conf['fperm']) chmod($to, $conf['fperm']); @unlink($from); return true; } return false; } return true; } /** * Runs an external command with input and output pipes. * Returns the exit code from the process. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $cmd * @param string $input input pipe * @param string $output output pipe * @return int exit code from process */ function io_exec($cmd, $input, &$output){ $descspec = array( 0=>array("pipe","r"), 1=>array("pipe","w"), 2=>array("pipe","w")); $ph = proc_open($cmd, $descspec, $pipes); if(!$ph) return -1; fclose($pipes[2]); // ignore stderr fwrite($pipes[0], $input); fclose($pipes[0]); $output = stream_get_contents($pipes[1]); fclose($pipes[1]); return proc_close($ph); } /** * Search a file for matching lines * * This is probably not faster than file()+preg_grep() but less * memory intensive because not the whole file needs to be loaded * at once. * * @author Andreas Gohr <andi@splitbrain.org> * @param string $file The file to search * @param string $pattern PCRE pattern * @param int $max How many lines to return (0 for all) * @param bool $backref When true returns array with backreferences instead of lines * @return array matching lines or backref, false on error */ function io_grep($file,$pattern,$max=0,$backref=false){ $fh = @fopen($file,'r'); if(!$fh) return false; $matches = array(); $cnt = 0; $line = ''; while (!feof($fh)) { $line .= fgets($fh, 4096); // read full line if(substr($line,-1) != "\n") continue; // check if line matches if(preg_match($pattern,$line,$match)){ if($backref){ $matches[] = $match; }else{ $matches[] = $line; } $cnt++; } if($max && $max == $cnt) break; $line = ''; } fclose($fh); return $matches; } /** * Get size of contents of a file, for a compressed file the uncompressed size * Warning: reading uncompressed size of content of bz-files requires uncompressing * * @author Gerrit Uitslag <klapinklapin@gmail.com> * * @param string $file filename path to file * @return int size of file */ function io_getSizeFile($file) { if (!file_exists($file)) return 0; if(substr($file,-3) == '.gz'){ $fp = @fopen($file, "rb"); if($fp === false) return 0; fseek($fp, -4, SEEK_END); $buffer = fread($fp, 4); fclose($fp); $array = unpack("V", $buffer); $uncompressedsize = end($array); }else if(substr($file,-4) == '.bz2'){ if(!DOKU_HAS_BZIP) return 0; $bz = bzopen($file,"r"); if($bz === false) return 0; $uncompressedsize = 0; while (!feof($bz)) { //8192 seems to be the maximum buffersize? $buffer = bzread($bz,8192); if(($buffer === false) || (bzerrno($bz) !== 0)) { return 0; } $uncompressedsize += strlen($buffer); } }else{ $uncompressedsize = filesize($file); } return $uncompressedsize; } StyleUtils.php 0000644 00000015007 15233462216 0007405 0 ustar 00 <?php namespace dokuwiki; /** * Class StyleUtils * * Reads and applies the template's style.ini settings */ class StyleUtils { /** @var string current template */ protected $tpl; /** @var bool reinitialize styles config */ protected $reinit; /** @var bool $preview preview mode */ protected $preview; /** @var array default replacements to be merged with custom style configs */ protected $defaultReplacements = array( '__text__' => "#000", '__background__' => "#fff", '__text_alt__' => "#999", '__background_alt__' => "#eee", '__text_neu__' => "#666", '__background_neu__' => "#ddd", '__border__' => "#ccc", '__highlight__' => "#ff9", '__link__' => "#00f", ); /** * StyleUtils constructor. * @param string $tpl template name: if not passed as argument, the default value from $conf will be used * @param bool $preview * @param bool $reinit whether static style conf should be reinitialized */ public function __construct($tpl = '', $preview = false, $reinit = false) { if (!$tpl) { global $conf; $tpl = $conf['template']; } $this->tpl = $tpl; $this->reinit = $reinit; $this->preview = $preview; } /** * Load style ini contents * * Loads and merges style.ini files from template and config and prepares * the stylesheet modes * * @author Andreas Gohr <andi@splitbrain.org> * @author Anna Dabrowska <info@cosmocode.de> * * @return array with keys 'stylesheets' and 'replacements' */ public function cssStyleini() { static $combined = []; if (!empty($combined) && !$this->reinit) { return $combined; } global $conf; global $config_cascade; $stylesheets = array(); // mode, file => base // guaranteed placeholder => value $replacements = $this->defaultReplacements; // merge all styles from config cascade if (!is_array($config_cascade['styleini'])) { trigger_error('Missing config cascade for styleini', E_USER_WARNING); } // allow replacement overwrites in preview mode if ($this->preview) { $config_cascade['styleini']['local'][] = $conf['cachedir'] . '/preview.ini'; } $combined['stylesheets'] = []; $combined['replacements'] = []; foreach (array('default', 'local', 'protected') as $config_group) { if (empty($config_cascade['styleini'][$config_group])) continue; // set proper server dirs $webbase = $this->getWebbase($config_group); foreach ($config_cascade['styleini'][$config_group] as $inifile) { // replace the placeholder with the name of the current template $inifile = str_replace('%TEMPLATE%', $this->tpl, $inifile); $incbase = dirname($inifile) . '/'; if (file_exists($inifile)) { $config = parse_ini_file($inifile, true); if (is_array($config['stylesheets'])) { foreach ($config['stylesheets'] as $inifile => $mode) { // validate and include style files $stylesheets = array_merge( $stylesheets, $this->getValidatedStyles($stylesheets, $inifile, $mode, $incbase, $webbase) ); $combined['stylesheets'] = array_merge($combined['stylesheets'], $stylesheets); } } if (is_array($config['replacements'])) { $replacements = array_replace( $replacements, $this->cssFixreplacementurls($config['replacements'], $webbase) ); $combined['replacements'] = array_merge($combined['replacements'], $replacements); } } } } return $combined; } /** * Checks if configured style files exist and, if necessary, adjusts file extensions in config * * @param array $stylesheets * @param string $file * @param string $mode * @param string $incbase * @param string $webbase * @return mixed */ protected function getValidatedStyles($stylesheets, $file, $mode, $incbase, $webbase) { global $conf; if (!file_exists($incbase . $file)) { list($extension, $basename) = array_map('strrev', explode('.', strrev($file), 2)); $newExtension = $extension === 'css' ? 'less' : 'css'; if (file_exists($incbase . $basename . '.' . $newExtension)) { $stylesheets[$mode][$incbase . $basename . '.' . $newExtension] = $webbase; if ($conf['allowdebug']) { msg("Stylesheet $file not found, using $basename.$newExtension instead. " . "Please contact developer of \"$this->tpl\" template.", 2); } } elseif ($conf['allowdebug']) { msg("Stylesheet $file not found, please contact the developer of \"$this->tpl\" template.", 2); } } $stylesheets[$mode][fullpath($incbase . $file)] = $webbase; return $stylesheets; } /** * Returns the web base path for the given level/group in config cascade. * Style resources are relative to the template directory for the main (default) styles * but relative to DOKU_BASE for everything else" * * @param string $config_group * @return string */ protected function getWebbase($config_group) { if ($config_group === 'default') { return tpl_basedir($this->tpl); } else { return DOKU_BASE; } } /** * Amend paths used in replacement relative urls, refer FS#2879 * * @author Chris Smith <chris@jalakai.co.uk> * * @param array $replacements with key-value pairs * @param string $location * @return array */ protected function cssFixreplacementurls($replacements, $location) { foreach ($replacements as $key => $value) { $replacements[$key] = preg_replace( '#(url\([ \'"]*)(?!/|data:|http://|https://| |\'|")#', '\\1' . $location, $value ); } return $replacements; } } auth.php 0000644 00000113626 15233462216 0006233 0 ustar 00 <?php /** * Authentication library * * Including this file will automatically try to login * a user by calling auth_login() * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ use dokuwiki\Extension\AuthPlugin; use dokuwiki\Extension\Event; use dokuwiki\Extension\PluginController; use dokuwiki\PassHash; use dokuwiki\Subscriptions\RegistrationSubscriptionSender; /** * Initialize the auth system. * * This function is automatically called at the end of init.php * * This used to be the main() of the auth.php * * @todo backend loading maybe should be handled by the class autoloader * @todo maybe split into multiple functions at the XXX marked positions * @triggers AUTH_LOGIN_CHECK * @return bool */ function auth_setup() { global $conf; /* @var AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; global $AUTH_ACL; global $lang; /* @var PluginController $plugin_controller */ global $plugin_controller; $AUTH_ACL = array(); if(!$conf['useacl']) return false; // try to load auth backend from plugins foreach ($plugin_controller->getList('auth') as $plugin) { if ($conf['authtype'] === $plugin) { $auth = $plugin_controller->load('auth', $plugin); break; } } if(!isset($auth) || !$auth){ msg($lang['authtempfail'], -1); return false; } if ($auth->success == false) { // degrade to unauthenticated user unset($auth); auth_logoff(); msg($lang['authtempfail'], -1); return false; } // do the login either by cookie or provided credentials XXX $INPUT->set('http_credentials', false); if(!$conf['rememberme']) $INPUT->set('r', false); // handle renamed HTTP_AUTHORIZATION variable (can happen when a fix like // the one presented at // http://www.besthostratings.com/articles/http-auth-php-cgi.html is used // for enabling HTTP authentication with CGI/SuExec) if(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; // streamline HTTP auth credentials (IIS/rewrite -> mod_php) if(isset($_SERVER['HTTP_AUTHORIZATION'])) { list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); } // if no credentials were given try to use HTTP auth (for SSO) if(!$INPUT->str('u') && empty($_COOKIE[DOKU_COOKIE]) && !empty($_SERVER['PHP_AUTH_USER'])) { $INPUT->set('u', $_SERVER['PHP_AUTH_USER']); $INPUT->set('p', $_SERVER['PHP_AUTH_PW']); $INPUT->set('http_credentials', true); } // apply cleaning (auth specific user names, remove control chars) if (true === $auth->success) { $INPUT->set('u', $auth->cleanUser(stripctl($INPUT->str('u')))); $INPUT->set('p', stripctl($INPUT->str('p'))); } $ok = null; if (!is_null($auth) && $auth->canDo('external')) { $ok = $auth->trustExternal($INPUT->str('u'), $INPUT->str('p'), $INPUT->bool('r')); } if ($ok === null) { // external trust mechanism not in place, or returns no result, // then attempt auth_login $evdata = array( 'user' => $INPUT->str('u'), 'password' => $INPUT->str('p'), 'sticky' => $INPUT->bool('r'), 'silent' => $INPUT->bool('http_credentials') ); Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper'); } //load ACL into a global array XXX $AUTH_ACL = auth_loadACL(); return true; } /** * Loads the ACL setup and handle user wildcards * * @author Andreas Gohr <andi@splitbrain.org> * * @return array */ function auth_loadACL() { global $config_cascade; global $USERINFO; /* @var Input $INPUT */ global $INPUT; if(!is_readable($config_cascade['acl']['default'])) return array(); $acl = file($config_cascade['acl']['default']); $out = array(); foreach($acl as $line) { $line = trim($line); 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) if(strstr($line, '%USER%')){ // if user is not logged in, this ACL line is meaningless - skip it if (!$INPUT->server->has('REMOTE_USER')) continue; $id = str_replace('%USER%',cleanID($INPUT->server->str('REMOTE_USER')),$id); $rest = str_replace('%USER%',auth_nameencode($INPUT->server->str('REMOTE_USER')),$rest); } // 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) 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"; } } return $out; } /** * Event hook callback for AUTH_LOGIN_CHECK * * @param array $evdata * @return bool */ function auth_login_wrapper($evdata) { return auth_login( $evdata['user'], $evdata['password'], $evdata['sticky'], $evdata['silent'] ); } /** * This tries to login the user based on the sent auth credentials * * The authentication works like this: if a username was given * a new login is assumed and user/password are checked. If they * are correct the password is encrypted with blowfish and stored * together with the username in a cookie - the same info is stored * in the session, too. Additonally a browserID is stored in the * session. * * If no username was given the cookie is checked: if the username, * crypted password and browserID match between session and cookie * no further testing is done and the user is accepted * * If a cookie was found but no session info was availabe the * blowfish encrypted password from the cookie is decrypted and * together with username rechecked by calling this function again. * * On a successful login $_SERVER[REMOTE_USER] and $USERINFO * are set. * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $user Username * @param string $pass Cleartext Password * @param bool $sticky Cookie should not expire * @param bool $silent Don't show error on bad auth * @return bool true on successful auth */ function auth_login($user, $pass, $sticky = false, $silent = false) { global $USERINFO; global $conf; global $lang; /* @var AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; $sticky ? $sticky = true : $sticky = false; //sanity check if(!$auth) return false; if(!empty($user)) { //usual login if(!empty($pass) && $auth->checkPass($user, $pass)) { // make logininfo globally available $INPUT->server->set('REMOTE_USER', $user); $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session auth_setCookie($user, auth_encrypt($pass, $secret), $sticky); return true; } else { //invalid credentials - log off if(!$silent) { http_status(403, 'Login failed'); msg($lang['badlogin'], -1); } auth_logoff(); return false; } } else { // read cookie information list($user, $sticky, $pass) = auth_getCookie(); if($user && $pass) { // we got a cookie - see if we can trust it // get session info $session = $_SESSION[DOKU_COOKIE]['auth']; if(isset($session) && $auth->useSessionCache($user) && ($session['time'] >= time() - $conf['auth_security_timeout']) && ($session['user'] == $user) && ($session['pass'] == sha1($pass)) && //still crypted ($session['buid'] == auth_browseruid()) ) { // he has session, cookie and browser right - let him in $INPUT->server->set('REMOTE_USER', $user); $USERINFO = $session['info']; //FIXME move all references to session return true; } // no we don't trust it yet - recheck pass but silent $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session $pass = auth_decrypt($pass, $secret); return auth_login($user, $pass, $sticky, true); } } //just to be sure auth_logoff(true); return false; } /** * Builds a pseudo UID from browser and IP data * * This is neither unique nor unfakable - still it adds some * security. Using the first part of the IP makes sure * proxy farms like AOLs are still okay. * * @author Andreas Gohr <andi@splitbrain.org> * * @return string a MD5 sum of various browser headers */ function auth_browseruid() { /* @var Input $INPUT */ global $INPUT; $ip = clientIP(true); $uid = ''; $uid .= $INPUT->server->str('HTTP_USER_AGENT'); $uid .= $INPUT->server->str('HTTP_ACCEPT_CHARSET'); $uid .= substr($ip, 0, strpos($ip, '.')); $uid = strtolower($uid); return md5($uid); } /** * Creates a random key to encrypt the password in cookies * * This function tries to read the password for encrypting * cookies from $conf['metadir'].'/_htcookiesalt' * if no such file is found a random key is created and * and stored in this file. * * @author Andreas Gohr <andi@splitbrain.org> * * @param bool $addsession if true, the sessionid is added to the salt * @param bool $secure if security is more important than keeping the old value * @return string */ function auth_cookiesalt($addsession = false, $secure = false) { if (defined('SIMPLE_TEST')) { return 'test'; } global $conf; $file = $conf['metadir'].'/_htcookiesalt'; if ($secure || !file_exists($file)) { $file = $conf['metadir'].'/_htcookiesalt2'; } $salt = io_readFile($file); if(empty($salt)) { $salt = bin2hex(auth_randombytes(64)); io_saveFile($file, $salt); } if($addsession) { $salt .= session_id(); } return $salt; } /** * Return cryptographically secure random bytes. * * @author Niklas Keller <me@kelunik.com> * * @param int $length number of bytes * @return string cryptographically secure random bytes */ function auth_randombytes($length) { return random_bytes($length); } /** * Cryptographically secure random number generator. * * @author Niklas Keller <me@kelunik.com> * * @param int $min * @param int $max * @return int */ function auth_random($min, $max) { return random_int($min, $max); } /** * Encrypt data using the given secret using AES * * The mode is CBC with a random initialization vector, the key is derived * using pbkdf2. * * @param string $data The data that shall be encrypted * @param string $secret The secret/password that shall be used * @return string The ciphertext */ function auth_encrypt($data, $secret) { $iv = auth_randombytes(16); $cipher = new \phpseclib\Crypt\AES(); $cipher->setPassword($secret); /* this uses the encrypted IV as IV as suggested in http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf, Appendix C for unique but necessarily random IVs. The resulting ciphertext is compatible to ciphertext that was created using a "normal" IV. */ return $cipher->encrypt($iv.$data); } /** * Decrypt the given AES ciphertext * * The mode is CBC, the key is derived using pbkdf2 * * @param string $ciphertext The encrypted data * @param string $secret The secret/password that shall be used * @return string The decrypted data */ function auth_decrypt($ciphertext, $secret) { $iv = substr($ciphertext, 0, 16); $cipher = new \phpseclib\Crypt\AES(); $cipher->setPassword($secret); $cipher->setIV($iv); return $cipher->decrypt(substr($ciphertext, 16)); } /** * Log out the current user * * This clears all authentication data and thus log the user * off. It also clears session data. * * @author Andreas Gohr <andi@splitbrain.org> * * @param bool $keepbc - when true, the breadcrumb data is not cleared */ function auth_logoff($keepbc = false) { global $conf; global $USERINFO; /* @var AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; // make sure the session is writable (it usually is) @session_start(); if(isset($_SESSION[DOKU_COOKIE]['auth']['user'])) unset($_SESSION[DOKU_COOKIE]['auth']['user']); if(isset($_SESSION[DOKU_COOKIE]['auth']['pass'])) unset($_SESSION[DOKU_COOKIE]['auth']['pass']); if(isset($_SESSION[DOKU_COOKIE]['auth']['info'])) unset($_SESSION[DOKU_COOKIE]['auth']['info']); if(!$keepbc && isset($_SESSION[DOKU_COOKIE]['bc'])) unset($_SESSION[DOKU_COOKIE]['bc']); $INPUT->server->remove('REMOTE_USER'); $USERINFO = null; //FIXME $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; setcookie(DOKU_COOKIE, '', time() - 600000, $cookieDir, '', ($conf['securecookie'] && is_ssl()), true); if($auth) $auth->logOff(); } /** * Check if a user is a manager * * Should usually be called without any parameters to check the current * user. * * The info is available through $INFO['ismanager'], too * * @param string $user Username * @param array $groups List of groups the user is in * @param bool $adminonly when true checks if user is admin * @param bool $recache set to true to refresh the cache * @return bool * @see auth_isadmin * * @author Andreas Gohr <andi@splitbrain.org> */ function auth_ismanager($user = null, $groups = null, $adminonly = false, $recache=false) { global $conf; global $USERINFO; /* @var AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; if(!$auth) return false; if(is_null($user)) { if(!$INPUT->server->has('REMOTE_USER')) { return false; } else { $user = $INPUT->server->str('REMOTE_USER'); } } if(is_null($groups)) { $groups = $USERINFO ? (array) $USERINFO['grps'] : array(); } // prefer cached result static $cache = []; $cachekey = serialize([$user, $adminonly, $groups]); if (!isset($cache[$cachekey]) || $recache) { // check superuser match $ok = auth_isMember($conf['superuser'], $user, $groups); // check managers if (!$ok && !$adminonly) { $ok = auth_isMember($conf['manager'], $user, $groups); } $cache[$cachekey] = $ok; } return $cache[$cachekey]; } /** * Check if a user is admin * * Alias to auth_ismanager with adminonly=true * * The info is available through $INFO['isadmin'], too * * @param string $user Username * @param array $groups List of groups the user is in * @param bool $recache set to true to refresh the cache * @return bool * @author Andreas Gohr <andi@splitbrain.org> * @see auth_ismanager() * */ function auth_isadmin($user = null, $groups = null, $recache=false) { return auth_ismanager($user, $groups, true, $recache); } /** * Match a user and his groups against a comma separated list of * users and groups to determine membership status * * Note: all input should NOT be nameencoded. * * @param string $memberlist commaseparated list of allowed users and groups * @param string $user user to match against * @param array $groups groups the user is member of * @return bool true for membership acknowledged */ function auth_isMember($memberlist, $user, array $groups) { /* @var AuthPlugin $auth */ global $auth; if(!$auth) return false; // clean user and groups if(!$auth->isCaseSensitive()) { $user = \dokuwiki\Utf8\PhpString::strtolower($user); $groups = array_map('utf8_strtolower', $groups); } $user = $auth->cleanUser($user); $groups = array_map(array($auth, 'cleanGroup'), $groups); // extract the memberlist $members = explode(',', $memberlist); $members = array_map('trim', $members); $members = array_unique($members); $members = array_filter($members); // compare cleaned values foreach($members as $member) { if($member == '@ALL' ) return true; if(!$auth->isCaseSensitive()) $member = \dokuwiki\Utf8\PhpString::strtolower($member); if($member[0] == '@') { $member = $auth->cleanGroup(substr($member, 1)); if(in_array($member, $groups)) return true; } else { $member = $auth->cleanUser($member); if($member == $user) return true; } } // still here? not a member! return false; } /** * Convinience function for auth_aclcheck() * * This checks the permissions for the current user * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $id page ID (needs to be resolved and cleaned) * @return int permission level */ function auth_quickaclcheck($id) { global $conf; global $USERINFO; /* @var Input $INPUT */ 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'), is_array($USERINFO) ? $USERINFO['grps'] : array()); } /** * Returns the maximum rights a user has for the given ID or its namespace * * @author Andreas Gohr <andi@splitbrain.org> * * @triggers AUTH_ACL_CHECK * @param string $id page ID (needs to be resolved and cleaned) * @param string $user Username * @param array|null $groups Array of groups the user is in * @return int permission level */ function auth_aclcheck($id, $user, $groups) { $data = array( 'id' => $id, 'user' => $user, 'groups' => $groups ); return Event::createAndTrigger('AUTH_ACL_CHECK', $data, 'auth_aclcheck_cb'); } /** * default ACL check method * * DO NOT CALL DIRECTLY, use auth_aclcheck() instead * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $data event data * @return int permission level */ function auth_aclcheck_cb($data) { $id =& $data['id']; $user =& $data['user']; $groups =& $data['groups']; global $conf; global $AUTH_ACL; /* @var AuthPlugin $auth */ global $auth; // if no ACL is used always return upload rights if(!$conf['useacl']) return AUTH_UPLOAD; if(!$auth) return AUTH_NONE; //make sure groups is an array if(!is_array($groups)) $groups = array(); //if user is superuser or in superusergroup return 255 (acl_admin) if(auth_isadmin($user, $groups)) { return AUTH_ADMIN; } if(!$auth->isCaseSensitive()) { $user = \dokuwiki\Utf8\PhpString::strtolower($user); $groups = array_map('utf8_strtolower', $groups); } $user = auth_nameencode($auth->cleanUser($user)); $groups = array_map(array($auth, 'cleanGroup'), (array) $groups); //prepend groups with @ and nameencode foreach($groups as &$group) { $group = '@'.auth_nameencode($group); } $ns = getNS($id); $perm = -1; //add ALL group $groups[] = '@ALL'; //add User if($user) $groups[] = $user; //check exact match first $matches = preg_grep('/^'.preg_quote($id, '/').'[ \t]+([^ \t]+)[ \t]+/', $AUTH_ACL); if(count($matches)) { foreach($matches as $match) { $match = preg_replace('/#.*$/', '', $match); //ignore comments $acl = preg_split('/[ \t]+/', $match); if(!$auth->isCaseSensitive() && $acl[1] !== '@ALL') { $acl[1] = \dokuwiki\Utf8\PhpString::strtolower($acl[1]); } if(!in_array($acl[1], $groups)) { continue; } if($acl[2] > AUTH_DELETE) $acl[2] = AUTH_DELETE; //no admins in the ACL! if($acl[2] > $perm) { $perm = $acl[2]; } } if($perm > -1) { //we had a match - return it return (int) $perm; } } //still here? do the namespace checks if($ns) { $path = $ns.':*'; } else { $path = '*'; //root document } do { $matches = preg_grep('/^'.preg_quote($path, '/').'[ \t]+([^ \t]+)[ \t]+/', $AUTH_ACL); if(count($matches)) { foreach($matches as $match) { $match = preg_replace('/#.*$/', '', $match); //ignore comments $acl = preg_split('/[ \t]+/', $match); if(!$auth->isCaseSensitive() && $acl[1] !== '@ALL') { $acl[1] = \dokuwiki\Utf8\PhpString::strtolower($acl[1]); } if(!in_array($acl[1], $groups)) { continue; } if($acl[2] > AUTH_DELETE) $acl[2] = AUTH_DELETE; //no admins in the ACL! if($acl[2] > $perm) { $perm = $acl[2]; } } //we had a match - return it if($perm != -1) { return (int) $perm; } } //get next higher namespace $ns = getNS($ns); if($path != '*') { $path = $ns.':*'; if($path == ':*') $path = '*'; } else { //we did this already //looks like there is something wrong with the ACL //break here msg('No ACL setup yet! Denying access to everyone.'); return AUTH_NONE; } } while(1); //this should never loop endless return AUTH_NONE; } /** * Encode ASCII special chars * * Some auth backends allow special chars in their user and groupnames * The special chars are encoded with this function. Only ASCII chars * are encoded UTF-8 multibyte are left as is (different from usual * urlencoding!). * * Decoding can be done with rawurldecode * * @author Andreas Gohr <gohr@cosmocode.de> * @see rawurldecode() * * @param string $name * @param bool $skip_group * @return string */ function auth_nameencode($name, $skip_group = false) { global $cache_authname; $cache =& $cache_authname; $name = (string) $name; // never encode wildcard FS#1955 if($name == '%USER%') return $name; if($name == '%GROUP%') return $name; if(!isset($cache[$name][$skip_group])) { 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) ); } else { $cache[$name][$skip_group] = preg_replace_callback( '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/', 'auth_nameencode_callback', $name ); } } return $cache[$name][$skip_group]; } /** * callback encodes the matches * * @param array $matches first complete match, next matching subpatterms * @return string */ function auth_nameencode_callback($matches) { return '%'.dechex(ord(substr($matches[1],-1))); } /** * Create a pronouncable password * * The $foruser variable might be used by plugins to run additional password * policy checks, but is not used by the default implementation * * @author Andreas Gohr <andi@splitbrain.org> * @link http://www.phpbuilder.com/annotate/message.php3?id=1014451 * @triggers AUTH_PASSWORD_GENERATE * * @param string $foruser username for which the password is generated * @return string pronouncable password */ function auth_pwgen($foruser = '') { $data = array( 'password' => '', 'foruser' => $foruser ); $evt = new Event('AUTH_PASSWORD_GENERATE', $data); if($evt->advise_before(true)) { $c = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones $v = 'aeiou'; //vowels $a = $c.$v; //both $s = '!$%&?+*~#-_:.;,'; // specials //use thre syllables... for($i = 0; $i < 3; $i++) { $data['password'] .= $c[auth_random(0, strlen($c) - 1)]; $data['password'] .= $v[auth_random(0, strlen($v) - 1)]; $data['password'] .= $a[auth_random(0, strlen($a) - 1)]; } //... and add a nice number and special $data['password'] .= $s[auth_random(0, strlen($s) - 1)].auth_random(10, 99); } $evt->advise_after(); return $data['password']; } /** * Sends a password to the given user * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $user Login name of the user * @param string $password The new password in clear text * @return bool true on success */ function auth_sendPassword($user, $password) { global $lang; /* @var AuthPlugin $auth */ global $auth; if(!$auth) return false; $user = $auth->cleanUser($user); $userinfo = $auth->getUserData($user, $requireGroups = false); if(!$userinfo['mail']) return false; $text = rawLocale('password'); $trep = array( 'FULLNAME' => $userinfo['name'], 'LOGIN' => $user, 'PASSWORD' => $password ); $mail = new Mailer(); $mail->to($mail->getCleanName($userinfo['name']).' <'.$userinfo['mail'].'>'); $mail->subject($lang['regpwmail']); $mail->setBody($text, $trep); return $mail->send(); } /** * Register a new user * * This registers a new user - Data is read directly from $_POST * * @author Andreas Gohr <andi@splitbrain.org> * * @return bool true on success, false on any error */ function register() { global $lang; global $conf; /* @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; global $INPUT; if(!$INPUT->post->bool('save')) return false; if(!actionOK('register')) return false; // gather input $login = trim($auth->cleanUser($INPUT->post->str('login'))); $fullname = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $INPUT->post->str('fullname'))); $email = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $INPUT->post->str('email'))); $pass = $INPUT->post->str('pass'); $passchk = $INPUT->post->str('passchk'); if(empty($login) || empty($fullname) || empty($email)) { msg($lang['regmissing'], -1); return false; } if($conf['autopasswd']) { $pass = auth_pwgen($login); // automatically generate password } elseif(empty($pass) || empty($passchk)) { msg($lang['regmissing'], -1); // complain about missing passwords return false; } elseif($pass != $passchk) { msg($lang['regbadpass'], -1); // complain about misspelled passwords return false; } //check mail if(!mail_isvalid($email)) { msg($lang['regbadmail'], -1); return false; } //okay try to create the user if(!$auth->triggerUserMod('create', array($login, $pass, $fullname, $email))) { msg($lang['regfail'], -1); return false; } // send notification about the new user $subscription = new RegistrationSubscriptionSender(); $subscription->sendRegister($login, $fullname, $email); // are we done? if(!$conf['autopasswd']) { msg($lang['regsuccess2'], 1); return true; } // autogenerated password? then send password to user if(auth_sendPassword($login, $pass)) { msg($lang['regsuccess'], 1); return true; } else { msg($lang['regmailfail'], -1); return false; } } /** * Update user profile * * @author Christopher Smith <chris@jalakai.co.uk> */ function updateprofile() { global $conf; global $lang; /* @var AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; if(!$INPUT->post->bool('save')) return false; if(!checkSecurityToken()) return false; if(!actionOK('profile')) { msg($lang['profna'], -1); return false; } $changes = array(); $changes['pass'] = $INPUT->post->str('newpass'); $changes['name'] = $INPUT->post->str('fullname'); $changes['mail'] = $INPUT->post->str('email'); // check misspelled passwords if($changes['pass'] != $INPUT->post->str('passchk')) { msg($lang['regbadpass'], -1); return false; } // clean fullname and email $changes['name'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $changes['name'])); $changes['mail'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $changes['mail'])); // no empty name and email (except the backend doesn't support them) if((empty($changes['name']) && $auth->canDo('modName')) || (empty($changes['mail']) && $auth->canDo('modMail')) ) { msg($lang['profnoempty'], -1); return false; } if(!mail_isvalid($changes['mail']) && $auth->canDo('modMail')) { msg($lang['regbadmail'], -1); return false; } $changes = array_filter($changes); // check for unavailable capabilities if(!$auth->canDo('modName')) unset($changes['name']); if(!$auth->canDo('modMail')) unset($changes['mail']); if(!$auth->canDo('modPass')) unset($changes['pass']); // anything to do? if(!count($changes)) { msg($lang['profnochange'], -1); return false; } if($conf['profileconfirm']) { if(!$auth->checkPass($INPUT->server->str('REMOTE_USER'), $INPUT->post->str('oldpass'))) { msg($lang['badpassconfirm'], -1); return false; } } if(!$auth->triggerUserMod('modify', array($INPUT->server->str('REMOTE_USER'), &$changes))) { msg($lang['proffail'], -1); return false; } if($changes['pass']) { // update cookie and session with the changed data list( /*user*/, $sticky, /*pass*/) = auth_getCookie(); $pass = auth_encrypt($changes['pass'], auth_cookiesalt(!$sticky, true)); auth_setCookie($INPUT->server->str('REMOTE_USER'), $pass, (bool) $sticky); } else { // make sure the session is writable @session_start(); // invalidate session cache $_SESSION[DOKU_COOKIE]['auth']['time'] = 0; session_write_close(); } return true; } /** * Delete the current logged-in user * * @return bool true on success, false on any error */ function auth_deleteprofile(){ global $conf; global $lang; /* @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; if(!$INPUT->post->bool('delete')) return false; if(!checkSecurityToken()) return false; // action prevented or auth module disallows if(!actionOK('profile_delete') || !$auth->canDo('delUser')) { msg($lang['profnodelete'], -1); return false; } if(!$INPUT->post->bool('confirm_delete')){ msg($lang['profconfdeletemissing'], -1); return false; } if($conf['profileconfirm']) { if(!$auth->checkPass($INPUT->server->str('REMOTE_USER'), $INPUT->post->str('oldpass'))) { msg($lang['badpassconfirm'], -1); return false; } } $deleted = array(); $deleted[] = $INPUT->server->str('REMOTE_USER'); if($auth->triggerUserMod('delete', array($deleted))) { // force and immediate logout including removing the sticky cookie auth_logoff(); return true; } return false; } /** * Send a new password * * This function handles both phases of the password reset: * * - handling the first request of password reset * - validating the password reset auth token * * @author Benoit Chesneau <benoit@bchesneau.info> * @author Chris Smith <chris@jalakai.co.uk> * @author Andreas Gohr <andi@splitbrain.org> * * @return bool true on success, false on any error */ function act_resendpwd() { global $lang; global $conf; /* @var AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; if(!actionOK('resendpwd')) { msg($lang['resendna'], -1); return false; } $token = preg_replace('/[^a-f0-9]+/', '', $INPUT->str('pwauth')); if($token) { // we're in token phase - get user info from token $tfile = $conf['cachedir'].'/'.$token[0].'/'.$token.'.pwauth'; if(!file_exists($tfile)) { msg($lang['resendpwdbadauth'], -1); $INPUT->remove('pwauth'); return false; } // token is only valid for 3 days if((time() - filemtime($tfile)) > (3 * 60 * 60 * 24)) { msg($lang['resendpwdbadauth'], -1); $INPUT->remove('pwauth'); @unlink($tfile); return false; } $user = io_readfile($tfile); $userinfo = $auth->getUserData($user, $requireGroups = false); if(!$userinfo['mail']) { msg($lang['resendpwdnouser'], -1); return false; } if(!$conf['autopasswd']) { // we let the user choose a password $pass = $INPUT->str('pass'); // password given correctly? if(!$pass) return false; if($pass != $INPUT->str('passchk')) { msg($lang['regbadpass'], -1); return false; } // change it if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) { msg($lang['proffail'], -1); return false; } } else { // autogenerate the password and send by mail $pass = auth_pwgen($user); if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) { msg($lang['proffail'], -1); return false; } if(auth_sendPassword($user, $pass)) { msg($lang['resendpwdsuccess'], 1); } else { msg($lang['regmailfail'], -1); } } @unlink($tfile); return true; } else { // we're in request phase if(!$INPUT->post->bool('save')) return false; if(!$INPUT->post->str('login')) { msg($lang['resendpwdmissing'], -1); return false; } else { $user = trim($auth->cleanUser($INPUT->post->str('login'))); } $userinfo = $auth->getUserData($user, $requireGroups = false); if(!$userinfo['mail']) { msg($lang['resendpwdnouser'], -1); return false; } // generate auth token $token = md5(auth_randombytes(16)); // random secret $tfile = $conf['cachedir'].'/'.$token[0].'/'.$token.'.pwauth'; $url = wl('', array('do'=> 'resendpwd', 'pwauth'=> $token), true, '&'); io_saveFile($tfile, $user); $text = rawLocale('pwconfirm'); $trep = array( 'FULLNAME' => $userinfo['name'], 'LOGIN' => $user, 'CONFIRM' => $url ); $mail = new Mailer(); $mail->to($userinfo['name'].' <'.$userinfo['mail'].'>'); $mail->subject($lang['regpwmail']); $mail->setBody($text, $trep); if($mail->send()) { msg($lang['resendpwdconfirm'], 1); } else { msg($lang['regmailfail'], -1); } return true; } // never reached } /** * Encrypts a password using the given method and salt * * If the selected method needs a salt and none was given, a random one * is chosen. * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $clear The clear text password * @param string $method The hashing method * @param string $salt A salt, null for random * @return string The crypted password */ function auth_cryptPassword($clear, $method = '', $salt = null) { global $conf; if(empty($method)) $method = $conf['passcrypt']; $pass = new PassHash(); $call = 'hash_'.$method; if(!method_exists($pass, $call)) { msg("Unsupported crypt method $method", -1); return false; } return $pass->$call($clear, $salt); } /** * Verifies a cleartext password against a crypted hash * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $clear The clear text password * @param string $crypt The hash to compare with * @return bool true if both match */ function auth_verifyPassword($clear, $crypt) { $pass = new PassHash(); return $pass->verify_hash($clear, $crypt); } /** * Set the authentication cookie and add user identification data to the session * * @param string $user username * @param string $pass encrypted password * @param bool $sticky whether or not the cookie will last beyond the session * @return bool */ function auth_setCookie($user, $pass, $sticky) { global $conf; /* @var AuthPlugin $auth */ global $auth; global $USERINFO; if(!$auth) return false; $USERINFO = $auth->getUserData($user); // set cookie $cookie = base64_encode($user).'|'.((int) $sticky).'|'.base64_encode($pass); $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; $time = $sticky ? (time() + 60 * 60 * 24 * 365) : 0; //one year setcookie(DOKU_COOKIE, $cookie, $time, $cookieDir, '', ($conf['securecookie'] && is_ssl()), true); // set session $_SESSION[DOKU_COOKIE]['auth']['user'] = $user; $_SESSION[DOKU_COOKIE]['auth']['pass'] = sha1($pass); $_SESSION[DOKU_COOKIE]['auth']['buid'] = auth_browseruid(); $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO; $_SESSION[DOKU_COOKIE]['auth']['time'] = time(); return true; } /** * Returns the user, (encrypted) password and sticky bit from cookie * * @returns array */ function auth_getCookie() { if(!isset($_COOKIE[DOKU_COOKIE])) { return array(null, null, null); } list($user, $sticky, $pass) = explode('|', $_COOKIE[DOKU_COOKIE], 3); $sticky = (bool) $sticky; $pass = base64_decode($pass); $user = base64_decode($user); return array($user, $sticky, $pass); } //Setup VIM: ex: et ts=2 : cli.php 0000644 00000047312 15233462216 0006037 0 ustar 00 <?php /** * Class DokuCLI * * All DokuWiki commandline scripts should inherit from this class and implement the abstract methods. * * @deprecated 2017-11-10 * @author Andreas Gohr <andi@splitbrain.org> */ abstract class DokuCLI { /** @var string the executed script itself */ protected $bin; /** @var DokuCLI_Options the option parser */ protected $options; /** @var DokuCLI_Colors */ public $colors; /** * constructor * * Initialize the arguments, set up helper classes and set up the CLI environment */ public function __construct() { set_exception_handler(array($this, 'fatal')); $this->options = new DokuCLI_Options(); $this->colors = new DokuCLI_Colors(); dbg_deprecated('use \splitbrain\phpcli\CLI instead'); $this->error('DokuCLI is deprecated, use \splitbrain\phpcli\CLI instead.'); } /** * Register options and arguments on the given $options object * * @param DokuCLI_Options $options * @return void */ abstract protected function setup(DokuCLI_Options $options); /** * Your main program * * Arguments and options have been parsed when this is run * * @param DokuCLI_Options $options * @return void */ abstract protected function main(DokuCLI_Options $options); /** * Execute the CLI program * * Executes the setup() routine, adds default options, initiate the options parsing and argument checking * and finally executes main() */ public function run() { if('cli' != php_sapi_name()) throw new DokuCLI_Exception('This has to be run from the command line'); // setup $this->setup($this->options); $this->options->registerOption( 'no-colors', 'Do not use any colors in output. Useful when piping output to other tools or files.' ); $this->options->registerOption( 'help', 'Display this help screen and exit immediately.', 'h' ); // parse $this->options->parseOptions(); // handle defaults if($this->options->getOpt('no-colors')) { $this->colors->disable(); } if($this->options->getOpt('help')) { echo $this->options->help(); exit(0); } // check arguments $this->options->checkArguments(); // execute $this->main($this->options); exit(0); } /** * Exits the program on a fatal error * * @param Exception|string $error either an exception or an error message */ public function fatal($error) { $code = 0; if(is_object($error) && is_a($error, 'Exception')) { /** @var Exception $error */ $code = $error->getCode(); $error = $error->getMessage(); } if(!$code) $code = DokuCLI_Exception::E_ANY; $this->error($error); exit($code); } /** * Print an error message * * @param string $string */ public function error($string) { $this->colors->ptln("E: $string", 'red', STDERR); } /** * Print a success message * * @param string $string */ public function success($string) { $this->colors->ptln("S: $string", 'green', STDERR); } /** * Print an info message * * @param string $string */ public function info($string) { $this->colors->ptln("I: $string", 'cyan', STDERR); } } /** * Class DokuCLI_Colors * * Handles color output on (Linux) terminals * * @author Andreas Gohr <andi@splitbrain.org> */ class DokuCLI_Colors { /** @var array known color names */ protected $colors = array( 'reset' => "\33[0m", 'black' => "\33[0;30m", 'darkgray' => "\33[1;30m", 'blue' => "\33[0;34m", 'lightblue' => "\33[1;34m", 'green' => "\33[0;32m", 'lightgreen' => "\33[1;32m", 'cyan' => "\33[0;36m", 'lightcyan' => "\33[1;36m", 'red' => "\33[0;31m", 'lightred' => "\33[1;31m", 'purple' => "\33[0;35m", 'lightpurple' => "\33[1;35m", 'brown' => "\33[0;33m", 'yellow' => "\33[1;33m", 'lightgray' => "\33[0;37m", 'white' => "\33[1;37m", ); /** @var bool should colors be used? */ protected $enabled = true; /** * Constructor * * Tries to disable colors for non-terminals */ public function __construct() { if(function_exists('posix_isatty') && !posix_isatty(STDOUT)) { $this->enabled = false; return; } if(!getenv('TERM')) { $this->enabled = false; return; } } /** * enable color output */ public function enable() { $this->enabled = true; } /** * disable color output */ public function disable() { $this->enabled = false; } /** * Convenience function to print a line in a given color * * @param string $line * @param string $color * @param resource $channel */ public function ptln($line, $color, $channel = STDOUT) { $this->set($color); fwrite($channel, rtrim($line)."\n"); $this->reset(); } /** * Set the given color for consecutive output * * @param string $color one of the supported color names * @throws DokuCLI_Exception */ public function set($color) { if(!$this->enabled) return; if(!isset($this->colors[$color])) throw new DokuCLI_Exception("No such color $color"); echo $this->colors[$color]; } /** * reset the terminal color */ public function reset() { $this->set('reset'); } } /** * Class DokuCLI_Options * * Parses command line options passed to the CLI script. Allows CLI scripts to easily register all accepted options and * commands and even generates a help text from this setup. * * @author Andreas Gohr <andi@splitbrain.org> */ class DokuCLI_Options { /** @var array keeps the list of options to parse */ protected $setup; /** @var array store parsed options */ protected $options = array(); /** @var string current parsed command if any */ protected $command = ''; /** @var array passed non-option arguments */ public $args = array(); /** @var string the executed script */ protected $bin; /** * Constructor */ public function __construct() { $this->setup = array( '' => array( 'opts' => array(), 'args' => array(), 'help' => '' ) ); // default command $this->args = $this->readPHPArgv(); $this->bin = basename(array_shift($this->args)); $this->options = array(); } /** * Sets the help text for the tool itself * * @param string $help */ public function setHelp($help) { $this->setup['']['help'] = $help; } /** * Register the names of arguments for help generation and number checking * * This has to be called in the order arguments are expected * * @param string $arg argument name (just for help) * @param string $help help text * @param bool $required is this a required argument * @param string $command if theses apply to a sub command only * @throws DokuCLI_Exception */ public function registerArgument($arg, $help, $required = true, $command = '') { if(!isset($this->setup[$command])) throw new DokuCLI_Exception("Command $command not registered"); $this->setup[$command]['args'][] = array( 'name' => $arg, 'help' => $help, 'required' => $required ); } /** * This registers a sub command * * Sub commands have their own options and use their own function (not main()). * * @param string $command * @param string $help * @throws DokuCLI_Exception */ public function registerCommand($command, $help) { if(isset($this->setup[$command])) throw new DokuCLI_Exception("Command $command already registered"); $this->setup[$command] = array( 'opts' => array(), 'args' => array(), 'help' => $help ); } /** * Register an option for option parsing and help generation * * @param string $long multi character option (specified with --) * @param string $help help text for this option * @param string|null $short one character option (specified with -) * @param bool|string $needsarg does this option require an argument? give it a name here * @param string $command what command does this option apply to * @throws DokuCLI_Exception */ public function registerOption($long, $help, $short = null, $needsarg = false, $command = '') { if(!isset($this->setup[$command])) throw new DokuCLI_Exception("Command $command not registered"); $this->setup[$command]['opts'][$long] = array( 'needsarg' => $needsarg, 'help' => $help, 'short' => $short ); if($short) { if(strlen($short) > 1) throw new DokuCLI_Exception("Short options should be exactly one ASCII character"); $this->setup[$command]['short'][$short] = $long; } } /** * Checks the actual number of arguments against the required number * * Throws an exception if arguments are missing. Called from parseOptions() * * @throws DokuCLI_Exception */ public function checkArguments() { $argc = count($this->args); $req = 0; foreach($this->setup[$this->command]['args'] as $arg) { if(!$arg['required']) break; // last required arguments seen $req++; } if($req > $argc) throw new DokuCLI_Exception("Not enough arguments", DokuCLI_Exception::E_OPT_ARG_REQUIRED); } /** * Parses the given arguments for known options and command * * The given $args array should NOT contain the executed file as first item anymore! The $args * array is stripped from any options and possible command. All found otions can be accessed via the * getOpt() function * * Note that command options will overwrite any global options with the same name * * @throws DokuCLI_Exception */ public function parseOptions() { $non_opts = array(); $argc = count($this->args); for($i = 0; $i < $argc; $i++) { $arg = $this->args[$i]; // The special element '--' means explicit end of options. Treat the rest of the arguments as non-options // and end the loop. if($arg == '--') { $non_opts = array_merge($non_opts, array_slice($this->args, $i + 1)); break; } // '-' is stdin - a normal argument if($arg == '-') { $non_opts = array_merge($non_opts, array_slice($this->args, $i)); break; } // first non-option if($arg[0] != '-') { $non_opts = array_merge($non_opts, array_slice($this->args, $i)); break; } // long option if(strlen($arg) > 1 && $arg[1] == '-') { list($opt, $val) = explode('=', substr($arg, 2), 2); if(!isset($this->setup[$this->command]['opts'][$opt])) { throw new DokuCLI_Exception("No such option $arg", DokuCLI_Exception::E_UNKNOWN_OPT); } // argument required? if($this->setup[$this->command]['opts'][$opt]['needsarg']) { if(is_null($val) && $i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) { $val = $this->args[++$i]; } if(is_null($val)) { throw new DokuCLI_Exception("Option $arg requires an argument", DokuCLI_Exception::E_OPT_ARG_REQUIRED); } $this->options[$opt] = $val; } else { $this->options[$opt] = true; } continue; } // short option $opt = substr($arg, 1); if(!isset($this->setup[$this->command]['short'][$opt])) { throw new DokuCLI_Exception("No such option $arg", DokuCLI_Exception::E_UNKNOWN_OPT); } else { $opt = $this->setup[$this->command]['short'][$opt]; // store it under long name } // argument required? if($this->setup[$this->command]['opts'][$opt]['needsarg']) { $val = null; if($i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) { $val = $this->args[++$i]; } if(is_null($val)) { throw new DokuCLI_Exception("Option $arg requires an argument", DokuCLI_Exception::E_OPT_ARG_REQUIRED); } $this->options[$opt] = $val; } else { $this->options[$opt] = true; } } // parsing is now done, update args array $this->args = $non_opts; // if not done yet, check if first argument is a command and reexecute argument parsing if it is if(!$this->command && $this->args && isset($this->setup[$this->args[0]])) { // it is a command! $this->command = array_shift($this->args); $this->parseOptions(); // second pass } } /** * Get the value of the given option * * Please note that all options are accessed by their long option names regardless of how they were * specified on commandline. * * Can only be used after parseOptions() has been run * * @param string $option * @param bool|string $default what to return if the option was not set * @return bool|string */ public function getOpt($option, $default = false) { if(isset($this->options[$option])) return $this->options[$option]; return $default; } /** * Return the found command if any * * @return string */ public function getCmd() { return $this->command; } /** * Builds a help screen from the available options. You may want to call it from -h or on error * * @return string */ public function help() { $text = ''; $hascommands = (count($this->setup) > 1); foreach($this->setup as $command => $config) { $hasopts = (bool) $this->setup[$command]['opts']; $hasargs = (bool) $this->setup[$command]['args']; if(!$command) { $text .= 'USAGE: '.$this->bin; } else { $text .= "\n$command"; } if($hasopts) $text .= ' <OPTIONS>'; foreach($this->setup[$command]['args'] as $arg) { if($arg['required']) { $text .= ' <'.$arg['name'].'>'; } else { $text .= ' [<'.$arg['name'].'>]'; } } $text .= "\n"; if($this->setup[$command]['help']) { $text .= "\n"; $text .= $this->tableFormat( array(2, 72), array('', $this->setup[$command]['help']."\n") ); } if($hasopts) { $text .= "\n OPTIONS\n\n"; foreach($this->setup[$command]['opts'] as $long => $opt) { $name = ''; if($opt['short']) { $name .= '-'.$opt['short']; if($opt['needsarg']) $name .= ' <'.$opt['needsarg'].'>'; $name .= ', '; } $name .= "--$long"; if($opt['needsarg']) $name .= ' <'.$opt['needsarg'].'>'; $text .= $this->tableFormat( array(2, 20, 52), array('', $name, $opt['help']) ); $text .= "\n"; } } if($hasargs) { $text .= "\n"; foreach($this->setup[$command]['args'] as $arg) { $name = '<'.$arg['name'].'>'; $text .= $this->tableFormat( array(2, 20, 52), array('', $name, $arg['help']) ); } } if($command == '' && $hascommands) { $text .= "\nThis tool accepts a command as first parameter as outlined below:\n"; } } return $text; } /** * Safely read the $argv PHP array across different PHP configurations. * Will take care on register_globals and register_argc_argv ini directives * * @throws DokuCLI_Exception * @return array the $argv PHP array or PEAR error if not registered */ private function readPHPArgv() { global $argv; if(!is_array($argv)) { if(!@is_array($_SERVER['argv'])) { if(!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { throw new DokuCLI_Exception( "Could not read cmd args (register_argc_argv=Off?)", DOKU_CLI_OPTS_ARG_READ ); } return $GLOBALS['HTTP_SERVER_VARS']['argv']; } return $_SERVER['argv']; } return $argv; } /** * Displays text in multiple word wrapped columns * * @param int[] $widths list of column widths (in characters) * @param string[] $texts list of texts for each column * @return string */ private function tableFormat($widths, $texts) { $wrapped = array(); $maxlen = 0; foreach($widths as $col => $width) { $wrapped[$col] = explode("\n", wordwrap($texts[$col], $width - 1, "\n", true)); // -1 char border $len = count($wrapped[$col]); if($len > $maxlen) $maxlen = $len; } $out = ''; for($i = 0; $i < $maxlen; $i++) { foreach($widths as $col => $width) { if(isset($wrapped[$col][$i])) { $val = $wrapped[$col][$i]; } else { $val = ''; } $out .= sprintf('%-'.$width.'s', $val); } $out .= "\n"; } return $out; } } /** * Class DokuCLI_Exception * * The code is used as exit code for the CLI tool. This should probably be extended. Many cases just fall back to the * E_ANY code. * * @author Andreas Gohr <andi@splitbrain.org> */ class DokuCLI_Exception extends Exception { const E_ANY = -1; // no error code specified const E_UNKNOWN_OPT = 1; //Unrecognized option const E_OPT_ARG_REQUIRED = 2; //Option requires argument const E_OPT_ARG_DENIED = 3; //Option not allowed argument const E_OPT_ABIGUOUS = 4; //Option abiguous const E_ARG_READ = 5; //Could not read argv /** * @param string $message The Exception message to throw. * @param int $code The Exception code * @param Exception $previous The previous exception used for the exception chaining. */ public function __construct($message = "", $code = 0, Exception $previous = null) { if(!$code) $code = DokuCLI_Exception::E_ANY; parent::__construct($message, $code, $previous); } } Manifest.php 0000644 00000004452 15233462216 0007034 0 ustar 00 <?php namespace dokuwiki; use dokuwiki\Extension\Event; class Manifest { public function sendManifest() { $manifest = retrieveConfig('manifest', 'jsonToArray'); global $conf; $manifest['scope'] = DOKU_REL; if (empty($manifest['name'])) { $manifest['name'] = $conf['title']; } if (empty($manifest['short_name'])) { $manifest['short_name'] = $conf['title']; } if (empty($manifest['description'])) { $manifest['description'] = $conf['tagline']; } if (empty($manifest['start_url'])) { $manifest['start_url'] = DOKU_REL; } $styleUtil = new \dokuwiki\StyleUtils(); $styleIni = $styleUtil->cssStyleini(); $replacements = $styleIni['replacements']; if (empty($manifest['background_color'])) { $manifest['background_color'] = $replacements['__background__']; } if (empty($manifest['theme_color'])) { $manifest['theme_color'] = !empty($replacements['__theme_color__']) ? $replacements['__theme_color__'] : $replacements['__background_alt__']; } if (empty($manifest['icons'])) { $manifest['icons'] = []; if (file_exists(mediaFN(':wiki:favicon.ico'))) { $url = ml(':wiki:favicon.ico', '', true, '', true); $manifest['icons'][] = [ 'src' => $url, 'sizes' => '16x16', ]; } $look = [ ':wiki:logo.svg', ':logo.svg', ':wiki:dokuwiki.svg' ]; foreach ($look as $svgLogo) { $svgLogoFN = mediaFN($svgLogo); if (file_exists($svgLogoFN)) { $url = ml($svgLogo, '', true, '', true); $manifest['icons'][] = [ 'src' => $url, 'sizes' => '17x17 512x512', 'type' => 'image/svg+xml', ]; break; }; } } Event::createAndTrigger('MANIFEST_SEND', $manifest); header('Content-Type: application/manifest+json'); echo json_encode($manifest); } } confutils.php 0000644 00000032722 15233462216 0007275 0 ustar 00 <?php /** * Utilities for collecting data from config files * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Harry Fuecks <hfuecks@gmail.com> */ /* * line prefix used to negate single value config items * (scheme.conf & stopwords.conf), e.g. * !gopher */ use dokuwiki\Extension\AuthPlugin; use dokuwiki\Extension\Event; const DOKU_CONF_NEGATION = '!'; /** * Returns the (known) extension and mimetype of a given filename * * If $knownonly is true (the default), then only known extensions * are returned. * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $file file name * @param bool $knownonly * @return array with extension, mimetype and if it should be downloaded */ function mimetype($file, $knownonly=true){ $mtypes = getMimeTypes(); // known mimetypes $ext = strrpos($file, '.'); if ($ext === false) { return array(false, false, false); } $ext = strtolower(substr($file, $ext + 1)); if (!isset($mtypes[$ext])){ if ($knownonly) { return array(false, false, false); } else { return array($ext, 'application/octet-stream', true); } } if($mtypes[$ext][0] == '!'){ return array($ext, substr($mtypes[$ext],1), true); }else{ return array($ext, $mtypes[$ext], false); } } /** * returns a hash of mimetypes * * @author Andreas Gohr <andi@splitbrain.org> */ function getMimeTypes() { static $mime = null; if ( !$mime ) { $mime = retrieveConfig('mime','confToHash'); $mime = array_filter($mime); } return $mime; } /** * returns a hash of acronyms * * @author Harry Fuecks <hfuecks@gmail.com> */ function getAcronyms() { static $acronyms = null; if ( !$acronyms ) { $acronyms = retrieveConfig('acronyms','confToHash'); $acronyms = array_filter($acronyms, 'strlen'); } return $acronyms; } /** * returns a hash of smileys * * @author Harry Fuecks <hfuecks@gmail.com> */ function getSmileys() { static $smileys = null; if ( !$smileys ) { $smileys = retrieveConfig('smileys','confToHash'); $smileys = array_filter($smileys, 'strlen'); } return $smileys; } /** * returns a hash of entities * * @author Harry Fuecks <hfuecks@gmail.com> */ function getEntities() { static $entities = null; if ( !$entities ) { $entities = retrieveConfig('entities','confToHash'); $entities = array_filter($entities, 'strlen'); } return $entities; } /** * returns a hash of interwikilinks * * @author Harry Fuecks <hfuecks@gmail.com> */ function getInterwiki() { static $wikis = null; if ( !$wikis ) { $wikis = retrieveConfig('interwiki','confToHash',array(true)); $wikis = array_filter($wikis, 'strlen'); //add sepecial case 'this' $wikis['this'] = DOKU_URL.'{NAME}'; } return $wikis; } /** * Returns the jquery script URLs for the versions defined in lib/scripts/jquery/versions * * @trigger CONFUTIL_CDN_SELECT * @return array */ function getCdnUrls() { global $conf; // load version info $versions = array(); $lines = file(DOKU_INC . 'lib/scripts/jquery/versions'); foreach($lines as $line) { $line = trim(preg_replace('/#.*$/', '', $line)); if($line === '') continue; list($key, $val) = explode('=', $line, 2); $key = trim($key); $val = trim($val); $versions[$key] = $val; } $src = array(); $data = array( 'versions' => $versions, 'src' => &$src ); $event = new Event('CONFUTIL_CDN_SELECT', $data); if($event->advise_before()) { if(!$conf['jquerycdn']) { $jqmod = md5(join('-', $versions)); $src[] = DOKU_BASE . 'lib/exe/jquery.php' . '?tseed=' . $jqmod; } elseif($conf['jquerycdn'] == 'jquery') { $src[] = sprintf('https://code.jquery.com/jquery-%s.min.js', $versions['JQ_VERSION']); $src[] = sprintf('https://code.jquery.com/ui/%s/jquery-ui.min.js', $versions['JQUI_VERSION']); } elseif($conf['jquerycdn'] == 'cdnjs') { $src[] = sprintf( 'https://cdnjs.cloudflare.com/ajax/libs/jquery/%s/jquery.min.js', $versions['JQ_VERSION'] ); $src[] = sprintf( 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/%s/jquery-ui.min.js', $versions['JQUI_VERSION'] ); } } $event->advise_after(); return $src; } /** * returns array of wordblock patterns * */ function getWordblocks() { static $wordblocks = null; if ( !$wordblocks ) { $wordblocks = retrieveConfig('wordblock','file',null,'array_merge_with_removal'); } return $wordblocks; } /** * Gets the list of configured schemes * * @return array the schemes */ function getSchemes() { static $schemes = null; if ( !$schemes ) { $schemes = retrieveConfig('scheme','file',null,'array_merge_with_removal'); $schemes = array_map('trim', $schemes); $schemes = preg_replace('/^#.*/', '', $schemes); $schemes = array_filter($schemes); } return $schemes; } /** * Builds a hash from an array of lines * * If $lower is set to true all hash keys are converted to * lower case. * * @author Harry Fuecks <hfuecks@gmail.com> * @author Andreas Gohr <andi@splitbrain.org> * @author Gina Haeussge <gina@foosel.net> * * @param array $lines * @param bool $lower * * @return array */ function linesToHash($lines, $lower = false) { $conf = array(); // remove BOM if(isset($lines[0]) && substr($lines[0], 0, 3) == pack('CCC', 0xef, 0xbb, 0xbf)) $lines[0] = substr($lines[0], 3); foreach($lines as $line) { //ignore comments (except escaped ones) $line = preg_replace('/(?<![&\\\\])#.*$/', '', $line); $line = str_replace('\\#', '#', $line); $line = trim($line); if($line === '') continue; $line = preg_split('/\s+/', $line, 2); $line = array_pad($line, 2, ''); // Build the associative array if($lower) { $conf[strtolower($line[0])] = $line[1]; } else { $conf[$line[0]] = $line[1]; } } return $conf; } /** * Builds a hash from a configfile * * If $lower is set to true all hash keys are converted to * lower case. * * @author Harry Fuecks <hfuecks@gmail.com> * @author Andreas Gohr <andi@splitbrain.org> * @author Gina Haeussge <gina@foosel.net> * * @param string $file * @param bool $lower * * @return array */ function confToHash($file,$lower=false) { $conf = array(); $lines = @file( $file ); if ( !$lines ) return $conf; return linesToHash($lines, $lower); } /** * Read a json config file into an array * * @param string $file * @return array */ function jsonToArray($file) { $json = file_get_contents($file); $conf = json_decode($json, true); if ($conf === null) { return []; } return $conf; } /** * Retrieve the requested configuration information * * @author Chris Smith <chris@jalakai.co.uk> * * @param string $type the configuration settings to be read, must correspond to a key/array in $config_cascade * @param callback $fn the function used to process the configuration file into an array * @param array $params optional additional params to pass to the callback * @param callback $combine the function used to combine arrays of values read from different configuration files; * the function takes two parameters, * $combined - the already read & merged configuration values * $new - array of config values from the config cascade file being currently processed * and returns an array of the merged configuration values. * @return array configuration values */ function retrieveConfig($type,$fn,$params=null,$combine='array_merge') { global $config_cascade; if(!is_array($params)) $params = array(); $combined = array(); if (!is_array($config_cascade[$type])) trigger_error('Missing config cascade for "'.$type.'"',E_USER_WARNING); foreach (array('default','local','protected') as $config_group) { if (empty($config_cascade[$type][$config_group])) continue; foreach ($config_cascade[$type][$config_group] as $file) { if (file_exists($file)) { $config = call_user_func_array($fn,array_merge(array($file),$params)); $combined = $combine($combined, $config); } } } return $combined; } /** * Include the requested configuration information * * @author Chris Smith <chris@jalakai.co.uk> * * @param string $type the configuration settings to be read, must correspond to a key/array in $config_cascade * @return array list of files, default before local before protected */ function getConfigFiles($type) { global $config_cascade; $files = array(); if (!is_array($config_cascade[$type])) trigger_error('Missing config cascade for "'.$type.'"',E_USER_WARNING); foreach (array('default','local','protected') as $config_group) { if (empty($config_cascade[$type][$config_group])) continue; $files = array_merge($files, $config_cascade[$type][$config_group]); } return $files; } /** * check if the given action was disabled in config * * @author Andreas Gohr <andi@splitbrain.org> * @param string $action * @returns boolean true if enabled, false if disabled */ function actionOK($action){ static $disabled = null; if(is_null($disabled) || defined('SIMPLE_TEST')){ global $conf; /** @var AuthPlugin $auth */ global $auth; // prepare disabled actions array and handle legacy options $disabled = explode(',',$conf['disableactions']); $disabled = array_map('trim',$disabled); if((isset($conf['openregister']) && !$conf['openregister']) || is_null($auth) || !$auth->canDo('addUser')) { $disabled[] = 'register'; } if((isset($conf['resendpasswd']) && !$conf['resendpasswd']) || is_null($auth) || !$auth->canDo('modPass')) { $disabled[] = 'resendpwd'; } if((isset($conf['subscribers']) && !$conf['subscribers']) || is_null($auth)) { $disabled[] = 'subscribe'; } if (is_null($auth) || !$auth->canDo('Profile')) { $disabled[] = 'profile'; } if (is_null($auth) || !$auth->canDo('delUser')) { $disabled[] = 'profile_delete'; } if (is_null($auth)) { $disabled[] = 'login'; } if (is_null($auth) || !$auth->canDo('logout')) { $disabled[] = 'logout'; } $disabled = array_unique($disabled); } return !in_array($action,$disabled); } /** * check if headings should be used as link text for the specified link type * * @author Chris Smith <chris@jalakai.co.uk> * * @param string $linktype 'content'|'navigation', content applies to links in wiki text * navigation applies to all other links * @return boolean true if headings should be used for $linktype, false otherwise */ function useHeading($linktype) { static $useHeading = null; if(defined('DOKU_UNITTEST')) $useHeading = null; // don't cache during unit tests if (is_null($useHeading)) { global $conf; if (!empty($conf['useheading'])) { switch ($conf['useheading']) { case 'content': $useHeading['content'] = true; break; case 'navigation': $useHeading['navigation'] = true; break; default: $useHeading['content'] = true; $useHeading['navigation'] = true; } } else { $useHeading = array(); } } return (!empty($useHeading[$linktype])); } /** * obscure config data so information isn't plain text * * @param string $str data to be encoded * @param string $code encoding method, values: plain, base64, uuencode. * @return string the encoded value */ function conf_encodeString($str,$code) { switch ($code) { case 'base64' : return '<b>'.base64_encode($str); case 'uuencode' : return '<u>'.convert_uuencode($str); case 'plain': default: return $str; } } /** * return obscured data as plain text * * @param string $str encoded data * @return string plain text */ function conf_decodeString($str) { switch (substr($str,0,3)) { case '<b>' : return base64_decode(substr($str,3)); case '<u>' : return convert_uudecode(substr($str,3)); default: // not encoded (or unknown) return $str; } } /** * array combination function to remove negated values (prefixed by !) * * @param array $current * @param array $new * * @return array the combined array, numeric keys reset */ function array_merge_with_removal($current, $new) { foreach ($new as $val) { if (substr($val,0,1) == DOKU_CONF_NEGATION) { $idx = array_search(trim(substr($val,1)),$current); if ($idx !== false) { unset($current[$idx]); } } else { $current[] = trim($val); } } return array_slice($current,0); } //Setup VIM: ex: et ts=4 : config_cascade.php 0000644 00000007143 15233462216 0010176 0 ustar 00 <?php /** * The default config cascade * * This array configures the default locations of various files in the * DokuWiki directory hierarchy. It can be overriden in inc/preload.php */ $config_cascade = array_merge( array( 'main' => array( 'default' => array(DOKU_CONF . 'dokuwiki.php'), 'local' => array(DOKU_CONF . 'local.php'), 'protected' => array(DOKU_CONF . 'local.protected.php'), ), 'acronyms' => array( 'default' => array(DOKU_CONF . 'acronyms.conf'), 'local' => array(DOKU_CONF . 'acronyms.local.conf'), ), 'entities' => array( 'default' => array(DOKU_CONF . 'entities.conf'), 'local' => array(DOKU_CONF . 'entities.local.conf'), ), 'interwiki' => array( 'default' => array(DOKU_CONF . 'interwiki.conf'), 'local' => array(DOKU_CONF . 'interwiki.local.conf'), ), 'license' => array( 'default' => array(DOKU_CONF . 'license.php'), 'local' => array(DOKU_CONF . 'license.local.php'), ), 'manifest' => array( 'default' => array(DOKU_CONF . 'manifest.json'), 'local' => array(DOKU_CONF . 'manifest.local.json'), ), 'mediameta' => array( 'default' => array(DOKU_CONF . 'mediameta.php'), 'local' => array(DOKU_CONF . 'mediameta.local.php'), ), 'mime' => array( 'default' => array(DOKU_CONF . 'mime.conf'), 'local' => array(DOKU_CONF . 'mime.local.conf'), ), 'scheme' => array( 'default' => array(DOKU_CONF . 'scheme.conf'), 'local' => array(DOKU_CONF . 'scheme.local.conf'), ), 'smileys' => array( 'default' => array(DOKU_CONF . 'smileys.conf'), 'local' => array(DOKU_CONF . 'smileys.local.conf'), ), 'wordblock' => array( 'default' => array(DOKU_CONF . 'wordblock.conf'), 'local' => array(DOKU_CONF . 'wordblock.local.conf'), ), 'userstyle' => array( 'screen' => array(DOKU_CONF . 'userstyle.css', DOKU_CONF . 'userstyle.less'), 'print' => array(DOKU_CONF . 'userprint.css', DOKU_CONF . 'userprint.less'), 'feed' => array(DOKU_CONF . 'userfeed.css', DOKU_CONF . 'userfeed.less'), 'all' => array(DOKU_CONF . 'userall.css', DOKU_CONF . 'userall.less') ), 'userscript' => array( 'default' => array(DOKU_CONF . 'userscript.js') ), 'styleini' => array( 'default' => array(DOKU_INC . 'lib/tpl/%TEMPLATE%/' . 'style.ini'), 'local' => array(DOKU_CONF . 'tpl/%TEMPLATE%/' . 'style.ini') ), 'acl' => array( 'default' => DOKU_CONF . 'acl.auth.php', ), 'plainauth.users' => array( 'default' => DOKU_CONF . 'users.auth.php', 'protected' => '' // not used by default ), 'plugins' => array( 'default' => array(DOKU_CONF . 'plugins.php'), 'local' => array(DOKU_CONF . 'plugins.local.php'), 'protected' => array( DOKU_CONF . 'plugins.required.php', DOKU_CONF . 'plugins.protected.php', ), ), 'lang' => array( 'core' => array(DOKU_CONF . 'lang/'), 'plugin' => array(DOKU_CONF . 'plugin_lang/'), 'template' => array(DOKU_CONF . 'template_lang/') ) ), $config_cascade ); Input/Get.php 0000644 00000001156 15233462216 0007102 0 ustar 00 <?php namespace dokuwiki\Input; /** * Internal class used for $_GET access in dokuwiki\Input\Input class */ class Get extends Input { /** @noinspection PhpMissingParentConstructorInspection * Initialize the $access array, remove subclass members */ public function __construct() { $this->access = &$_GET; } /** * Sets a parameter in $_GET and $_REQUEST * * @param string $name Parameter name * @param mixed $value Value to set */ public function set($name, $value) { parent::set($name, $value); $_REQUEST[$name] = $value; } } Input/Server.php 0000644 00000000546 15233462216 0007633 0 ustar 00 <?php namespace dokuwiki\Input; /** * Internal class used for $_SERVER access in dokuwiki\Input\Input class */ class Server extends Input { /** @noinspection PhpMissingParentConstructorInspection * Initialize the $access array, remove subclass members */ public function __construct() { $this->access = &$_SERVER; } } Input/Post.php 0000644 00000001163 15233462216 0007306 0 ustar 00 <?php namespace dokuwiki\Input; /** * Internal class used for $_POST access in dokuwiki\Input\Input class */ class Post extends Input { /** @noinspection PhpMissingParentConstructorInspection * Initialize the $access array, remove subclass members */ public function __construct() { $this->access = &$_POST; } /** * Sets a parameter in $_POST and $_REQUEST * * @param string $name Parameter name * @param mixed $value Value to set */ public function set($name, $value) { parent::set($name, $value); $_REQUEST[$name] = $value; } } Input/Input.php 0000644 00000021505 15233462216 0007462 0 ustar 00 <?php namespace dokuwiki\Input; /** * Encapsulates access to the $_REQUEST array, making sure used parameters are initialized and * have the correct type. * * All function access the $_REQUEST array by default, if you want to access $_POST or $_GET * explicitly use the $post and $get members. * * @author Andreas Gohr <andi@splitbrain.org> */ class Input { /** @var Post Access $_POST parameters */ public $post; /** @var Get Access $_GET parameters */ public $get; /** @var Server Access $_SERVER parameters */ public $server; protected $access; /** * @var Callable */ protected $filter; /** * Intilizes the dokuwiki\Input\Input class and it subcomponents */ public function __construct() { $this->access = &$_REQUEST; $this->post = new Post(); $this->get = new Get(); $this->server = new Server(); } /** * Apply the set filter to the given value * * @param string $data * @return string */ protected function applyfilter($data) { if (!$this->filter) return $data; return call_user_func($this->filter, $data); } /** * Return a filtered copy of the input object * * Expects a callable that accepts one string parameter and returns a filtered string * * @param Callable|string $filter * @return Input */ public function filter($filter = 'stripctl') { $this->filter = $filter; $clone = clone $this; $this->filter = ''; return $clone; } /** * Check if a parameter was set * * Basically a wrapper around isset. When called on the $post and $get subclasses, * the parameter is set to $_POST or $_GET and to $_REQUEST * * @see isset * @param string $name Parameter name * @return bool */ public function has($name) { return isset($this->access[$name]); } /** * Remove a parameter from the superglobals * * Basically a wrapper around unset. When NOT called on the $post and $get subclasses, * the parameter will also be removed from $_POST or $_GET * * @see isset * @param string $name Parameter name */ public function remove($name) { if (isset($this->access[$name])) { unset($this->access[$name]); } // also remove from sub classes if (isset($this->post) && isset($_POST[$name])) { unset($_POST[$name]); } if (isset($this->get) && isset($_GET[$name])) { unset($_GET[$name]); } } /** * Access a request parameter without any type conversion * * @param string $name Parameter name * @param mixed $default Default to return if parameter isn't set * @param bool $nonempty Return $default if parameter is set but empty() * @return mixed */ public function param($name, $default = null, $nonempty = false) { if (!isset($this->access[$name])) return $default; $value = $this->applyfilter($this->access[$name]); if ($nonempty && empty($value)) return $default; return $value; } /** * Sets a parameter * * @param string $name Parameter name * @param mixed $value Value to set */ public function set($name, $value) { $this->access[$name] = $value; } /** * Get a reference to a request parameter * * This avoids copying data in memory, when the parameter is not set it will be created * and intialized with the given $default value before a reference is returned * * @param string $name Parameter name * @param mixed $default If parameter is not set, initialize with this value * @param bool $nonempty Init with $default if parameter is set but empty() * @return mixed (reference) */ public function &ref($name, $default = '', $nonempty = false) { if (!isset($this->access[$name]) || ($nonempty && empty($this->access[$name]))) { $this->set($name, $default); } return $this->access[$name]; } /** * Access a request parameter as int * * @param string $name Parameter name * @param int $default Default to return if parameter isn't set or is an array * @param bool $nonempty Return $default if parameter is set but empty() * @return int */ public function int($name, $default = 0, $nonempty = false) { if (!isset($this->access[$name])) return $default; if (is_array($this->access[$name])) return $default; $value = $this->applyfilter($this->access[$name]); if ($value === '') return $default; if ($nonempty && empty($value)) return $default; return (int)$value; } /** * Access a request parameter as string * * @param string $name Parameter name * @param string $default Default to return if parameter isn't set or is an array * @param bool $nonempty Return $default if parameter is set but empty() * @return string */ public function str($name, $default = '', $nonempty = false) { if (!isset($this->access[$name])) return $default; if (is_array($this->access[$name])) return $default; $value = $this->applyfilter($this->access[$name]); if ($nonempty && empty($value)) return $default; return (string)$value; } /** * Access a request parameter and make sure it is has a valid value * * Please note that comparisons to the valid values are not done typesafe (request vars * are always strings) however the function will return the correct type from the $valids * array when an match was found. * * @param string $name Parameter name * @param array $valids Array of valid values * @param mixed $default Default to return if parameter isn't set or not valid * @return null|mixed */ public function valid($name, $valids, $default = null) { if (!isset($this->access[$name])) return $default; if (is_array($this->access[$name])) return $default; // we don't allow arrays $value = $this->applyfilter($this->access[$name]); $found = array_search($value, $valids); if ($found !== false) return $valids[$found]; // return the valid value for type safety return $default; } /** * Access a request parameter as bool * * Note: $nonempty is here for interface consistency and makes not much sense for booleans * * @param string $name Parameter name * @param mixed $default Default to return if parameter isn't set * @param bool $nonempty Return $default if parameter is set but empty() * @return bool */ public function bool($name, $default = false, $nonempty = false) { if (!isset($this->access[$name])) return $default; if (is_array($this->access[$name])) return $default; $value = $this->applyfilter($this->access[$name]); if ($value === '') return $default; if ($nonempty && empty($value)) return $default; return (bool)$value; } /** * Access a request parameter as array * * @param string $name Parameter name * @param mixed $default Default to return if parameter isn't set * @param bool $nonempty Return $default if parameter is set but empty() * @return array */ public function arr($name, $default = array(), $nonempty = false) { if (!isset($this->access[$name])) return $default; if (!is_array($this->access[$name])) return $default; if ($nonempty && empty($this->access[$name])) return $default; return (array)$this->access[$name]; } /** * Create a simple key from an array key * * This is useful to access keys where the information is given as an array key or as a single array value. * For example when the information was submitted as the name of a submit button. * * This function directly changes the access array. * * Eg. $_REQUEST['do']['save']='Speichern' becomes $_REQUEST['do'] = 'save' * * This function returns the $INPUT object itself for easy chaining * * @param string $name * @return Input */ public function extract($name) { if (!isset($this->access[$name])) return $this; if (!is_array($this->access[$name])) return $this; $keys = array_keys($this->access[$name]); if (!$keys) { // this was an empty array $this->remove($name); return $this; } // get the first key $value = array_shift($keys); if ($value === 0) { // we had a numeric array, assume the value is not in the key $value = array_shift($this->access[$name]); } $this->set($name, $value); return $this; } } deprecated.php 0000644 00000041537 15233462216 0007373 0 ustar 00 <?php // phpcs:ignoreFile -- this file violates PSR2 by definition /** * These classes and functions are deprecated and will be removed in future releases */ use dokuwiki\Debug\DebugHelper; use dokuwiki\Subscriptions\BulkSubscriptionSender; use dokuwiki\Subscriptions\MediaSubscriptionSender; use dokuwiki\Subscriptions\PageSubscriptionSender; use dokuwiki\Subscriptions\RegistrationSubscriptionSender; use dokuwiki\Subscriptions\SubscriberManager; /** * @inheritdoc * @deprecated 2018-05-07 */ class RemoteAccessDeniedException extends \dokuwiki\Remote\AccessDeniedException { /** @inheritdoc */ public function __construct($message = "", $code = 0, Throwable $previous = null) { dbg_deprecated(\dokuwiki\Remote\AccessDeniedException::class); parent::__construct($message, $code, $previous); } } /** * @inheritdoc * @deprecated 2018-05-07 */ class RemoteException extends \dokuwiki\Remote\RemoteException { /** @inheritdoc */ public function __construct($message = "", $code = 0, Throwable $previous = null) { dbg_deprecated(\dokuwiki\Remote\RemoteException::class); parent::__construct($message, $code, $previous); } } /** * Escapes regex characters other than (, ) and / * * @param string $str * @return string * @deprecated 2018-05-04 */ function Doku_Lexer_Escape($str) { dbg_deprecated('\\dokuwiki\\Parsing\\Lexer\\Lexer::escape()'); return \dokuwiki\Parsing\Lexer\Lexer::escape($str); } /** * @inheritdoc * @deprecated 2018-06-01 */ class setting extends \dokuwiki\plugin\config\core\Setting\Setting { /** @inheritdoc */ public function __construct($key, array $params = null) { dbg_deprecated(\dokuwiki\plugin\config\core\Setting\Setting::class); parent::__construct($key, $params); } } /** * @inheritdoc * @deprecated 2018-06-01 */ class setting_authtype extends \dokuwiki\plugin\config\core\Setting\SettingAuthtype { /** @inheritdoc */ public function __construct($key, array $params = null) { dbg_deprecated(\dokuwiki\plugin\config\core\Setting\SettingAuthtype::class); parent::__construct($key, $params); } } /** * @inheritdoc * @deprecated 2018-06-01 */ class setting_string extends \dokuwiki\plugin\config\core\Setting\SettingString { /** @inheritdoc */ public function __construct($key, array $params = null) { dbg_deprecated(\dokuwiki\plugin\config\core\Setting\SettingString::class); parent::__construct($key, $params); } } /** * @inheritdoc * @deprecated 2018-06-15 */ class PageChangelog extends \dokuwiki\ChangeLog\PageChangeLog { /** @inheritdoc */ public function __construct($id, $chunk_size = 8192) { dbg_deprecated(\dokuwiki\ChangeLog\PageChangeLog::class); parent::__construct($id, $chunk_size); } } /** * @inheritdoc * @deprecated 2018-06-15 */ class MediaChangelog extends \dokuwiki\ChangeLog\MediaChangeLog { /** @inheritdoc */ public function __construct($id, $chunk_size = 8192) { dbg_deprecated(\dokuwiki\ChangeLog\MediaChangeLog::class); parent::__construct($id, $chunk_size); } } /** Behavior switch for JSON::decode() */ define('JSON_LOOSE_TYPE', 16); /** Behavior switch for JSON::decode() */ define('JSON_STRICT_TYPE', 0); /** * Encode/Decode JSON * @deprecated 2018-07-27 */ class JSON { protected $use = 0; /** * @param int $use JSON_*_TYPE flag * @deprecated 2018-07-27 */ public function __construct($use = JSON_STRICT_TYPE) { $this->use = $use; } /** * Encode given structure to JSON * * @param mixed $var * @return string * @deprecated 2018-07-27 */ public function encode($var) { dbg_deprecated('json_encode'); return json_encode($var); } /** * Alias for encode() * @param $var * @return string * @deprecated 2018-07-27 */ public function enc($var) { return $this->encode($var); } /** * Decode given string from JSON * * @param string $str * @return mixed * @deprecated 2018-07-27 */ public function decode($str) { dbg_deprecated('json_encode'); return json_decode($str, ($this->use == JSON_LOOSE_TYPE)); } /** * Alias for decode * * @param $str * @return mixed * @deprecated 2018-07-27 */ public function dec($str) { return $this->decode($str); } } /** * @inheritdoc * @deprecated 2019-02-19 */ class Input extends \dokuwiki\Input\Input { /** * @inheritdoc * @deprecated 2019-02-19 */ public function __construct() { dbg_deprecated(\dokuwiki\Input\Input::class); parent::__construct(); } } /** * @inheritdoc * @deprecated 2019-02-19 */ class PostInput extends \dokuwiki\Input\Post { /** * @inheritdoc * @deprecated 2019-02-19 */ public function __construct() { dbg_deprecated(\dokuwiki\Input\Post::class); parent::__construct(); } } /** * @inheritdoc * @deprecated 2019-02-19 */ class GetInput extends \dokuwiki\Input\Get { /** * @inheritdoc * @deprecated 2019-02-19 */ public function __construct() { dbg_deprecated(\dokuwiki\Input\Get::class); parent::__construct(); } } /** * @inheritdoc * @deprecated 2019-02-19 */ class ServerInput extends \dokuwiki\Input\Server { /** * @inheritdoc * @deprecated 2019-02-19 */ public function __construct() { dbg_deprecated(\dokuwiki\Input\Server::class); parent::__construct(); } } /** * @inheritdoc * @deprecated 2019-03-06 */ class PassHash extends \dokuwiki\PassHash { /** * @inheritdoc * @deprecated 2019-03-06 */ public function __construct() { dbg_deprecated(\dokuwiki\PassHash::class); } } /** * @deprecated since 2019-03-17 use \dokuwiki\HTTP\HTTPClientException instead! */ class HTTPClientException extends \dokuwiki\HTTP\HTTPClientException { /** * @inheritdoc * @deprecated 2019-03-17 */ public function __construct($message = '', $code = 0, $previous = null) { DebugHelper::dbgDeprecatedFunction(dokuwiki\HTTP\HTTPClientException::class); parent::__construct($message, $code, $previous); } } /** * @deprecated since 2019-03-17 use \dokuwiki\HTTP\HTTPClient instead! */ class HTTPClient extends \dokuwiki\HTTP\HTTPClient { /** * @inheritdoc * @deprecated 2019-03-17 */ public function __construct() { DebugHelper::dbgDeprecatedFunction(dokuwiki\HTTP\HTTPClient::class); parent::__construct(); } } /** * @deprecated since 2019-03-17 use \dokuwiki\HTTP\DokuHTTPClient instead! */ class DokuHTTPClient extends \dokuwiki\HTTP\DokuHTTPClient { /** * @inheritdoc * @deprecated 2019-03-17 */ public function __construct() { DebugHelper::dbgDeprecatedFunction(dokuwiki\HTTP\DokuHTTPClient::class); parent::__construct(); } } /** * function wrapper to process (create, trigger and destroy) an event * * @param string $name name for the event * @param mixed $data event data * @param callback $action (optional, default=NULL) default action, a php callback function * @param bool $canPreventDefault (optional, default=true) can hooks prevent the default action * * @return mixed the event results value after all event processing is complete * by default this is the return value of the default action however * it can be set or modified by event handler hooks * @deprecated 2018-06-15 */ function trigger_event($name, &$data, $action=null, $canPreventDefault=true) { dbg_deprecated('\dokuwiki\Extension\Event::createAndTrigger'); return \dokuwiki\Extension\Event::createAndTrigger($name, $data, $action, $canPreventDefault); } /** * @inheritdoc * @deprecated 2018-06-15 */ class Doku_Plugin_Controller extends \dokuwiki\Extension\PluginController { /** @inheritdoc */ public function __construct() { dbg_deprecated(\dokuwiki\Extension\PluginController::class); parent::__construct(); } } /** * Class for handling (email) subscriptions * * @author Adrian Lang <lang@cosmocode.de> * @author Andreas Gohr <andi@splitbrain.org> * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * * @deprecated 2019-04-22 Use the classes in the \dokuwiki\Subscriptions namespace instead! */ class Subscription { /** * Check if subscription system is enabled * * @return bool * * @deprecated 2019-04-20 \dokuwiki\Subscriptions\SubscriberManager::isenabled */ public function isenabled() { DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\SubscriberManager::isenabled'); $subscriberManager = new SubscriberManager(); return $subscriberManager->isenabled(); } /** * Recursively search for matching subscriptions * * This function searches all relevant subscription files for a page or * namespace. * * @author Adrian Lang <lang@cosmocode.de> * * @param string $page The target object’s (namespace or page) id * @param string|array $user * @param string|array $style * @param string|array $data * @return array * * @deprecated 2019-04-20 \dokuwiki\Subscriptions\SubscriberManager::subscribers */ public function subscribers($page, $user = null, $style = null, $data = null) { DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\SubscriberManager::subscribers'); $manager = new SubscriberManager(); return $manager->subscribers($page, $user, $style, $data); } /** * Adds a new subscription for the given page or namespace * * This will automatically overwrite any existent subscription for the given user on this * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces. * * @param string $id The target page or namespace, specified by id; Namespaces * are identified by appending a colon. * @param string $user * @param string $style * @param string $data * @throws Exception when user or style is empty * @return bool * * @deprecated 2019-04-20 \dokuwiki\Subscriptions\SubscriberManager::add */ public function add($id, $user, $style, $data = '') { DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\SubscriberManager::add'); $manager = new SubscriberManager(); return $manager->add($id, $user, $style, $data); } /** * Removes a subscription for the given page or namespace * * This removes all subscriptions matching the given criteria on the given page or * namespace. It will *not* modify any subscriptions that may exist in higher * namespaces. * * @param string $id The target object’s (namespace or page) id * @param string|array $user * @param string|array $style * @param string|array $data * @return bool * * @deprecated 2019-04-20 \dokuwiki\Subscriptions\SubscriberManager::remove */ public function remove($id, $user = null, $style = null, $data = null) { DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\SubscriberManager::remove'); $manager = new SubscriberManager(); return $manager->remove($id, $user, $style, $data); } /** * Get data for $INFO['subscribed'] * * $INFO['subscribed'] is either false if no subscription for the current page * and user is in effect. Else it contains an array of arrays with the fields * “target”, “style”, and optionally “data”. * * @param string $id Page ID, defaults to global $ID * @param string $user User, defaults to $_SERVER['REMOTE_USER'] * @return array|false * @author Adrian Lang <lang@cosmocode.de> * * @deprecated 2019-04-20 \dokuwiki\Subscriptions\SubscriberManager::userSubscription */ public function user_subscription($id = '', $user = '') { DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\SubscriberManager::userSubscription'); $manager = new SubscriberManager(); return $manager->userSubscription($id, $user); } /** * Send digest and list subscriptions * * This sends mails to all subscribers that have a subscription for namespaces above * the given page if the needed $conf['subscribe_time'] has passed already. * * This function is called form lib/exe/indexer.php * * @param string $page * @return int number of sent mails * * @deprecated 2019-04-20 \dokuwiki\Subscriptions\BulkSubscriptionSender::sendBulk */ public function send_bulk($page) { DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\BulkSubscriptionSender::sendBulk'); $subscriptionSender = new BulkSubscriptionSender(); return $subscriptionSender->sendBulk($page); } /** * Send the diff for some page change * * @param string $subscriber_mail The target mail address * @param string $template Mail template ('subscr_digest', 'subscr_single', 'mailtext', ...) * @param string $id Page for which the notification is * @param int|null $rev Old revision if any * @param string $summary Change summary if any * @return bool true if successfully sent * * @deprecated 2019-04-20 \dokuwiki\Subscriptions\PageSubscriptionSender::sendPageDiff */ public function send_diff($subscriber_mail, $template, $id, $rev = null, $summary = '') { DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\PageSubscriptionSender::sendPageDiff'); $subscriptionSender = new PageSubscriptionSender(); return $subscriptionSender->sendPageDiff($subscriber_mail, $template, $id, $rev, $summary); } /** * Send the diff for some media change * * @fixme this should embed thumbnails of images in HTML version * * @param string $subscriber_mail The target mail address * @param string $template Mail template ('uploadmail', ...) * @param string $id Media file for which the notification is * @param int|bool $rev Old revision if any * * @deprecated 2019-04-20 \dokuwiki\Subscriptions\MediaSubscriptionSender::sendMediaDiff */ public function send_media_diff($subscriber_mail, $template, $id, $rev = false) { DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\MediaSubscriptionSender::sendMediaDiff'); $subscriptionSender = new MediaSubscriptionSender(); return $subscriptionSender->sendMediaDiff($subscriber_mail, $template, $id, $rev); } /** * Send a notify mail on new registration * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $login login name of the new user * @param string $fullname full name of the new user * @param string $email email address of the new user * @return bool true if a mail was sent * * @deprecated 2019-04-20 \dokuwiki\Subscriptions\RegistrationSubscriptionSender::sendRegister */ public function send_register($login, $fullname, $email) { DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\RegistrationSubscriptionSender::sendRegister'); $subscriptionSender = new RegistrationSubscriptionSender(); return $subscriptionSender->sendRegister($login, $fullname, $email); } /** * Default callback for COMMON_NOTIFY_ADDRESSLIST * * Aggregates all email addresses of user who have subscribed the given page with 'every' style * * @author Steven Danz <steven-danz@kc.rr.com> * @author Adrian Lang <lang@cosmocode.de> * * @todo move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead, * use an array for the addresses within it * * @param array &$data Containing the entries: * - $id (the page id), * - $self (whether the author should be notified, * - $addresslist (current email address list) * - $replacements (array of additional string substitutions, @KEY@ to be replaced by value) * * @deprecated 2019-04-20 \dokuwiki\Subscriptions\SubscriberManager::notifyAddresses */ public function notifyaddresses(&$data) { DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\SubscriberManager::notifyAddresses'); $manager = new SubscriberManager(); $manager->notifyAddresses($data); } } /** * @deprecated 2019-12-29 use \dokuwiki\Search\Indexer */ class Doku_Indexer extends \dokuwiki\Search\Indexer {}; Sitemap/Mapper.php 0000644 00000012173 15233462216 0010113 0 ustar 00 <?php /** * Sitemap handling functions * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Michael Hamann <michael@content-space.de> */ namespace dokuwiki\Sitemap; use dokuwiki\HTTP\DokuHTTPClient; /** * A class for building sitemaps and pinging search engines with the sitemap URL. * * @author Michael Hamann */ class Mapper { /** * Builds a Google Sitemap of all public pages known to the indexer * * The map is placed in the cache directory named sitemap.xml.gz - This * file needs to be writable! * * @author Michael Hamann * @author Andreas Gohr * @link https://www.google.com/webmasters/sitemaps/docs/en/about.html * @link http://www.sitemaps.org/ * * @return bool */ public static function generate(){ global $conf; if($conf['sitemap'] < 1 || !is_numeric($conf['sitemap'])) return false; $sitemap = Mapper::getFilePath(); if(file_exists($sitemap)){ if(!is_writable($sitemap)) return false; }else{ if(!is_writable(dirname($sitemap))) return false; } if(@filesize($sitemap) && @filemtime($sitemap) > (time()-($conf['sitemap']*86400))){ // 60*60*24=86400 dbglog('Sitemapper::generate(): Sitemap up to date'); return false; } dbglog("Sitemapper::generate(): using $sitemap"); $pages = idx_get_indexer()->getPages(); dbglog('Sitemapper::generate(): creating sitemap using '.count($pages).' pages'); $items = array(); // build the sitemap items foreach($pages as $id){ //skip hidden, non existing and restricted files if(isHiddenPage($id)) continue; if(auth_aclcheck($id,'',array()) < AUTH_READ) continue; $item = Item::createFromID($id); if ($item !== null) $items[] = $item; } $eventData = array('items' => &$items, 'sitemap' => &$sitemap); $event = new \dokuwiki\Extension\Event('SITEMAP_GENERATE', $eventData); if ($event->advise_before(true)) { //save the new sitemap $event->result = io_saveFile($sitemap, Mapper::getXML($items)); } $event->advise_after(); return $event->result; } /** * Builds the sitemap XML string from the given array auf SitemapItems. * * @param $items array The SitemapItems that shall be included in the sitemap. * @return string The sitemap XML. * * @author Michael Hamann */ private static function getXML($items) { ob_start(); echo '<?xml version="1.0" encoding="UTF-8"?>'.NL; echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'.NL; foreach ($items as $item) { /** @var Item $item */ echo $item->toXML(); } echo '</urlset>'.NL; $result = ob_get_contents(); ob_end_clean(); return $result; } /** * Helper function for getting the path to the sitemap file. * * @return string The path to the sitemap file. * * @author Michael Hamann */ public static function getFilePath() { global $conf; $sitemap = $conf['cachedir'].'/sitemap.xml'; if (self::sitemapIsCompressed()) { $sitemap .= '.gz'; } return $sitemap; } /** * Helper function for checking if the sitemap is compressed * * @return bool If the sitemap file is compressed */ public static function sitemapIsCompressed() { global $conf; return $conf['compression'] === 'bz2' || $conf['compression'] === 'gz'; } /** * Pings search engines with the sitemap url. Plugins can add or remove * urls to ping using the SITEMAP_PING event. * * @author Michael Hamann * * @return bool */ public static function pingSearchEngines() { //ping search engines... $http = new DokuHTTPClient(); $http->timeout = 8; $encoded_sitemap_url = urlencode(wl('', array('do' => 'sitemap'), true, '&')); $ping_urls = array( 'google' => 'http://www.google.com/webmasters/sitemaps/ping?sitemap='.$encoded_sitemap_url, 'microsoft' => 'http://www.bing.com/webmaster/ping.aspx?siteMap='.$encoded_sitemap_url, 'yandex' => 'http://blogs.yandex.ru/pings/?status=success&url='.$encoded_sitemap_url ); $data = array('ping_urls' => $ping_urls, 'encoded_sitemap_url' => $encoded_sitemap_url ); $event = new \dokuwiki\Extension\Event('SITEMAP_PING', $data); if ($event->advise_before(true)) { foreach ($data['ping_urls'] as $name => $url) { dbglog("Sitemapper::PingSearchEngines(): pinging $name"); $resp = $http->get($url); if($http->error) dbglog("Sitemapper:pingSearchengines(): $http->error"); dbglog('Sitemapper:pingSearchengines(): '.preg_replace('/[\n\r]/',' ',strip_tags($resp))); } } $event->advise_after(); return true; } } Sitemap/Item.php 0000644 00000004515 15233462216 0007566 0 ustar 00 <?php namespace dokuwiki\Sitemap; /** * An item of a sitemap. * * @author Michael Hamann */ class Item { public $url; public $lastmod; public $changefreq; public $priority; /** * Create a new item. * * @param string $url The url of the item * @param int $lastmod Timestamp of the last modification * @param string $changefreq How frequently the item is likely to change. * Valid values: always, hourly, daily, weekly, monthly, yearly, never. * @param $priority float|string The priority of the item relative to other URLs on your site. * Valid values range from 0.0 to 1.0. */ public function __construct($url, $lastmod, $changefreq = null, $priority = null) { $this->url = $url; $this->lastmod = $lastmod; $this->changefreq = $changefreq; $this->priority = $priority; } /** * Helper function for creating an item for a wikipage id. * * @param string $id A wikipage id. * @param string $changefreq How frequently the item is likely to change. * Valid values: always, hourly, daily, weekly, monthly, yearly, never. * @param float|string $priority The priority of the item relative to other URLs on your site. * Valid values range from 0.0 to 1.0. * @return Item The sitemap item. */ public static function createFromID($id, $changefreq = null, $priority = null) { $id = trim($id); $date = @filemtime(wikiFN($id)); if(!$date) return null; return new Item(wl($id, '', true), $date, $changefreq, $priority); } /** * Get the XML representation of the sitemap item. * * @return string The XML representation. */ public function toXML() { $result = ' <url>'.NL .' <loc>'.hsc($this->url).'</loc>'.NL .' <lastmod>'.date_iso8601($this->lastmod).'</lastmod>'.NL; if ($this->changefreq !== null) $result .= ' <changefreq>'.hsc($this->changefreq).'</changefreq>'.NL; if ($this->priority !== null) $result .= ' <priority>'.hsc($this->priority).'</priority>'.NL; $result .= ' </url>'.NL; return $result; } } pluginutils.php 0000644 00000010252 15233462216 0007640 0 ustar 00 <?php /** * Utilities for handling plugins * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ // plugin related constants use dokuwiki\Extension\AdminPlugin; use dokuwiki\Extension\PluginController; use dokuwiki\Extension\PluginInterface; if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); // note that only [a-z0-9]+ is officially supported, // this is only to support plugins that don't follow these conventions, too if(!defined('DOKU_PLUGIN_NAME_REGEX')) define('DOKU_PLUGIN_NAME_REGEX', '[a-zA-Z0-9\x7f-\xff]+'); /** * Original plugin functions, remain for backwards compatibility */ /** * Return list of available plugins * * @param string $type type of plugins; empty string for all * @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) { /** @var $plugin_controller PluginController */ global $plugin_controller; $plugins = $plugin_controller->getList($type,$all); sort($plugins, SORT_NATURAL|SORT_FLAG_CASE); return $plugins; } /** * Returns plugin object * Returns only new instances of a plugin when $new is true or if plugin is not Singleton, * otherwise an already loaded instance. * * @param $type string type of plugin to load * @param $name string name of the plugin to load * @param $new bool true to return a new instance of the plugin, false to use an already loaded instance * @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) { /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->load($type,$name,$new,$disabled); } /** * Whether plugin is disabled * * @param string $plugin name of plugin * @return bool true disabled, false enabled */ function plugin_isdisabled($plugin) { /** @var $plugin_controller PluginController */ global $plugin_controller; return !$plugin_controller->isEnabled($plugin); } /** * Enable the plugin * * @param string $plugin name of plugin * @return bool true saving succeed, false saving failed */ function plugin_enable($plugin) { /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->enable($plugin); } /** * Disable the plugin * * @param string $plugin name of plugin * @return bool true saving succeed, false saving failed */ function plugin_disable($plugin) { /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->disable($plugin); } /** * Returns directory name of plugin * * @param string $plugin name of plugin * @return string name of directory * @deprecated 2018-07-20 */ function plugin_directory($plugin) { dbg_deprecated('$plugin directly'); return $plugin; } /** * Returns cascade of the config files * * @return array with arrays of plugin configs */ function plugin_getcascade() { /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->getCascade(); } /** * Return the currently operating admin plugin or null * if not on an admin plugin page * * @return Doku_Plugin_Admin */ function plugin_getRequestAdminPlugin() { static $admin_plugin = false; global $ACT,$INPUT,$INFO; if ($admin_plugin === false) { if (($ACT == 'admin') && ($page = $INPUT->str('page', '', true)) != '') { $pluginlist = plugin_list('admin'); if (in_array($page, $pluginlist)) { // attempt to load the plugin /** @var $admin_plugin AdminPlugin */ $admin_plugin = plugin_load('admin', $page); // verify if ($admin_plugin && !$admin_plugin->isAccessibleByCurrentUser()) { $admin_plugin = null; $INPUT->remove('page'); msg('For admins only',-1); } } } } return $admin_plugin; } Search/Indexer.php 0000644 00000122515 15233462216 0010072 0 ustar 00 <?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; } } parser/renderer.php 0000644 00000054261 15233462216 0010373 0 ustar 00 <?php /** * Renderer output base class * * @author Harry Fuecks <hfuecks@gmail.com> * @author Andreas Gohr <andi@splitbrain.org> */ use dokuwiki\Extension\Plugin; use dokuwiki\Extension\SyntaxPlugin; /** * Allowed chars in $language for code highlighting * @see GeSHi::set_language() */ define('PREG_PATTERN_VALID_LANGUAGE', '#[^a-zA-Z0-9\-_]#'); /** * An empty renderer, produces no output * * Inherits from dokuwiki\Plugin\DokuWiki_Plugin for giving additional functions to render plugins * * The renderer transforms the syntax instructions created by the parser and handler into the * desired output format. For each instruction a corresponding method defined in this class will * be called. That method needs to produce the desired output for the instruction and add it to the * $doc field. When all instructions are processed, the $doc field contents will be cached by * DokuWiki and sent to the user. */ abstract class Doku_Renderer extends Plugin { /** @var array Settings, control the behavior of the renderer */ public $info = array( 'cache' => true, // may the rendered result cached? 'toc' => true, // render the TOC? ); /** @var array contains the smiley configuration, set in p_render() */ public $smileys = array(); /** @var array contains the entity configuration, set in p_render() */ public $entities = array(); /** @var array contains the acronym configuration, set in p_render() */ public $acronyms = array(); /** @var array contains the interwiki configuration, set in p_render() */ public $interwiki = array(); /** @var array the list of headers used to create unique link ids */ protected $headers = array(); /** * @var string the rendered document, this will be cached after the renderer ran through */ public $doc = ''; /** * clean out any per-use values * * This is called before each use of the renderer object and should be used to * completely reset the state of the renderer to be reused for a new document */ public function reset(){ $this->headers = array(); $this->doc = ''; $this->info['cache'] = true; $this->info['toc'] = true; } /** * Allow the plugin to prevent DokuWiki from reusing an instance * * Since most renderer plugins fail to implement Doku_Renderer::reset() we default * to reinstantiating the renderer here * * @return bool false if the plugin has to be instantiated */ public function isSingleton() { return false; } /** * Returns the format produced by this renderer. * * Has to be overidden by sub classes * * @return string */ abstract public function getFormat(); /** * Disable caching of this renderer's output */ public function nocache() { $this->info['cache'] = false; } /** * Disable TOC generation for this renderer's output * * This might not be used for certain sub renderer */ public function notoc() { $this->info['toc'] = false; } /** * Handle plugin rendering * * Most likely this needs NOT to be overwritten by sub classes * * @param string $name Plugin name * @param mixed $data custom data set by handler * @param string $state matched state if any * @param string $match raw matched syntax */ public function plugin($name, $data, $state = '', $match = '') { /** @var SyntaxPlugin $plugin */ $plugin = plugin_load('syntax', $name); if($plugin != null) { $plugin->render($this->getFormat(), $this, $data); } } /** * handle nested render instructions * this method (and nest_close method) should not be overloaded in actual renderer output classes * * @param array $instructions */ public function nest($instructions) { foreach($instructions as $instruction) { // execute the callback against ourself if(method_exists($this, $instruction[0])) { call_user_func_array(array($this, $instruction[0]), $instruction[1] ? $instruction[1] : array()); } } } /** * dummy closing instruction issued by Doku_Handler_Nest * * normally the syntax mode should override this instruction when instantiating Doku_Handler_Nest - * however plugins will not be able to - as their instructions require data. */ public function nest_close() { } #region Syntax modes - sub classes will need to implement them to fill $doc /** * Initialize the document */ public function document_start() { } /** * Finalize the document */ public function document_end() { } /** * Render the Table of Contents * * @return string */ public function render_TOC() { return ''; } /** * Add an item to the TOC * * @param string $id the hash link * @param string $text the text to display * @param int $level the nesting level */ public function toc_additem($id, $text, $level) { } /** * Render a heading * * @param string $text the text to display * @param int $level header level * @param int $pos byte position in the original source */ public function header($text, $level, $pos) { } /** * Open a new section * * @param int $level section level (as determined by the previous header) */ public function section_open($level) { } /** * Close the current section */ public function section_close() { } /** * Render plain text data * * @param string $text */ public function cdata($text) { } /** * Open a paragraph */ public function p_open() { } /** * Close a paragraph */ public function p_close() { } /** * Create a line break */ public function linebreak() { } /** * Create a horizontal line */ public function hr() { } /** * Start strong (bold) formatting */ public function strong_open() { } /** * Stop strong (bold) formatting */ public function strong_close() { } /** * Start emphasis (italics) formatting */ public function emphasis_open() { } /** * Stop emphasis (italics) formatting */ public function emphasis_close() { } /** * Start underline formatting */ public function underline_open() { } /** * Stop underline formatting */ public function underline_close() { } /** * Start monospace formatting */ public function monospace_open() { } /** * Stop monospace formatting */ public function monospace_close() { } /** * Start a subscript */ public function subscript_open() { } /** * Stop a subscript */ public function subscript_close() { } /** * Start a superscript */ public function superscript_open() { } /** * Stop a superscript */ public function superscript_close() { } /** * Start deleted (strike-through) formatting */ public function deleted_open() { } /** * Stop deleted (strike-through) formatting */ public function deleted_close() { } /** * Start a footnote */ public function footnote_open() { } /** * Stop a footnote */ public function footnote_close() { } /** * Open an unordered list */ public function listu_open() { } /** * Close an unordered list */ public function listu_close() { } /** * Open an ordered list */ public function listo_open() { } /** * Close an ordered list */ public function listo_close() { } /** * Open a list item * * @param int $level the nesting level * @param bool $node true when a node; false when a leaf */ public function listitem_open($level,$node=false) { } /** * Close a list item */ public function listitem_close() { } /** * Start the content of a list item */ public function listcontent_open() { } /** * Stop the content of a list item */ public function listcontent_close() { } /** * Output unformatted $text * * Defaults to $this->cdata() * * @param string $text */ public function unformatted($text) { $this->cdata($text); } /** * Output inline PHP code * * If $conf['phpok'] is true this should evaluate the given code and append the result * to $doc * * @param string $text The PHP code */ public function php($text) { } /** * Output block level PHP code * * If $conf['phpok'] is true this should evaluate the given code and append the result * to $doc * * @param string $text The PHP code */ public function phpblock($text) { } /** * Output raw inline HTML * * If $conf['htmlok'] is true this should add the code as is to $doc * * @param string $text The HTML */ public function html($text) { } /** * Output raw block-level HTML * * If $conf['htmlok'] is true this should add the code as is to $doc * * @param string $text The HTML */ public function htmlblock($text) { } /** * Output preformatted text * * @param string $text */ public function preformatted($text) { } /** * Start a block quote */ public function quote_open() { } /** * Stop a block quote */ public function quote_close() { } /** * Display text as file content, optionally syntax highlighted * * @param string $text text to show * @param string $lang programming language to use for syntax highlighting * @param string $file file path label */ public function file($text, $lang = null, $file = null) { } /** * Display text as code content, optionally syntax highlighted * * @param string $text text to show * @param string $lang programming language to use for syntax highlighting * @param string $file file path label */ public function code($text, $lang = null, $file = null) { } /** * Format an acronym * * Uses $this->acronyms * * @param string $acronym */ public function acronym($acronym) { } /** * Format a smiley * * Uses $this->smiley * * @param string $smiley */ public function smiley($smiley) { } /** * Format an entity * * Entities are basically small text replacements * * Uses $this->entities * * @param string $entity */ public function entity($entity) { } /** * Typographically format a multiply sign * * Example: ($x=640, $y=480) should result in "640×480" * * @param string|int $x first value * @param string|int $y second value */ public function multiplyentity($x, $y) { } /** * Render an opening single quote char (language specific) */ public function singlequoteopening() { } /** * Render a closing single quote char (language specific) */ public function singlequoteclosing() { } /** * Render an apostrophe char (language specific) */ public function apostrophe() { } /** * Render an opening double quote char (language specific) */ public function doublequoteopening() { } /** * Render an closinging double quote char (language specific) */ public function doublequoteclosing() { } /** * Render a CamelCase link * * @param string $link The link name * @see http://en.wikipedia.org/wiki/CamelCase */ public function camelcaselink($link) { } /** * Render a page local link * * @param string $hash hash link identifier * @param string $name name for the link */ public function locallink($hash, $name = null) { } /** * Render a wiki internal link * * @param string $link page ID to link to. eg. 'wiki:syntax' * @param string|array $title name for the link, array for media file */ public function internallink($link, $title = null) { } /** * Render an external link * * @param string $link full URL with scheme * @param string|array $title name for the link, array for media file */ public function externallink($link, $title = null) { } /** * Render the output of an RSS feed * * @param string $url URL of the feed * @param array $params Finetuning of the output */ public function rss($url, $params) { } /** * Render an interwiki link * * You may want to use $this->_resolveInterWiki() here * * @param string $link original link - probably not much use * @param string|array $title name for the link, array for media file * @param string $wikiName indentifier (shortcut) for the remote wiki * @param string $wikiUri the fragment parsed from the original link */ public function interwikilink($link, $title, $wikiName, $wikiUri) { } /** * Link to file on users OS * * @param string $link the link * @param string|array $title name for the link, array for media file */ public function filelink($link, $title = null) { } /** * Link to windows share * * @param string $link the link * @param string|array $title name for the link, array for media file */ public function windowssharelink($link, $title = null) { } /** * Render a linked E-Mail Address * * Should honor $conf['mailguard'] setting * * @param string $address Email-Address * @param string|array $name name for the link, array for media file */ public function emaillink($address, $name = null) { } /** * Render an internal media file * * @param string $src media ID * @param string $title descriptive text * @param string $align left|center|right * @param int $width width of media in pixel * @param int $height height of media in pixel * @param string $cache cache|recache|nocache * @param string $linking linkonly|detail|nolink */ public function internalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null) { } /** * Render an external media file * * @param string $src full media URL * @param string $title descriptive text * @param string $align left|center|right * @param int $width width of media in pixel * @param int $height height of media in pixel * @param string $cache cache|recache|nocache * @param string $linking linkonly|detail|nolink */ public function externalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null) { } /** * Render a link to an internal media file * * @param string $src media ID * @param string $title descriptive text * @param string $align left|center|right * @param int $width width of media in pixel * @param int $height height of media in pixel * @param string $cache cache|recache|nocache */ public function internalmedialink($src, $title = null, $align = null, $width = null, $height = null, $cache = null) { } /** * Render a link to an external media file * * @param string $src media ID * @param string $title descriptive text * @param string $align left|center|right * @param int $width width of media in pixel * @param int $height height of media in pixel * @param string $cache cache|recache|nocache */ public function externalmedialink($src, $title = null, $align = null, $width = null, $height = null, $cache = null) { } /** * Start a table * * @param int $maxcols maximum number of columns * @param int $numrows NOT IMPLEMENTED * @param int $pos byte position in the original source */ public function table_open($maxcols = null, $numrows = null, $pos = null) { } /** * Close a table * * @param int $pos byte position in the original source */ public function table_close($pos = null) { } /** * Open a table header */ public function tablethead_open() { } /** * Close a table header */ public function tablethead_close() { } /** * Open a table body */ public function tabletbody_open() { } /** * Close a table body */ public function tabletbody_close() { } /** * Open a table footer */ public function tabletfoot_open() { } /** * Close a table footer */ public function tabletfoot_close() { } /** * Open a table row */ public function tablerow_open() { } /** * Close a table row */ public function tablerow_close() { } /** * Open a table header cell * * @param int $colspan * @param string $align left|center|right * @param int $rowspan */ public function tableheader_open($colspan = 1, $align = null, $rowspan = 1) { } /** * Close a table header cell */ public function tableheader_close() { } /** * Open a table cell * * @param int $colspan * @param string $align left|center|right * @param int $rowspan */ public function tablecell_open($colspan = 1, $align = null, $rowspan = 1) { } /** * Close a table cell */ public function tablecell_close() { } #endregion #region util functions, you probably won't need to reimplement them /** * Creates a linkid from a headline * * @author Andreas Gohr <andi@splitbrain.org> * @param string $title The headline title * @param boolean $create Create a new unique ID? * @return string */ public function _headerToLink($title, $create = false) { if($create) { return sectionID($title, $this->headers); } else { $check = false; return sectionID($title, $check); } } /** * Removes any Namespace from the given name but keeps * casing and special chars * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $name * @return string */ public function _simpleTitle($name) { global $conf; //if there is a hash we use the ancor name only @list($name, $hash) = explode('#', $name, 2); if($hash) return $hash; if($conf['useslash']) { $name = strtr($name, ';/', ';:'); } else { $name = strtr($name, ';', ':'); } return noNSorNS($name); } /** * Resolve an interwikilink * * @param string $shortcut identifier for the interwiki link * @param string $reference fragment that refers the content * @param null|bool $exists reference which returns if an internal page exists * @return string interwikilink */ public function _resolveInterWiki(&$shortcut, $reference, &$exists = null) { //get interwiki URL if(isset($this->interwiki[$shortcut])) { $url = $this->interwiki[$shortcut]; }elseif(isset($this->interwiki['default'])) { $shortcut = 'default'; $url = $this->interwiki[$shortcut]; }else{ // not parsable interwiki outputs '' to make sure string manipluation works $shortcut = ''; $url = ''; } //split into hash and url part $hash = strrchr($reference, '#'); if($hash) { $reference = substr($reference, 0, -strlen($hash)); $hash = substr($hash, 1); } //replace placeholder if(preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#', $url)) { //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 : preg_replace_callback('/[[\\\\\]^`{|}#%]/', function($match) { return rawurlencode($match[0]); }, $reference), $url); $parsed = parse_url($reference); if (empty($parsed['scheme'])) $parsed['scheme'] = ''; if (empty($parsed['host'])) $parsed['host'] = ''; if (empty($parsed['port'])) $parsed['port'] = 80; if (empty($parsed['path'])) $parsed['path'] = ''; if (empty($parsed['query'])) $parsed['query'] = ''; $url = strtr($url,[ '{SCHEME}' => $parsed['scheme'], '{HOST}' => $parsed['host'], '{PORT}' => $parsed['port'], '{PATH}' => $parsed['path'], '{QUERY}' => $parsed['query'] , ]); } else if($url != '') { // make sure when no url is defined, we keep it null // default $url = $url.rawurlencode($reference); } //handle as wiki links if($url[0] === ':') { $urlparam = null; $id = $url; if (strpos($url, '?') !== false) { list($id, $urlparam) = explode('?', $url, 2); } $url = wl(cleanID($id), $urlparam); $exists = page_exists($id); } if($hash) $url .= '#'.rawurlencode($hash); return $url; } #endregion } //Setup VIM: ex: et ts=4 : parser/parser.php 0000644 00000005416 15233462216 0010057 0 ustar 00 <?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 */ global $PARSER_MODES; $PARSER_MODES = array( // containers are complex modes that can contain many other modes // hr breaks the principle but they shouldn't be used in tables / lists // so they are put here 'container' => array('listblock', 'table', 'quote', 'hr'), // some mode are allowed inside the base mode only 'baseonly' => array('header'), // modes for styling text -- footnote behaves similar to styling 'formatting' => array( 'strong', 'emphasis', 'underline', 'monospace', 'subscript', 'superscript', 'deleted', 'footnote' ), // modes where the token is simply replaced - they can not contain any // other modes 'substition' => array( 'acronym', 'smiley', 'wordblock', 'entity', 'camelcaselink', 'internallink', 'media', 'externallink', 'linebreak', 'emaillink', 'windowssharelink', 'filelink', 'notoc', 'nocache', 'multiplyentity', 'quotes', 'rss' ), // modes which have a start and end token but inside which // no other modes should be applied 'protected' => array('preformatted', 'code', 'file', 'php', 'html', 'htmlblock', 'phpblock'), // inside this mode no wiki markup should be applied but lineendings // and whitespace isn't preserved 'disabled' => array('unformatted'), // used to mark paragraph boundaries 'paragraphs' => array('eol') ); /** * Class Doku_Parser * * @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 = 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); } } parser/handler.php 0000644 00000112267 15233462216 0010203 0 ustar 00 <?php use dokuwiki\Extension\Event; use dokuwiki\Extension\SyntaxPlugin; use dokuwiki\Parsing\Handler\Block; use dokuwiki\Parsing\Handler\CallWriter; use dokuwiki\Parsing\Handler\CallWriterInterface; use dokuwiki\Parsing\Handler\Lists; use dokuwiki\Parsing\Handler\Nest; use dokuwiki\Parsing\Handler\Preformatted; use dokuwiki\Parsing\Handler\Quote; use dokuwiki\Parsing\Handler\Table; /** * Class Doku_Handler */ class Doku_Handler { /** @var CallWriterInterface */ protected $callWriter = null; /** @var array The current CallWriter will write directly to this list of calls, Parser reads it */ public $calls = array(); /** @var array internal status holders for some modes */ protected $status = array( 'section' => false, 'doublequote' => 0, ); /** @var bool should blocks be rewritten? FIXME seems to always be true */ protected $rewriteBlocks = true; /** * Doku_Handler constructor. */ public function __construct() { $this->callWriter = new CallWriter($this); } /** * Add a new call by passing it to the current CallWriter * * @param string $handler handler method name (see mode handlers below) * @param mixed $args arguments for this call * @param int $pos byte position in the original source file */ public function addCall($handler, $args, $pos) { $call = array($handler,$args, $pos); $this->callWriter->writeCall($call); } /** * Accessor for the current CallWriter * * @return CallWriterInterface */ public function getCallWriter() { return $this->callWriter; } /** * Set a new CallWriter * * @param CallWriterInterface $callWriter */ public function setCallWriter($callWriter) { $this->callWriter = $callWriter; } /** * Return the current internal status of the given name * * @param string $status * @return mixed|null */ public function getStatus($status) { if (!isset($this->status[$status])) return null; return $this->status[$status]; } /** * Set a new internal status * * @param string $status * @param mixed $value */ public function setStatus($status, $value) { $this->status[$status] = $value; } /** @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 * * @param string $plugin name of the plugin * @param mixed $args arguments for this call * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @param string $match matched syntax */ public function addPluginCall($plugin, $args, $state, $pos, $match) { $call = array('plugin',array($plugin, $args, $state, $match), $pos); $this->callWriter->writeCall($call); } /** * Finishes handling * * Called from the parser. Calls finalise() on the call writer, closes open * sections, rewrites blocks and adds document_start and document_end calls. * * @triggers PARSER_HANDLER_DONE */ public function finalize(){ $this->callWriter->finalise(); if ( $this->status['section'] ) { $last_call = end($this->calls); array_push($this->calls,array('section_close',array(), $last_call[2])); } if ( $this->rewriteBlocks ) { $B = new Block(); $this->calls = $B->process($this->calls); } Event::createAndTrigger('PARSER_HANDLER_DONE',$this); array_unshift($this->calls,array('document_start',array(),0)); $last_call = end($this->calls); array_push($this->calls,array('document_end',array(),$last_call[2])); } /** * fetch the current call and advance the pointer to the next one * * @fixme seems to be unused? * @return bool|mixed */ public function fetch() { $call = current($this->calls); if($call !== false) { next($this->calls); //advance the pointer return $call; } return false; } /** * Internal function for parsing highlight options. * $options is parsed for key value pairs separated by commas. * A value might also be missing in which case the value will simple * be set to true. Commas in strings are ignored, e.g. option="4,56" * will work as expected and will only create one entry. * * @param string $options space separated list of key-value pairs, * e.g. option1=123, option2="456" * @return array|null Array of key-value pairs $array['key'] = 'value'; * or null if no entries found */ protected function parse_highlight_options($options) { $result = array(); preg_match_all('/(\w+(?:="[^"]*"))|(\w+(?:=[^\s]*))|(\w+[^=\s\]])(?:\s*)/', $options, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $equal_sign = strpos($match [0], '='); if ($equal_sign === false) { $key = trim($match[0]); $result [$key] = 1; } else { $key = substr($match[0], 0, $equal_sign); $value = substr($match[0], $equal_sign+1); $value = trim($value, '"'); if (strlen($value) > 0) { $result [$key] = $value; } else { $result [$key] = 1; } } } // Check for supported options $result = array_intersect_key( $result, array_flip(array( 'enable_line_numbers', 'start_line_numbers_at', 'highlight_lines_extra', 'enable_keyword_links') ) ); // Sanitize values if(isset($result['enable_line_numbers'])) { if($result['enable_line_numbers'] === 'false') { $result['enable_line_numbers'] = false; } $result['enable_line_numbers'] = (bool) $result['enable_line_numbers']; } if(isset($result['highlight_lines_extra'])) { $result['highlight_lines_extra'] = array_map('intval', explode(',', $result['highlight_lines_extra'])); $result['highlight_lines_extra'] = array_filter($result['highlight_lines_extra']); $result['highlight_lines_extra'] = array_unique($result['highlight_lines_extra']); } if(isset($result['start_line_numbers_at'])) { $result['start_line_numbers_at'] = (int) $result['start_line_numbers_at']; } if(isset($result['enable_keyword_links'])) { if($result['enable_keyword_links'] === 'false') { $result['enable_keyword_links'] = false; } $result['enable_keyword_links'] = (bool) $result['enable_keyword_links']; } if (count($result) == 0) { return null; } return $result; } /** * Simplifies handling for the formatting tags which all behave the same * * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @param string $name actual mode name */ protected function nestingTag($match, $state, $pos, $name) { switch ( $state ) { case DOKU_LEXER_ENTER: $this->addCall($name.'_open', array(), $pos); break; case DOKU_LEXER_EXIT: $this->addCall($name.'_close', array(), $pos); break; case DOKU_LEXER_UNMATCHED: $this->addCall('cdata', array($match), $pos); break; } } /** * The following methods define the handlers for the different Syntax modes * * The handlers are called from dokuwiki\Parsing\Lexer\Lexer\invokeParser() * * @todo it might make sense to move these into their own class or merge them with the * ParserMode classes some time. */ // region mode handlers /** * Special plugin handler * * This handler is called for all modes starting with 'plugin_'. * An additional parameter with the plugin name is passed. The plugin's handle() * method is called here * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @param string $pluginname name of the plugin * @return bool mode handled? */ public function plugin($match, $state, $pos, $pluginname){ $data = array($match); /** @var SyntaxPlugin $plugin */ $plugin = plugin_load('syntax',$pluginname); if($plugin != null){ $data = $plugin->handle($match, $state, $pos, $this); } if ($data !== false) { $this->addPluginCall($pluginname,$data,$state,$pos,$match); } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function base($match, $state, $pos) { switch ( $state ) { case DOKU_LEXER_UNMATCHED: $this->addCall('cdata', array($match), $pos); return true; break; } return false; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function header($match, $state, $pos) { // get level and title $title = trim($match); $level = 7 - strspn($title,'='); if($level < 1) $level = 1; $title = trim($title,'='); $title = trim($title); if ($this->status['section']) $this->addCall('section_close', array(), $pos); $this->addCall('header', array($title, $level, $pos), $pos); $this->addCall('section_open', array($level), $pos); $this->status['section'] = true; return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function notoc($match, $state, $pos) { $this->addCall('notoc', array(), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function nocache($match, $state, $pos) { $this->addCall('nocache', array(), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function linebreak($match, $state, $pos) { $this->addCall('linebreak', array(), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function eol($match, $state, $pos) { $this->addCall('eol', array(), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function hr($match, $state, $pos) { $this->addCall('hr', array(), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function strong($match, $state, $pos) { $this->nestingTag($match, $state, $pos, 'strong'); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function emphasis($match, $state, $pos) { $this->nestingTag($match, $state, $pos, 'emphasis'); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function underline($match, $state, $pos) { $this->nestingTag($match, $state, $pos, 'underline'); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function monospace($match, $state, $pos) { $this->nestingTag($match, $state, $pos, 'monospace'); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function subscript($match, $state, $pos) { $this->nestingTag($match, $state, $pos, 'subscript'); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function superscript($match, $state, $pos) { $this->nestingTag($match, $state, $pos, 'superscript'); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function deleted($match, $state, $pos) { $this->nestingTag($match, $state, $pos, 'deleted'); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function footnote($match, $state, $pos) { if (!isset($this->_footnote)) $this->_footnote = false; switch ( $state ) { case DOKU_LEXER_ENTER: // footnotes can not be nested - however due to limitations in lexer it can't be prevented // we will still enter a new footnote mode, we just do nothing if ($this->_footnote) { $this->addCall('cdata', array($match), $pos); break; } $this->_footnote = true; $this->callWriter = new Nest($this->callWriter, 'footnote_close'); $this->addCall('footnote_open', array(), $pos); break; case DOKU_LEXER_EXIT: // check whether we have already exitted the footnote mode, can happen if the modes were nested if (!$this->_footnote) { $this->addCall('cdata', array($match), $pos); break; } $this->_footnote = false; $this->addCall('footnote_close', array(), $pos); /** @var Nest $reWriter */ $reWriter = $this->callWriter; $this->callWriter = $reWriter->process(); break; case DOKU_LEXER_UNMATCHED: $this->addCall('cdata', array($match), $pos); break; } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function listblock($match, $state, $pos) { switch ( $state ) { case DOKU_LEXER_ENTER: $this->callWriter = new Lists($this->callWriter); $this->addCall('list_open', array($match), $pos); break; case DOKU_LEXER_EXIT: $this->addCall('list_close', array(), $pos); /** @var Lists $reWriter */ $reWriter = $this->callWriter; $this->callWriter = $reWriter->process(); break; case DOKU_LEXER_MATCHED: $this->addCall('list_item', array($match), $pos); break; case DOKU_LEXER_UNMATCHED: $this->addCall('cdata', array($match), $pos); break; } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function unformatted($match, $state, $pos) { if ( $state == DOKU_LEXER_UNMATCHED ) { $this->addCall('unformatted', array($match), $pos); } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function php($match, $state, $pos) { if ( $state == DOKU_LEXER_UNMATCHED ) { $this->addCall('php', array($match), $pos); } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function phpblock($match, $state, $pos) { if ( $state == DOKU_LEXER_UNMATCHED ) { $this->addCall('phpblock', array($match), $pos); } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function html($match, $state, $pos) { if ( $state == DOKU_LEXER_UNMATCHED ) { $this->addCall('html', array($match), $pos); } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function htmlblock($match, $state, $pos) { if ( $state == DOKU_LEXER_UNMATCHED ) { $this->addCall('htmlblock', array($match), $pos); } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function preformatted($match, $state, $pos) { switch ( $state ) { case DOKU_LEXER_ENTER: $this->callWriter = new Preformatted($this->callWriter); $this->addCall('preformatted_start', array(), $pos); break; case DOKU_LEXER_EXIT: $this->addCall('preformatted_end', array(), $pos); /** @var Preformatted $reWriter */ $reWriter = $this->callWriter; $this->callWriter = $reWriter->process(); break; case DOKU_LEXER_MATCHED: $this->addCall('preformatted_newline', array(), $pos); break; case DOKU_LEXER_UNMATCHED: $this->addCall('preformatted_content', array($match), $pos); break; } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function quote($match, $state, $pos) { switch ( $state ) { case DOKU_LEXER_ENTER: $this->callWriter = new Quote($this->callWriter); $this->addCall('quote_start', array($match), $pos); break; case DOKU_LEXER_EXIT: $this->addCall('quote_end', array(), $pos); /** @var Lists $reWriter */ $reWriter = $this->callWriter; $this->callWriter = $reWriter->process(); break; case DOKU_LEXER_MATCHED: $this->addCall('quote_newline', array($match), $pos); break; case DOKU_LEXER_UNMATCHED: $this->addCall('cdata', array($match), $pos); break; } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function file($match, $state, $pos) { return $this->code($match, $state, $pos, 'file'); } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @param string $type either 'code' or 'file' * @return bool mode handled? */ public function code($match, $state, $pos, $type='code') { if ( $state == DOKU_LEXER_UNMATCHED ) { $matches = explode('>',$match,2); // Cut out variable options enclosed in [] preg_match('/\[.*\]/', $matches[0], $options); if (!empty($options[0])) { $matches[0] = str_replace($options[0], '', $matches[0]); } $param = preg_split('/\s+/', $matches[0], 2, PREG_SPLIT_NO_EMPTY); while(count($param) < 2) array_push($param, null); // We shortcut html here. if ($param[0] == 'html') $param[0] = 'html4strict'; if ($param[0] == '-') $param[0] = null; array_unshift($param, $matches[1]); if (!empty($options[0])) { $param [] = $this->parse_highlight_options ($options[0]); } $this->addCall($type, $param, $pos); } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function acronym($match, $state, $pos) { $this->addCall('acronym', array($match), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function smiley($match, $state, $pos) { $this->addCall('smiley', array($match), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function wordblock($match, $state, $pos) { $this->addCall('wordblock', array($match), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function entity($match, $state, $pos) { $this->addCall('entity', array($match), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function multiplyentity($match, $state, $pos) { preg_match_all('/\d+/',$match,$matches); $this->addCall('multiplyentity', array($matches[0][0], $matches[0][1]), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function singlequoteopening($match, $state, $pos) { $this->addCall('singlequoteopening', array(), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function singlequoteclosing($match, $state, $pos) { $this->addCall('singlequoteclosing', array(), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function apostrophe($match, $state, $pos) { $this->addCall('apostrophe', array(), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function doublequoteopening($match, $state, $pos) { $this->addCall('doublequoteopening', array(), $pos); $this->status['doublequote']++; return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function doublequoteclosing($match, $state, $pos) { if ($this->status['doublequote'] <= 0) { $this->doublequoteopening($match, $state, $pos); } else { $this->addCall('doublequoteclosing', array(), $pos); $this->status['doublequote'] = max(0, --$this->status['doublequote']); } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function camelcaselink($match, $state, $pos) { $this->addCall('camelcaselink', array($match), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function internallink($match, $state, $pos) { // Strip the opening and closing markup $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match); // Split title from URL $link = explode('|',$link,2); if ( !isset($link[1]) ) { $link[1] = null; } else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) { // If the title is an image, convert it to an array containing the image details $link[1] = Doku_Handler_Parse_Media($link[1]); } $link[0] = trim($link[0]); //decide which kind of link it is if ( link_isinterwiki($link[0]) ) { // Interwiki $interwiki = explode('>',$link[0],2); $this->addCall( 'interwikilink', array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]), $pos ); }elseif ( preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u',$link[0]) ) { // Windows Share $this->addCall( 'windowssharelink', array($link[0],$link[1]), $pos ); }elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) { // external link (accepts all protocols) $this->addCall( 'externallink', array($link[0],$link[1]), $pos ); }elseif ( preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$link[0]) ) { // E-Mail (pattern above is defined in inc/mail.php) $this->addCall( 'emaillink', array($link[0],$link[1]), $pos ); }elseif ( preg_match('!^#.+!',$link[0]) ){ // local link $this->addCall( 'locallink', array(substr($link[0],1),$link[1]), $pos ); }else{ // internal link $this->addCall( 'internallink', array($link[0],$link[1]), $pos ); } return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function filelink($match, $state, $pos) { $this->addCall('filelink', array($match, null), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function windowssharelink($match, $state, $pos) { $this->addCall('windowssharelink', array($match, null), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function media($match, $state, $pos) { $p = Doku_Handler_Parse_Media($match); $this->addCall( $p['type'], array($p['src'], $p['title'], $p['align'], $p['width'], $p['height'], $p['cache'], $p['linking']), $pos ); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function rss($match, $state, $pos) { $link = preg_replace(array('/^\{\{rss>/','/\}\}$/'),'',$match); // get params list($link,$params) = explode(' ',$link,2); $p = array(); if(preg_match('/\b(\d+)\b/',$params,$match)){ $p['max'] = $match[1]; }else{ $p['max'] = 8; } $p['reverse'] = (preg_match('/rev/',$params)); $p['author'] = (preg_match('/\b(by|author)/',$params)); $p['date'] = (preg_match('/\b(date)/',$params)); $p['details'] = (preg_match('/\b(desc|detail)/',$params)); $p['nosort'] = (preg_match('/\b(nosort)\b/',$params)); if (preg_match('/\b(\d+)([dhm])\b/',$params,$match)) { $period = array('d' => 86400, 'h' => 3600, 'm' => 60); $p['refresh'] = max(600,$match[1]*$period[$match[2]]); // n * period in seconds, minimum 10 minutes } else { $p['refresh'] = 14400; // default to 4 hours } $this->addCall('rss', array($link, $p), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function externallink($match, $state, $pos) { $url = $match; $title = null; // add protocol on simple short URLs if(substr($url,0,3) == 'ftp' && (substr($url,0,6) != 'ftp://')){ $title = $url; $url = 'ftp://'.$url; } if(substr($url,0,3) == 'www' && (substr($url,0,7) != 'http://')){ $title = $url; $url = 'http://'.$url; } $this->addCall('externallink', array($url, $title), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function emaillink($match, $state, $pos) { $email = preg_replace(array('/^</','/>$/'),'',$match); $this->addCall('emaillink', array($email, null), $pos); return true; } /** * @param string $match matched syntax * @param int $state a LEXER_STATE_* constant * @param int $pos byte position in the original source file * @return bool mode handled? */ public function table($match, $state, $pos) { switch ( $state ) { case DOKU_LEXER_ENTER: $this->callWriter = new Table($this->callWriter); $this->addCall('table_start', array($pos + 1), $pos); if ( trim($match) == '^' ) { $this->addCall('tableheader', array(), $pos); } else { $this->addCall('tablecell', array(), $pos); } break; case DOKU_LEXER_EXIT: $this->addCall('table_end', array($pos), $pos); /** @var Table $reWriter */ $reWriter = $this->callWriter; $this->callWriter = $reWriter->process(); break; case DOKU_LEXER_UNMATCHED: if ( trim($match) != '' ) { $this->addCall('cdata', array($match), $pos); } break; case DOKU_LEXER_MATCHED: if ( $match == ' ' ){ $this->addCall('cdata', array($match), $pos); } else if ( preg_match('/:::/',$match) ) { $this->addCall('rowspan', array($match), $pos); } else if ( preg_match('/\t+/',$match) ) { $this->addCall('table_align', array($match), $pos); } else if ( preg_match('/ {2,}/',$match) ) { $this->addCall('table_align', array($match), $pos); } else if ( $match == "\n|" ) { $this->addCall('table_row', array(), $pos); $this->addCall('tablecell', array(), $pos); } else if ( $match == "\n^" ) { $this->addCall('table_row', array(), $pos); $this->addCall('tableheader', array(), $pos); } else if ( $match == '|' ) { $this->addCall('tablecell', array(), $pos); } else if ( $match == '^' ) { $this->addCall('tableheader', array(), $pos); } break; } return true; } // endregion modes } //------------------------------------------------------------------------ function Doku_Handler_Parse_Media($match) { // Strip the opening and closing markup $link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match); // Split title from URL $link = explode('|',$link,2); // Check alignment $ralign = (bool)preg_match('/^ /',$link[0]); $lalign = (bool)preg_match('/ $/',$link[0]); // Logic = what's that ;)... if ( $lalign & $ralign ) { $align = 'center'; } else if ( $ralign ) { $align = 'right'; } else if ( $lalign ) { $align = 'left'; } else { $align = null; } // The title... if ( !isset($link[1]) ) { $link[1] = null; } //remove aligning spaces $link[0] = trim($link[0]); //split into src and parameters (using the very last questionmark) $pos = strrpos($link[0], '?'); if($pos !== false){ $src = substr($link[0],0,$pos); $param = substr($link[0],$pos+1); }else{ $src = $link[0]; $param = ''; } //parse width and height if(preg_match('#(\d+)(x(\d+))?#i',$param,$size)){ !empty($size[1]) ? $w = $size[1] : $w = null; !empty($size[3]) ? $h = $size[3] : $h = null; } else { $w = null; $h = null; } //get linking command if(preg_match('/nolink/i',$param)){ $linking = 'nolink'; }else if(preg_match('/direct/i',$param)){ $linking = 'direct'; }else if(preg_match('/linkonly/i',$param)){ $linking = 'linkonly'; }else{ $linking = 'details'; } //get caching command if (preg_match('/(nocache|recache)/i',$param,$cachemode)){ $cache = $cachemode[1]; }else{ $cache = 'cache'; } // Check whether this is a local or remote image or interwiki if (media_isexternal($src) || link_isinterwiki($src)){ $call = 'externalmedia'; } else { $call = 'internalmedia'; } $params = array( 'type'=>$call, 'src'=>$src, 'title'=>$link[1], 'align'=>$align, 'width'=>$w, 'height'=>$h, 'cache'=>$cache, 'linking'=>$linking, ); return $params; } parser/metadata.php 0000644 00000047013 15233462216 0010342 0 ustar 00 <?php /** * The MetaData Renderer * * Metadata is additional information about a DokuWiki page that gets extracted mainly from the page's content * but also it's own filesystem data (like the creation time). All metadata is stored in the fields $meta and * $persistent. * * Some simplified rendering to $doc is done to gather the page's (text-only) abstract. * * @author Esther Brunner <wikidesign@gmail.com> */ class Doku_Renderer_metadata extends Doku_Renderer { /** the approximate byte lenght to capture for the abstract */ const ABSTRACT_LEN = 250; /** the maximum UTF8 character length for the abstract */ const ABSTRACT_MAX = 500; /** @var array transient meta data, will be reset on each rendering */ public $meta = array(); /** @var array persistent meta data, will be kept until explicitly deleted */ public $persistent = array(); /** @var array the list of headers used to create unique link ids */ protected $headers = array(); /** @var string temporary $doc store */ protected $store = ''; /** @var string keeps the first image reference */ protected $firstimage = ''; /** @var bool whether or not data is being captured for the abstract, public to be accessible by plugins */ public $capturing = true; /** @var bool determines if enough data for the abstract was collected, yet */ public $capture = true; /** @var int number of bytes captured for abstract */ protected $captured = 0; /** * Returns the format produced by this renderer. * * @return string always 'metadata' */ public function getFormat() { return 'metadata'; } /** * Initialize the document * * Sets up some of the persistent info about the page if it doesn't exist, yet. */ public function document_start() { global $ID; $this->headers = array(); // external pages are missing create date if (!isset($this->persistent['date']['created']) || !$this->persistent['date']['created']) { $this->persistent['date']['created'] = filectime(wikiFN($ID)); } if (!isset($this->persistent['user'])) { $this->persistent['user'] = ''; } if (!isset($this->persistent['creator'])) { $this->persistent['creator'] = ''; } // reset metadata to persistent values $this->meta = $this->persistent; } /** * Finalize the document * * Stores collected data in the metadata */ public function document_end() { global $ID; // store internal info in metadata (notoc,nocache) $this->meta['internal'] = $this->info; if (!isset($this->meta['description']['abstract'])) { // cut off too long abstracts $this->doc = trim($this->doc); if (strlen($this->doc) > self::ABSTRACT_MAX) { $this->doc = \dokuwiki\Utf8\PhpString::substr($this->doc, 0, self::ABSTRACT_MAX).'…'; } $this->meta['description']['abstract'] = $this->doc; } $this->meta['relation']['firstimage'] = $this->firstimage; if (!isset($this->meta['date']['modified'])) { $this->meta['date']['modified'] = filemtime(wikiFN($ID)); } } /** * Render plain text data * * This function takes care of the amount captured data and will stop capturing when * enough abstract data is available * * @param $text */ public function cdata($text) { if (!$this->capture || !$this->capturing) { return; } $this->doc .= $text; $this->captured += strlen($text); if ($this->captured > self::ABSTRACT_LEN) { $this->capture = false; } } /** * Add an item to the TOC * * @param string $id the hash link * @param string $text the text to display * @param int $level the nesting level */ public function toc_additem($id, $text, $level) { global $conf; //only add items within configured levels if ($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) { // the TOC is one of our standard ul list arrays ;-) $this->meta['description']['tableofcontents'][] = array( 'hid' => $id, 'title' => $text, 'type' => 'ul', 'level' => $level - $conf['toptoclevel'] + 1 ); } } /** * Render a heading * * @param string $text the text to display * @param int $level header level * @param int $pos byte position in the original source */ public function header($text, $level, $pos) { if (!isset($this->meta['title'])) { $this->meta['title'] = $text; } // add the header to the TOC $hid = $this->_headerToLink($text, true); $this->toc_additem($hid, $text, $level); // add to summary $this->cdata(DOKU_LF.$text.DOKU_LF); } /** * Open a paragraph */ public function p_open() { $this->cdata(DOKU_LF); } /** * Close a paragraph */ public function p_close() { $this->cdata(DOKU_LF); } /** * Create a line break */ public function linebreak() { $this->cdata(DOKU_LF); } /** * Create a horizontal line */ public function hr() { $this->cdata(DOKU_LF.'----------'.DOKU_LF); } /** * Callback for footnote start syntax * * All following content will go to the footnote instead of * the document. To achieve this the previous rendered content * is moved to $store and $doc is cleared * * @author Andreas Gohr <andi@splitbrain.org> */ public function footnote_open() { if ($this->capture) { // move current content to store // this is required to ensure safe behaviour of plugins accessed within footnotes $this->store = $this->doc; $this->doc = ''; // disable capturing $this->capturing = false; } } /** * Callback for footnote end syntax * * All content rendered whilst within footnote syntax mode is discarded, * the previously rendered content is restored and capturing is re-enabled. * * @author Andreas Gohr */ public function footnote_close() { if ($this->capture) { // re-enable capturing $this->capturing = true; // restore previously rendered content $this->doc = $this->store; $this->store = ''; } } /** * Open an unordered list */ public function listu_open() { $this->cdata(DOKU_LF); } /** * Open an ordered list */ public function listo_open() { $this->cdata(DOKU_LF); } /** * Open a list item * * @param int $level the nesting level * @param bool $node true when a node; false when a leaf */ public function listitem_open($level, $node=false) { $this->cdata(str_repeat(DOKU_TAB, $level).'* '); } /** * Close a list item */ public function listitem_close() { $this->cdata(DOKU_LF); } /** * Output preformatted text * * @param string $text */ public function preformatted($text) { $this->cdata($text); } /** * Start a block quote */ public function quote_open() { $this->cdata(DOKU_LF.DOKU_TAB.'"'); } /** * Stop a block quote */ public function quote_close() { $this->cdata('"'.DOKU_LF); } /** * Display text as file content, optionally syntax highlighted * * @param string $text text to show * @param string $lang programming language to use for syntax highlighting * @param string $file file path label */ public function file($text, $lang = null, $file = null) { $this->cdata(DOKU_LF.$text.DOKU_LF); } /** * Display text as code content, optionally syntax highlighted * * @param string $text text to show * @param string $language programming language to use for syntax highlighting * @param string $file file path label */ public function code($text, $language = null, $file = null) { $this->cdata(DOKU_LF.$text.DOKU_LF); } /** * Format an acronym * * Uses $this->acronyms * * @param string $acronym */ public function acronym($acronym) { $this->cdata($acronym); } /** * Format a smiley * * Uses $this->smiley * * @param string $smiley */ public function smiley($smiley) { $this->cdata($smiley); } /** * Format an entity * * Entities are basically small text replacements * * Uses $this->entities * * @param string $entity */ public function entity($entity) { $this->cdata($entity); } /** * Typographically format a multiply sign * * Example: ($x=640, $y=480) should result in "640×480" * * @param string|int $x first value * @param string|int $y second value */ public function multiplyentity($x, $y) { $this->cdata($x.'×'.$y); } /** * Render an opening single quote char (language specific) */ public function singlequoteopening() { global $lang; $this->cdata($lang['singlequoteopening']); } /** * Render a closing single quote char (language specific) */ public function singlequoteclosing() { global $lang; $this->cdata($lang['singlequoteclosing']); } /** * Render an apostrophe char (language specific) */ public function apostrophe() { global $lang; $this->cdata($lang['apostrophe']); } /** * Render an opening double quote char (language specific) */ public function doublequoteopening() { global $lang; $this->cdata($lang['doublequoteopening']); } /** * Render an closinging double quote char (language specific) */ public function doublequoteclosing() { global $lang; $this->cdata($lang['doublequoteclosing']); } /** * Render a CamelCase link * * @param string $link The link name * @see http://en.wikipedia.org/wiki/CamelCase */ public function camelcaselink($link) { $this->internallink($link, $link); } /** * Render a page local link * * @param string $hash hash link identifier * @param string $name name for the link */ public function locallink($hash, $name = null) { if (is_array($name)) { $this->_firstimage($name['src']); if ($name['type'] == 'internalmedia') { $this->_recordMediaUsage($name['src']); } } } /** * keep track of internal links in $this->meta['relation']['references'] * * @param string $id page ID to link to. eg. 'wiki:syntax' * @param string|array|null $name name for the link, array for media file */ public function internallink($id, $name = null) { global $ID; if (is_array($name)) { $this->_firstimage($name['src']); if ($name['type'] == 'internalmedia') { $this->_recordMediaUsage($name['src']); } } $parts = explode('?', $id, 2); if (count($parts) === 2) { $id = $parts[0]; } $default = $this->_simpleTitle($id); // first resolve and clean up the $id resolve_pageid(getNS($ID), $id, $exists); @list($page) = explode('#', $id, 2); // set metadata $this->meta['relation']['references'][$page] = $exists; // $data = array('relation' => array('isreferencedby' => array($ID => true))); // p_set_metadata($id, $data); // add link title to summary if ($this->capture) { $name = $this->_getLinkTitle($name, $default, $id); $this->doc .= $name; } } /** * Render an external link * * @param string $url full URL with scheme * @param string|array|null $name name for the link, array for media file */ public function externallink($url, $name = null) { if (is_array($name)) { $this->_firstimage($name['src']); if ($name['type'] == 'internalmedia') { $this->_recordMediaUsage($name['src']); } } if ($this->capture) { $this->doc .= $this->_getLinkTitle($name, '<'.$url.'>'); } } /** * Render an interwiki link * * You may want to use $this->_resolveInterWiki() here * * @param string $match original link - probably not much use * @param string|array $name name for the link, array for media file * @param string $wikiName indentifier (shortcut) for the remote wiki * @param string $wikiUri the fragment parsed from the original link */ public function interwikilink($match, $name, $wikiName, $wikiUri) { if (is_array($name)) { $this->_firstimage($name['src']); if ($name['type'] == 'internalmedia') { $this->_recordMediaUsage($name['src']); } } if ($this->capture) { list($wikiUri) = explode('#', $wikiUri, 2); $name = $this->_getLinkTitle($name, $wikiUri); $this->doc .= $name; } } /** * Link to windows share * * @param string $url the link * @param string|array $name name for the link, array for media file */ public function windowssharelink($url, $name = null) { if (is_array($name)) { $this->_firstimage($name['src']); if ($name['type'] == 'internalmedia') { $this->_recordMediaUsage($name['src']); } } if ($this->capture) { if ($name) { $this->doc .= $name; } else { $this->doc .= '<'.$url.'>'; } } } /** * Render a linked E-Mail Address * * Should honor $conf['mailguard'] setting * * @param string $address Email-Address * @param string|array $name name for the link, array for media file */ public function emaillink($address, $name = null) { if (is_array($name)) { $this->_firstimage($name['src']); if ($name['type'] == 'internalmedia') { $this->_recordMediaUsage($name['src']); } } if ($this->capture) { if ($name) { $this->doc .= $name; } else { $this->doc .= '<'.$address.'>'; } } } /** * Render an internal media file * * @param string $src media ID * @param string $title descriptive text * @param string $align left|center|right * @param int $width width of media in pixel * @param int $height height of media in pixel * @param string $cache cache|recache|nocache * @param string $linking linkonly|detail|nolink */ public function internalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null) { if ($this->capture && $title) { $this->doc .= '['.$title.']'; } $this->_firstimage($src); $this->_recordMediaUsage($src); } /** * Render an external media file * * @param string $src full media URL * @param string $title descriptive text * @param string $align left|center|right * @param int $width width of media in pixel * @param int $height height of media in pixel * @param string $cache cache|recache|nocache * @param string $linking linkonly|detail|nolink */ public function externalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null) { if ($this->capture && $title) { $this->doc .= '['.$title.']'; } $this->_firstimage($src); } /** * Render the output of an RSS feed * * @param string $url URL of the feed * @param array $params Finetuning of the output */ public function rss($url, $params) { $this->meta['relation']['haspart'][$url] = true; $this->meta['date']['valid']['age'] = isset($this->meta['date']['valid']['age']) ? min($this->meta['date']['valid']['age'], $params['refresh']) : $params['refresh']; } #region Utils /** * Removes any Namespace from the given name but keeps * casing and special chars * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $name * * @return mixed|string */ public function _simpleTitle($name) { global $conf; if (is_array($name)) { return ''; } if ($conf['useslash']) { $nssep = '[:;/]'; } else { $nssep = '[:;]'; } $name = preg_replace('!.*'.$nssep.'!', '', $name); //if there is a hash we use the anchor name only $name = preg_replace('!.*#!', '', $name); return $name; } /** * Construct a title and handle images in titles * * @author Harry Fuecks <hfuecks@gmail.com> * @param string|array|null $title either string title or media array * @param string $default default title if nothing else is found * @param null|string $id linked page id (used to extract title from first heading) * @return string title text */ public function _getLinkTitle($title, $default, $id = null) { if (is_array($title)) { if ($title['title']) { return '['.$title['title'].']'; } else { return $default; } } elseif (is_null($title) || trim($title) == '') { if (useHeading('content') && $id) { $heading = p_get_first_heading($id, METADATA_DONT_RENDER); if ($heading) { return $heading; } } return $default; } else { return $title; } } /** * Remember first image * * @param string $src image URL or ID */ protected function _firstimage($src) { global $ID; if ($this->firstimage) { return; } list($src) = explode('#', $src, 2); if (!media_isexternal($src)) { resolve_mediaid(getNS($ID), $src, $exists); } if (preg_match('/.(jpe?g|gif|png)$/i', $src)) { $this->firstimage = $src; } } /** * Store list of used media files in metadata * * @param string $src media ID */ protected function _recordMediaUsage($src) { global $ID; list ($src) = explode('#', $src, 2); if (media_isexternal($src)) { return; } resolve_mediaid(getNS($ID), $src, $exists); $this->meta['relation']['media'][$src] = $exists; } #endregion } //Setup VIM: ex: et ts=4 : parser/xhtml.php 0000644 00000175254 15233462216 0007727 0 ustar 00 <?php use dokuwiki\ChangeLog\MediaChangeLog; /** * Renderer for XHTML output * * This is DokuWiki's main renderer used to display page content in the wiki * * @author Harry Fuecks <hfuecks@gmail.com> * @author Andreas Gohr <andi@splitbrain.org> * */ class Doku_Renderer_xhtml extends Doku_Renderer { /** @var array store the table of contents */ public $toc = array(); /** @var array A stack of section edit data */ protected $sectionedits = array(); /** @var string|int link pages and media against this revision */ public $date_at = ''; /** @var int last section edit id, used by startSectionEdit */ protected $lastsecid = 0; /** @var array a list of footnotes, list starts at 1! */ protected $footnotes = array(); /** @var int current section level */ protected $lastlevel = 0; /** @var array section node tracker */ protected $node = array(0, 0, 0, 0, 0); /** @var string temporary $doc store */ protected $store = ''; /** @var array global counter, for table classes etc. */ protected $_counter = array(); // /** @var int counts the code and file blocks, used to provide download links */ protected $_codeblock = 0; /** @var array list of allowed URL schemes */ protected $schemes = null; /** * Register a new edit section range * * @param int $start The byte position for the edit start * @param array $data Associative array with section data: * Key 'name': the section name/title * Key 'target': the target for the section edit, * e.g. 'section' or 'table' * Key 'hid': header id * Key 'codeblockOffset': actual code block index * Key 'start': set in startSectionEdit(), * do not set yourself * Key 'range': calculated from 'start' and * $key in finishSectionEdit(), * do not set yourself * @return string A marker class for the starting HTML element * * @author Adrian Lang <lang@cosmocode.de> */ public function startSectionEdit($start, $data) { if (!is_array($data)) { msg( sprintf( 'startSectionEdit: $data "%s" is NOT an array! One of your plugins needs an update.', hsc((string) $data) ), -1 ); // @deprecated 2018-04-14, backward compatibility $args = func_get_args(); $data = array(); if(isset($args[1])) $data['target'] = $args[1]; if(isset($args[2])) $data['name'] = $args[2]; if(isset($args[3])) $data['hid'] = $args[3]; } $data['secid'] = ++$this->lastsecid; $data['start'] = $start; $this->sectionedits[] = $data; return 'sectionedit'.$data['secid']; } /** * Finish an edit section range * * @param int $end The byte position for the edit end; null for the rest of the page * * @author Adrian Lang <lang@cosmocode.de> */ public function finishSectionEdit($end = null, $hid = null) { $data = array_pop($this->sectionedits); if(!is_null($end) && $end <= $data['start']) { return; } if(!is_null($hid)) { $data['hid'] .= $hid; } $data['range'] = $data['start'].'-'.(is_null($end) ? '' : $end); unset($data['start']); $this->doc .= '<!-- EDIT'.hsc(json_encode ($data)).' -->'; } /** * Returns the format produced by this renderer. * * @return string always 'xhtml' */ public function getFormat() { return 'xhtml'; } /** * Initialize the document */ public function document_start() { //reset some internals $this->toc = array(); } /** * Finalize the document */ public function document_end() { // Finish open section edits. while(count($this->sectionedits) > 0) { if($this->sectionedits[count($this->sectionedits) - 1]['start'] <= 1) { // If there is only one section, do not write a section edit // marker. array_pop($this->sectionedits); } else { $this->finishSectionEdit(); } } if(count($this->footnotes) > 0) { $this->doc .= '<div class="footnotes">'.DOKU_LF; foreach($this->footnotes as $id => $footnote) { // check its not a placeholder that indicates actual footnote text is elsewhere if(substr($footnote, 0, 5) != "@@FNT") { // open the footnote and set the anchor and backlink $this->doc .= '<div class="fn">'; $this->doc .= '<sup><a href="#fnt__'.$id.'" id="fn__'.$id.'" class="fn_bot">'; $this->doc .= $id.')</a></sup> '.DOKU_LF; // get any other footnotes that use the same markup $alt = array_keys($this->footnotes, "@@FNT$id"); if(count($alt)) { foreach($alt as $ref) { // set anchor and backlink for the other footnotes $this->doc .= ', <sup><a href="#fnt__'.($ref).'" id="fn__'.($ref).'" class="fn_bot">'; $this->doc .= ($ref).')</a></sup> '.DOKU_LF; } } // add footnote markup and close this footnote $this->doc .= '<div class="content">'.$footnote.'</div>'; $this->doc .= '</div>'.DOKU_LF; } } $this->doc .= '</div>'.DOKU_LF; } // Prepare the TOC global $conf; if( $this->info['toc'] && is_array($this->toc) && $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads'] ) { global $TOC; $TOC = $this->toc; } // make sure there are no empty paragraphs $this->doc = preg_replace('#<p>\s*</p>#', '', $this->doc); } /** * Add an item to the TOC * * @param string $id the hash link * @param string $text the text to display * @param int $level the nesting level */ public function toc_additem($id, $text, $level) { global $conf; //handle TOC if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) { $this->toc[] = html_mktocitem($id, $text, $level - $conf['toptoclevel'] + 1); } } /** * Render a heading * * @param string $text the text to display * @param int $level header level * @param int $pos byte position in the original source */ public function header($text, $level, $pos) { global $conf; if(blank($text)) return; //skip empty headlines $hid = $this->_headerToLink($text, true); //only add items within configured levels $this->toc_additem($hid, $text, $level); // adjust $node to reflect hierarchy of levels $this->node[$level - 1]++; if($level < $this->lastlevel) { for($i = 0; $i < $this->lastlevel - $level; $i++) { $this->node[$this->lastlevel - $i - 1] = 0; } } $this->lastlevel = $level; if($level <= $conf['maxseclevel'] && count($this->sectionedits) > 0 && $this->sectionedits[count($this->sectionedits) - 1]['target'] === 'section' ) { $this->finishSectionEdit($pos - 1); } // write the header $this->doc .= DOKU_LF.'<h'.$level; if($level <= $conf['maxseclevel']) { $data = array(); $data['target'] = 'section'; $data['name'] = $text; $data['hid'] = $hid; $data['codeblockOffset'] = $this->_codeblock; $this->doc .= ' class="'.$this->startSectionEdit($pos, $data).'"'; } $this->doc .= ' id="'.$hid.'">'; $this->doc .= $this->_xmlEntities($text); $this->doc .= "</h$level>".DOKU_LF; } /** * Open a new section * * @param int $level section level (as determined by the previous header) */ public function section_open($level) { $this->doc .= '<div class="level'.$level.'">'.DOKU_LF; } /** * Close the current section */ public function section_close() { $this->doc .= DOKU_LF.'</div>'.DOKU_LF; } /** * Render plain text data * * @param $text */ public function cdata($text) { $this->doc .= $this->_xmlEntities($text); } /** * Open a paragraph */ public function p_open() { $this->doc .= DOKU_LF.'<p>'.DOKU_LF; } /** * Close a paragraph */ public function p_close() { $this->doc .= DOKU_LF.'</p>'.DOKU_LF; } /** * Create a line break */ public function linebreak() { $this->doc .= '<br/>'.DOKU_LF; } /** * Create a horizontal line */ public function hr() { $this->doc .= '<hr />'.DOKU_LF; } /** * Start strong (bold) formatting */ public function strong_open() { $this->doc .= '<strong>'; } /** * Stop strong (bold) formatting */ public function strong_close() { $this->doc .= '</strong>'; } /** * Start emphasis (italics) formatting */ public function emphasis_open() { $this->doc .= '<em>'; } /** * Stop emphasis (italics) formatting */ public function emphasis_close() { $this->doc .= '</em>'; } /** * Start underline formatting */ public function underline_open() { $this->doc .= '<em class="u">'; } /** * Stop underline formatting */ public function underline_close() { $this->doc .= '</em>'; } /** * Start monospace formatting */ public function monospace_open() { $this->doc .= '<code>'; } /** * Stop monospace formatting */ public function monospace_close() { $this->doc .= '</code>'; } /** * Start a subscript */ public function subscript_open() { $this->doc .= '<sub>'; } /** * Stop a subscript */ public function subscript_close() { $this->doc .= '</sub>'; } /** * Start a superscript */ public function superscript_open() { $this->doc .= '<sup>'; } /** * Stop a superscript */ public function superscript_close() { $this->doc .= '</sup>'; } /** * Start deleted (strike-through) formatting */ public function deleted_open() { $this->doc .= '<del>'; } /** * Stop deleted (strike-through) formatting */ public function deleted_close() { $this->doc .= '</del>'; } /** * Callback for footnote start syntax * * All following content will go to the footnote instead of * the document. To achieve this the previous rendered content * is moved to $store and $doc is cleared * * @author Andreas Gohr <andi@splitbrain.org> */ public function footnote_open() { // move current content to store and record footnote $this->store = $this->doc; $this->doc = ''; } /** * Callback for footnote end syntax * * All rendered content is moved to the $footnotes array and the old * content is restored from $store again * * @author Andreas Gohr */ public function footnote_close() { /** @var $fnid int takes track of seen footnotes, assures they are unique even across multiple docs FS#2841 */ static $fnid = 0; // assign new footnote id (we start at 1) $fnid++; // recover footnote into the stack and restore old content $footnote = $this->doc; $this->doc = $this->store; $this->store = ''; // check to see if this footnote has been seen before $i = array_search($footnote, $this->footnotes); if($i === false) { // its a new footnote, add it to the $footnotes array $this->footnotes[$fnid] = $footnote; } else { // seen this one before, save a placeholder $this->footnotes[$fnid] = "@@FNT".($i); } // output the footnote reference and link $this->doc .= '<sup><a href="#fn__'.$fnid.'" id="fnt__'.$fnid.'" class="fn_top">'.$fnid.')</a></sup>'; } /** * Open an unordered list * * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input */ public function listu_open($classes = null) { $class = ''; if($classes !== null) { if(is_array($classes)) $classes = join(' ', $classes); $class = " class=\"$classes\""; } $this->doc .= "<ul$class>".DOKU_LF; } /** * Close an unordered list */ public function listu_close() { $this->doc .= '</ul>'.DOKU_LF; } /** * Open an ordered list * * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input */ public function listo_open($classes = null) { $class = ''; if($classes !== null) { if(is_array($classes)) $classes = join(' ', $classes); $class = " class=\"$classes\""; } $this->doc .= "<ol$class>".DOKU_LF; } /** * Close an ordered list */ public function listo_close() { $this->doc .= '</ol>'.DOKU_LF; } /** * Open a list item * * @param int $level the nesting level * @param bool $node true when a node; false when a leaf */ public function listitem_open($level, $node=false) { $branching = $node ? ' node' : ''; $this->doc .= '<li class="level'.$level.$branching.'">'; } /** * Close a list item */ public function listitem_close() { $this->doc .= '</li>'.DOKU_LF; } /** * Start the content of a list item */ public function listcontent_open() { $this->doc .= '<div class="li">'; } /** * Stop the content of a list item */ public function listcontent_close() { $this->doc .= '</div>'.DOKU_LF; } /** * Output unformatted $text * * Defaults to $this->cdata() * * @param string $text */ public function unformatted($text) { $this->doc .= $this->_xmlEntities($text); } /** * Execute PHP code if allowed * * @param string $text PHP code that is either executed or printed * @param string $wrapper html element to wrap result if $conf['phpok'] is okff * * @author Andreas Gohr <andi@splitbrain.org> */ public function php($text, $wrapper = 'code') { global $conf; if($conf['phpok']) { ob_start(); eval($text); $this->doc .= ob_get_contents(); ob_end_clean(); } else { $this->doc .= p_xhtml_cached_geshi($text, 'php', $wrapper); } } /** * Output block level PHP code * * If $conf['phpok'] is true this should evaluate the given code and append the result * to $doc * * @param string $text The PHP code */ public function phpblock($text) { $this->php($text, 'pre'); } /** * Insert HTML if allowed * * @param string $text html text * @param string $wrapper html element to wrap result if $conf['htmlok'] is okff * * @author Andreas Gohr <andi@splitbrain.org> */ public function html($text, $wrapper = 'code') { global $conf; if($conf['htmlok']) { $this->doc .= $text; } else { $this->doc .= p_xhtml_cached_geshi($text, 'html4strict', $wrapper); } } /** * Output raw block-level HTML * * If $conf['htmlok'] is true this should add the code as is to $doc * * @param string $text The HTML */ public function htmlblock($text) { $this->html($text, 'pre'); } /** * Start a block quote */ public function quote_open() { $this->doc .= '<blockquote><div class="no">'.DOKU_LF; } /** * Stop a block quote */ public function quote_close() { $this->doc .= '</div></blockquote>'.DOKU_LF; } /** * Output preformatted text * * @param string $text */ public function preformatted($text) { $this->doc .= '<pre class="code">'.trim($this->_xmlEntities($text), "\n\r").'</pre>'.DOKU_LF; } /** * Display text as file content, optionally syntax highlighted * * @param string $text text to show * @param string $language programming language to use for syntax highlighting * @param string $filename file path label * @param array $options assoziative array with additional geshi options */ public function file($text, $language = null, $filename = null, $options=null) { $this->_highlight('file', $text, $language, $filename, $options); } /** * Display text as code content, optionally syntax highlighted * * @param string $text text to show * @param string $language programming language to use for syntax highlighting * @param string $filename file path label * @param array $options assoziative array with additional geshi options */ public function code($text, $language = null, $filename = null, $options=null) { $this->_highlight('code', $text, $language, $filename, $options); } /** * Use GeSHi to highlight language syntax in code and file blocks * * @author Andreas Gohr <andi@splitbrain.org> * @param string $type code|file * @param string $text text to show * @param string $language programming language to use for syntax highlighting * @param string $filename file path label * @param array $options assoziative array with additional geshi options */ public function _highlight($type, $text, $language = null, $filename = null, $options = null) { global $ID; global $lang; global $INPUT; $language = preg_replace(PREG_PATTERN_VALID_LANGUAGE, '', $language); $language = preg_replace(PREG_PATTERN_VALID_LANGUAGE, '', $language); if($filename) { // add icon list($ext) = mimetype($filename, false); $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); $class = 'mediafile mf_'.$class; $offset = 0; if ($INPUT->has('codeblockOffset')) { $offset = $INPUT->str('codeblockOffset'); } $this->doc .= '<dl class="'.$type.'">'.DOKU_LF; $this->doc .= '<dt><a href="' . exportlink( $ID, 'code', array('codeblock' => $offset + $this->_codeblock) ) . '" title="' . $lang['download'] . '" class="' . $class . '">'; $this->doc .= hsc($filename); $this->doc .= '</a></dt>'.DOKU_LF.'<dd>'; } if($text[0] == "\n") { $text = substr($text, 1); } if(substr($text, -1) == "\n") { $text = substr($text, 0, -1); } if(empty($language)) { // empty is faster than is_null and can prevent '' string $this->doc .= '<pre class="'.$type.'">'.$this->_xmlEntities($text).'</pre>'.DOKU_LF; } else { $class = 'code'; //we always need the code class to make the syntax highlighting apply if($type != 'code') $class .= ' '.$type; $this->doc .= "<pre class=\"$class $language\">" . p_xhtml_cached_geshi($text, $language, '', $options) . '</pre>' . DOKU_LF; } if($filename) { $this->doc .= '</dd></dl>'.DOKU_LF; } $this->_codeblock++; } /** * Format an acronym * * Uses $this->acronyms * * @param string $acronym */ public function acronym($acronym) { if(array_key_exists($acronym, $this->acronyms)) { $title = $this->_xmlEntities($this->acronyms[$acronym]); $this->doc .= '<abbr title="'.$title .'">'.$this->_xmlEntities($acronym).'</abbr>'; } else { $this->doc .= $this->_xmlEntities($acronym); } } /** * Format a smiley * * Uses $this->smiley * * @param string $smiley */ public function smiley($smiley) { if(array_key_exists($smiley, $this->smileys)) { $this->doc .= '<img src="'.DOKU_BASE.'lib/images/smileys/'.$this->smileys[$smiley]. '" class="icon" alt="'. $this->_xmlEntities($smiley).'" />'; } else { $this->doc .= $this->_xmlEntities($smiley); } } /** * Format an entity * * Entities are basically small text replacements * * Uses $this->entities * * @param string $entity */ public function entity($entity) { if(array_key_exists($entity, $this->entities)) { $this->doc .= $this->entities[$entity]; } else { $this->doc .= $this->_xmlEntities($entity); } } /** * Typographically format a multiply sign * * Example: ($x=640, $y=480) should result in "640×480" * * @param string|int $x first value * @param string|int $y second value */ public function multiplyentity($x, $y) { $this->doc .= "$x×$y"; } /** * Render an opening single quote char (language specific) */ public function singlequoteopening() { global $lang; $this->doc .= $lang['singlequoteopening']; } /** * Render a closing single quote char (language specific) */ public function singlequoteclosing() { global $lang; $this->doc .= $lang['singlequoteclosing']; } /** * Render an apostrophe char (language specific) */ public function apostrophe() { global $lang; $this->doc .= $lang['apostrophe']; } /** * Render an opening double quote char (language specific) */ public function doublequoteopening() { global $lang; $this->doc .= $lang['doublequoteopening']; } /** * Render an closinging double quote char (language specific) */ public function doublequoteclosing() { global $lang; $this->doc .= $lang['doublequoteclosing']; } /** * Render a CamelCase link * * @param string $link The link name * @param bool $returnonly whether to return html or write to doc attribute * @return void|string writes to doc attribute or returns html depends on $returnonly * * @see http://en.wikipedia.org/wiki/CamelCase */ public function camelcaselink($link, $returnonly = false) { if($returnonly) { return $this->internallink($link, $link, null, true); } else { $this->internallink($link, $link); } } /** * Render a page local link * * @param string $hash hash link identifier * @param string $name name for the link * @param bool $returnonly whether to return html or write to doc attribute * @return void|string writes to doc attribute or returns html depends on $returnonly */ public function locallink($hash, $name = null, $returnonly = false) { global $ID; $name = $this->_getLinkTitle($name, $hash, $isImage); $hash = $this->_headerToLink($hash); $title = $ID.' ↵'; $doc = '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">'; $doc .= $name; $doc .= '</a>'; if($returnonly) { return $doc; } else { $this->doc .= $doc; } } /** * Render an internal Wiki Link * * $search,$returnonly & $linktype are not for the renderer but are used * elsewhere - no need to implement them in other renderers * * @author Andreas Gohr <andi@splitbrain.org> * @param string $id pageid * @param string|null $name link name * @param string|null $search adds search url param * @param bool $returnonly whether to return html or write to doc attribute * @param string $linktype type to set use of headings * @return void|string writes to doc attribute or returns html depends on $returnonly */ public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') { global $conf; global $ID; global $INFO; $params = ''; $parts = explode('?', $id, 2); if(count($parts) === 2) { $id = $parts[0]; $params = $parts[1]; } // For empty $id we need to know the current $ID // We need this check because _simpleTitle needs // correct $id and resolve_pageid() use cleanID($id) // (some things could be lost) if($id === '') { $id = $ID; } // default name is based on $id as given $default = $this->_simpleTitle($id); // now first resolve and clean up the $id resolve_pageid(getNS($ID), $id, $exists, $this->date_at, true); $link = array(); $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype); if(!$isImage) { if($exists) { $class = 'wikilink1'; } else { $class = 'wikilink2'; $link['rel'] = 'nofollow'; } } else { $class = 'media'; } //keep hash anchor @list($id, $hash) = explode('#', $id, 2); if(!empty($hash)) $hash = $this->_headerToLink($hash); //prepare for formating $link['target'] = $conf['target']['wiki']; $link['style'] = ''; $link['pre'] = ''; $link['suf'] = ''; $link['more'] = 'data-wiki-id="'.$id.'"'; // id is already cleaned $link['class'] = $class; if($this->date_at) { $params = $params.'&at='.rawurlencode($this->date_at); } $link['url'] = wl($id, $params); $link['name'] = $name; $link['title'] = $id; //add search string if($search) { ($conf['userewrite']) ? $link['url'] .= '?' : $link['url'] .= '&'; if(is_array($search)) { $search = array_map('rawurlencode', $search); $link['url'] .= 's[]='.join('&s[]=', $search); } else { $link['url'] .= 's='.rawurlencode($search); } } //keep hash if($hash) $link['url'] .= '#'.$hash; //output formatted if($returnonly) { return $this->_formatLink($link); } else { $this->doc .= $this->_formatLink($link); } } /** * Render an external link * * @param string $url full URL with scheme * @param string|array $name name for the link, array for media file * @param bool $returnonly whether to return html or write to doc attribute * @return void|string writes to doc attribute or returns html depends on $returnonly */ public function externallink($url, $name = null, $returnonly = false) { global $conf; $name = $this->_getLinkTitle($name, $url, $isImage); // url might be an attack vector, only allow registered protocols if(is_null($this->schemes)) $this->schemes = getSchemes(); list($scheme) = explode('://', $url); $scheme = strtolower($scheme); if(!in_array($scheme, $this->schemes)) $url = ''; // is there still an URL? if(!$url) { if($returnonly) { return $name; } else { $this->doc .= $name; } return; } // set class if(!$isImage) { $class = 'urlextern'; } else { $class = 'media'; } //prepare for formating $link = array(); $link['target'] = $conf['target']['extern']; $link['style'] = ''; $link['pre'] = ''; $link['suf'] = ''; $link['more'] = ''; $link['class'] = $class; $link['url'] = $url; $link['rel'] = ''; $link['name'] = $name; $link['title'] = $this->_xmlEntities($url); if($conf['relnofollow']) $link['rel'] .= ' ugc nofollow'; if($conf['target']['extern']) $link['rel'] .= ' noopener'; //output formatted if($returnonly) { return $this->_formatLink($link); } else { $this->doc .= $this->_formatLink($link); } } /** * Render an interwiki link * * You may want to use $this->_resolveInterWiki() here * * @param string $match original link - probably not much use * @param string|array $name name for the link, array for media file * @param string $wikiName indentifier (shortcut) for the remote wiki * @param string $wikiUri the fragment parsed from the original link * @param bool $returnonly whether to return html or write to doc attribute * @return void|string writes to doc attribute or returns html depends on $returnonly */ public function interwikilink($match, $name, $wikiName, $wikiUri, $returnonly = false) { global $conf; $link = array(); $link['target'] = $conf['target']['interwiki']; $link['pre'] = ''; $link['suf'] = ''; $link['more'] = ''; $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); $link['rel'] = ''; //get interwiki URL $exists = null; $url = $this->_resolveInterWiki($wikiName, $wikiUri, $exists); if(!$isImage) { $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName); $link['class'] = "interwiki iw_$class"; } else { $link['class'] = 'media'; } //do we stay at the same server? Use local target if(strpos($url, DOKU_URL) === 0 OR strpos($url, DOKU_BASE) === 0) { $link['target'] = $conf['target']['wiki']; } if($exists !== null && !$isImage) { if($exists) { $link['class'] .= ' wikilink1'; } else { $link['class'] .= ' wikilink2'; $link['rel'] .= ' nofollow'; } } if($conf['target']['interwiki']) $link['rel'] .= ' noopener'; $link['url'] = $url; $link['title'] = htmlspecialchars($link['url']); // output formatted if($returnonly) { if($url == '') return $link['name']; return $this->_formatLink($link); } else { if($url == '') $this->doc .= $link['name']; else $this->doc .= $this->_formatLink($link); } } /** * Link to windows share * * @param string $url the link * @param string|array $name name for the link, array for media file * @param bool $returnonly whether to return html or write to doc attribute * @return void|string writes to doc attribute or returns html depends on $returnonly */ public function windowssharelink($url, $name = null, $returnonly = false) { global $conf; //simple setup $link = array(); $link['target'] = $conf['target']['windows']; $link['pre'] = ''; $link['suf'] = ''; $link['style'] = ''; $link['name'] = $this->_getLinkTitle($name, $url, $isImage); if(!$isImage) { $link['class'] = 'windows'; } else { $link['class'] = 'media'; } $link['title'] = $this->_xmlEntities($url); $url = str_replace('\\', '/', $url); $url = 'file:///'.$url; $link['url'] = $url; //output formatted if($returnonly) { return $this->_formatLink($link); } else { $this->doc .= $this->_formatLink($link); } } /** * Render a linked E-Mail Address * * Honors $conf['mailguard'] setting * * @param string $address Email-Address * @param string|array $name name for the link, array for media file * @param bool $returnonly whether to return html or write to doc attribute * @return void|string writes to doc attribute or returns html depends on $returnonly */ public function emaillink($address, $name = null, $returnonly = false) { global $conf; //simple setup $link = array(); $link['target'] = ''; $link['pre'] = ''; $link['suf'] = ''; $link['style'] = ''; $link['more'] = ''; $name = $this->_getLinkTitle($name, '', $isImage); if(!$isImage) { $link['class'] = 'mail'; } else { $link['class'] = 'media'; } $address = $this->_xmlEntities($address); $address = obfuscate($address); $title = $address; if(empty($name)) { $name = $address; } if($conf['mailguard'] == 'visible') $address = rawurlencode($address); $link['url'] = 'mailto:'.$address; $link['name'] = $name; $link['title'] = $title; //output formatted if($returnonly) { return $this->_formatLink($link); } else { $this->doc .= $this->_formatLink($link); } } /** * Render an internal media file * * @param string $src media ID * @param string $title descriptive text * @param string $align left|center|right * @param int $width width of media in pixel * @param int $height height of media in pixel * @param string $cache cache|recache|nocache * @param string $linking linkonly|detail|nolink * @param bool $return return HTML instead of adding to $doc * @return void|string writes to doc attribute or returns html depends on $return */ public function internalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null, $return = false) { global $ID; if (strpos($src, '#') !== false) { list($src, $hash) = explode('#', $src, 2); } resolve_mediaid(getNS($ID), $src, $exists, $this->date_at, true); $noLink = false; $render = ($linking == 'linkonly') ? false : true; $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); list($ext, $mime) = mimetype($src, false); if(substr($mime, 0, 5) == 'image' && $render) { $link['url'] = ml( $src, array( 'id' => $ID, 'cache' => $cache, 'rev' => $this->_getLastMediaRevisionAt($src) ), ($linking == 'direct') ); } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { // don't link movies $noLink = true; } else { // add file icons $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); $link['class'] .= ' mediafile mf_'.$class; $link['url'] = ml( $src, array( 'id' => $ID, 'cache' => $cache, 'rev' => $this->_getLastMediaRevisionAt($src) ), true ); if($exists) $link['title'] .= ' ('.filesize_h(filesize(mediaFN($src))).')'; } if (!empty($hash)) $link['url'] .= '#'.$hash; //markup non existing files if(!$exists) { $link['class'] .= ' wikilink2'; } //output formatted if($return) { if($linking == 'nolink' || $noLink) return $link['name']; else return $this->_formatLink($link); } else { if($linking == 'nolink' || $noLink) $this->doc .= $link['name']; else $this->doc .= $this->_formatLink($link); } } /** * Render an external media file * * @param string $src full media URL * @param string $title descriptive text * @param string $align left|center|right * @param int $width width of media in pixel * @param int $height height of media in pixel * @param string $cache cache|recache|nocache * @param string $linking linkonly|detail|nolink * @param bool $return return HTML instead of adding to $doc * @return void|string writes to doc attribute or returns html depends on $return */ public function externalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null, $return = false) { if(link_isinterwiki($src)){ list($shortcut, $reference) = explode('>', $src, 2); $exists = null; $src = $this->_resolveInterWiki($shortcut, $reference, $exists); if($src == '' && empty($title)){ // make sure at least something will be shown in this case $title = $reference; } } list($src, $hash) = explode('#', $src, 2); $noLink = false; if($src == '') { // only output plaintext without link if there is no src $noLink = true; } $render = ($linking == 'linkonly') ? false : true; $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); $link['url'] = ml($src, array('cache' => $cache)); list($ext, $mime) = mimetype($src, false); if(substr($mime, 0, 5) == 'image' && $render) { // link only jpeg images // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true; } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { // don't link movies $noLink = true; } else { // add file icons $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); $link['class'] .= ' mediafile mf_'.$class; } if($hash) $link['url'] .= '#'.$hash; //output formatted if($return) { if($linking == 'nolink' || $noLink) return $link['name']; else return $this->_formatLink($link); } else { if($linking == 'nolink' || $noLink) $this->doc .= $link['name']; else $this->doc .= $this->_formatLink($link); } } /** * Renders an RSS feed * * @param string $url URL of the feed * @param array $params Finetuning of the output * * @author Andreas Gohr <andi@splitbrain.org> */ public function rss($url, $params) { global $lang; global $conf; require_once(DOKU_INC.'inc/FeedParser.php'); $feed = new FeedParser(); $feed->set_feed_url($url); //disable warning while fetching if(!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); } $rc = $feed->init(); if(isset($elvl)) { error_reporting($elvl); } if($params['nosort']) $feed->enable_order_by_date(false); //decide on start and end if($params['reverse']) { $mod = -1; $start = $feed->get_item_quantity() - 1; $end = $start - ($params['max']); $end = ($end < -1) ? -1 : $end; } else { $mod = 1; $start = 0; $end = $feed->get_item_quantity(); $end = ($end > $params['max']) ? $params['max'] : $end; } $this->doc .= '<ul class="rss">'; if($rc) { for($x = $start; $x != $end; $x += $mod) { $item = $feed->get_item($x); $this->doc .= '<li><div class="li">'; // support feeds without links $lnkurl = $item->get_permalink(); if($lnkurl) { // title is escaped by SimplePie, we unescape here because it // is escaped again in externallink() FS#1705 $this->externallink( $item->get_permalink(), html_entity_decode($item->get_title(), ENT_QUOTES, 'UTF-8') ); } else { $this->doc .= ' '.$item->get_title(); } if($params['author']) { $author = $item->get_author(0); if($author) { $name = $author->get_name(); if(!$name) $name = $author->get_email(); if($name) $this->doc .= ' '.$lang['by'].' '.hsc($name); } } if($params['date']) { $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')'; } if($params['details']) { $this->doc .= '<div class="detail">'; if($conf['htmlok']) { $this->doc .= $item->get_description(); } else { $this->doc .= strip_tags($item->get_description()); } $this->doc .= '</div>'; } $this->doc .= '</div></li>'; } } else { $this->doc .= '<li><div class="li">'; $this->doc .= '<em>'.$lang['rssfailed'].'</em>'; $this->externallink($url); if($conf['allowdebug']) { $this->doc .= '<!--'.hsc($feed->error).'-->'; } $this->doc .= '</div></li>'; } $this->doc .= '</ul>'; } /** * Start a table * * @param int $maxcols maximum number of columns * @param int $numrows NOT IMPLEMENTED * @param int $pos byte position in the original source * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input */ public function table_open($maxcols = null, $numrows = null, $pos = null, $classes = null) { // initialize the row counter used for classes $this->_counter['row_counter'] = 0; $class = 'table'; if($classes !== null) { if(is_array($classes)) $classes = join(' ', $classes); $class .= ' ' . $classes; } if($pos !== null) { $hid = $this->_headerToLink($class, true); $data = array(); $data['target'] = 'table'; $data['name'] = ''; $data['hid'] = $hid; $class .= ' '.$this->startSectionEdit($pos, $data); } $this->doc .= '<div class="'.$class.'"><table class="inline">'. DOKU_LF; } /** * Close a table * * @param int $pos byte position in the original source */ public function table_close($pos = null) { $this->doc .= '</table></div>'.DOKU_LF; if($pos !== null) { $this->finishSectionEdit($pos); } } /** * Open a table header */ public function tablethead_open() { $this->doc .= DOKU_TAB.'<thead>'.DOKU_LF; } /** * Close a table header */ public function tablethead_close() { $this->doc .= DOKU_TAB.'</thead>'.DOKU_LF; } /** * Open a table body */ public function tabletbody_open() { $this->doc .= DOKU_TAB.'<tbody>'.DOKU_LF; } /** * Close a table body */ public function tabletbody_close() { $this->doc .= DOKU_TAB.'</tbody>'.DOKU_LF; } /** * Open a table footer */ public function tabletfoot_open() { $this->doc .= DOKU_TAB.'<tfoot>'.DOKU_LF; } /** * Close a table footer */ public function tabletfoot_close() { $this->doc .= DOKU_TAB.'</tfoot>'.DOKU_LF; } /** * Open a table row * * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input */ public function tablerow_open($classes = null) { // initialize the cell counter used for classes $this->_counter['cell_counter'] = 0; $class = 'row'.$this->_counter['row_counter']++; if($classes !== null) { if(is_array($classes)) $classes = join(' ', $classes); $class .= ' ' . $classes; } $this->doc .= DOKU_TAB.'<tr class="'.$class.'">'.DOKU_LF.DOKU_TAB.DOKU_TAB; } /** * Close a table row */ public function tablerow_close() { $this->doc .= DOKU_LF.DOKU_TAB.'</tr>'.DOKU_LF; } /** * Open a table header cell * * @param int $colspan * @param string $align left|center|right * @param int $rowspan * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input */ public function tableheader_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { $class = 'class="col'.$this->_counter['cell_counter']++; if(!is_null($align)) { $class .= ' '.$align.'align'; } if($classes !== null) { if(is_array($classes)) $classes = join(' ', $classes); $class .= ' ' . $classes; } $class .= '"'; $this->doc .= '<th '.$class; if($colspan > 1) { $this->_counter['cell_counter'] += $colspan - 1; $this->doc .= ' colspan="'.$colspan.'"'; } if($rowspan > 1) { $this->doc .= ' rowspan="'.$rowspan.'"'; } $this->doc .= '>'; } /** * Close a table header cell */ public function tableheader_close() { $this->doc .= '</th>'; } /** * Open a table cell * * @param int $colspan * @param string $align left|center|right * @param int $rowspan * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input */ public function tablecell_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { $class = 'class="col'.$this->_counter['cell_counter']++; if(!is_null($align)) { $class .= ' '.$align.'align'; } if($classes !== null) { if(is_array($classes)) $classes = join(' ', $classes); $class .= ' ' . $classes; } $class .= '"'; $this->doc .= '<td '.$class; if($colspan > 1) { $this->_counter['cell_counter'] += $colspan - 1; $this->doc .= ' colspan="'.$colspan.'"'; } if($rowspan > 1) { $this->doc .= ' rowspan="'.$rowspan.'"'; } $this->doc .= '>'; } /** * Close a table cell */ public function tablecell_close() { $this->doc .= '</td>'; } /** * Returns the current header level. * (required e.g. by the filelist plugin) * * @return int The current header level */ public function getLastlevel() { return $this->lastlevel; } #region Utility functions /** * Build a link * * Assembles all parts defined in $link returns HTML for the link * * @param array $link attributes of a link * @return string * * @author Andreas Gohr <andi@splitbrain.org> */ public function _formatLink($link) { //make sure the url is XHTML compliant (skip mailto) if(substr($link['url'], 0, 7) != 'mailto:') { $link['url'] = str_replace('&', '&', $link['url']); $link['url'] = str_replace('&amp;', '&', $link['url']); } //remove double encodings in titles $link['title'] = str_replace('&amp;', '&', $link['title']); // be sure there are no bad chars in url or title // (we can't do this for name because it can contain an img tag) $link['url'] = strtr($link['url'], array('>' => '%3E', '<' => '%3C', '"' => '%22')); $link['title'] = strtr($link['title'], array('>' => '>', '<' => '<', '"' => '"')); $ret = ''; $ret .= $link['pre']; $ret .= '<a href="'.$link['url'].'"'; if(!empty($link['class'])) $ret .= ' class="'.$link['class'].'"'; if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"'; if(!empty($link['title'])) $ret .= ' title="'.$link['title'].'"'; if(!empty($link['style'])) $ret .= ' style="'.$link['style'].'"'; if(!empty($link['rel'])) $ret .= ' rel="'.trim($link['rel']).'"'; if(!empty($link['more'])) $ret .= ' '.$link['more']; $ret .= '>'; $ret .= $link['name']; $ret .= '</a>'; $ret .= $link['suf']; return $ret; } /** * Renders internal and external media * * @author Andreas Gohr <andi@splitbrain.org> * @param string $src media ID * @param string $title descriptive text * @param string $align left|center|right * @param int $width width of media in pixel * @param int $height height of media in pixel * @param string $cache cache|recache|nocache * @param bool $render should the media be embedded inline or just linked * @return string */ public function _media($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $render = true) { $ret = ''; list($ext, $mime) = mimetype($src); if(substr($mime, 0, 5) == 'image') { // first get the $title if(!is_null($title)) { $title = $this->_xmlEntities($title); } elseif($ext == 'jpg' || $ext == 'jpeg') { //try to use the caption from IPTC/EXIF require_once(DOKU_INC.'inc/JpegMeta.php'); $jpeg = new JpegMeta(mediaFN($src)); if($jpeg !== false) $cap = $jpeg->getTitle(); if(!empty($cap)) { $title = $this->_xmlEntities($cap); } } if(!$render) { // if the picture is not supposed to be rendered // return the title of the picture if($title === null || $title === "") { // just show the sourcename $title = $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src))); } return $title; } //add image tag $ret .= '<img src="' . ml( $src, array( 'w' => $width, 'h' => $height, 'cache' => $cache, 'rev' => $this->_getLastMediaRevisionAt($src) ) ) . '"'; $ret .= ' class="media'.$align.'"'; if($title) { $ret .= ' title="'.$title.'"'; $ret .= ' alt="'.$title.'"'; } else { $ret .= ' alt=""'; } if(!is_null($width)) $ret .= ' width="'.$this->_xmlEntities($width).'"'; if(!is_null($height)) $ret .= ' height="'.$this->_xmlEntities($height).'"'; $ret .= ' />'; } elseif(media_supportedav($mime, 'video') || media_supportedav($mime, 'audio')) { // first get the $title $title = !is_null($title) ? $title : false; if(!$render) { // if the file is not supposed to be rendered // return the title of the file (just the sourcename if there is no title) return $this->_xmlEntities($title ? $title : \dokuwiki\Utf8\PhpString::basename(noNS($src))); } $att = array(); $att['class'] = "media$align"; if($title) { $att['title'] = $title; } if(media_supportedav($mime, 'video')) { //add video $ret .= $this->_video($src, $width, $height, $att); } if(media_supportedav($mime, 'audio')) { //add audio $ret .= $this->_audio($src, $att); } } elseif($mime == 'application/x-shockwave-flash') { if(!$render) { // if the flash is not supposed to be rendered // return the title of the flash if(!$title) { // just show the sourcename $title = \dokuwiki\Utf8\PhpString::basename(noNS($src)); } return $this->_xmlEntities($title); } $att = array(); $att['class'] = "media$align"; if($align == 'right') $att['align'] = 'right'; if($align == 'left') $att['align'] = 'left'; $ret .= html_flashobject( ml($src, array('cache' => $cache), true, '&'), $width, $height, array('quality' => 'high'), null, $att, $this->_xmlEntities($title) ); } elseif($title) { // well at least we have a title to display $ret .= $this->_xmlEntities($title); } else { // just show the sourcename $ret .= $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src))); } return $ret; } /** * Escape string for output * * @param $string * @return string */ public function _xmlEntities($string) { return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); } /** * Construct a title and handle images in titles * * @author Harry Fuecks <hfuecks@gmail.com> * @param string|array $title either string title or media array * @param string $default default title if nothing else is found * @param bool $isImage will be set to true if it's a media file * @param null|string $id linked page id (used to extract title from first heading) * @param string $linktype content|navigation * @return string HTML of the title, might be full image tag or just escaped text */ public function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') { $isImage = false; if(is_array($title)) { $isImage = true; return $this->_imageTitle($title); } elseif(is_null($title) || trim($title) == '') { if(useHeading($linktype) && $id) { $heading = p_get_first_heading($id); if(!blank($heading)) { return $this->_xmlEntities($heading); } } return $this->_xmlEntities($default); } else { return $this->_xmlEntities($title); } } /** * Returns HTML code for images used in link titles * * @author Andreas Gohr <andi@splitbrain.org> * @param array $img * @return string HTML img tag or similar */ public function _imageTitle($img) { global $ID; // some fixes on $img['src'] // see internalmedia() and externalmedia() list($img['src']) = explode('#', $img['src'], 2); if($img['type'] == 'internalmedia') { resolve_mediaid(getNS($ID), $img['src'], $exists ,$this->date_at, true); } return $this->_media( $img['src'], $img['title'], $img['align'], $img['width'], $img['height'], $img['cache'] ); } /** * helperfunction to return a basic link to a media * * used in internalmedia() and externalmedia() * * @author Pierre Spring <pierre.spring@liip.ch> * @param string $src media ID * @param string $title descriptive text * @param string $align left|center|right * @param int $width width of media in pixel * @param int $height height of media in pixel * @param string $cache cache|recache|nocache * @param bool $render should the media be embedded inline or just linked * @return array associative array with link config */ public function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) { global $conf; $link = array(); $link['class'] = 'media'; $link['style'] = ''; $link['pre'] = ''; $link['suf'] = ''; $link['more'] = ''; $link['target'] = $conf['target']['media']; if($conf['target']['media']) $link['rel'] = 'noopener'; $link['title'] = $this->_xmlEntities($src); $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render); return $link; } /** * Embed video(s) in HTML * * @author Anika Henke <anika@selfthinker.org> * @author Schplurtz le Déboulonné <Schplurtz@laposte.net> * * @param string $src - ID of video to embed * @param int $width - width of the video in pixels * @param int $height - height of the video in pixels * @param array $atts - additional attributes for the <video> tag * @return string */ public function _video($src, $width, $height, $atts = null) { // prepare width and height if(is_null($atts)) $atts = array(); $atts['width'] = (int) $width; $atts['height'] = (int) $height; if(!$atts['width']) $atts['width'] = 320; if(!$atts['height']) $atts['height'] = 240; $posterUrl = ''; $files = array(); $tracks = array(); $isExternal = media_isexternal($src); if ($isExternal) { // take direct source for external files list(/*ext*/, $srcMime) = mimetype($src); $files[$srcMime] = $src; } else { // prepare alternative formats $extensions = array('webm', 'ogv', 'mp4'); $files = media_alternativefiles($src, $extensions); $poster = media_alternativefiles($src, array('jpg', 'png')); $tracks = media_trackfiles($src); if(!empty($poster)) { $posterUrl = ml(reset($poster), '', true, '&'); } } $out = ''; // open video tag $out .= '<video '.buildAttributes($atts).' controls="controls"'; if($posterUrl) $out .= ' poster="'.hsc($posterUrl).'"'; $out .= '>'.NL; $fallback = ''; // output source for each alternative video format foreach($files as $mime => $file) { if ($isExternal) { $url = $file; $linkType = 'externalmedia'; } else { $url = ml($file, '', true, '&'); $linkType = 'internalmedia'; } $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file))); $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; // alternative content (just a link to the file) $fallback .= $this->$linkType( $file, $title, null, null, null, $cache = null, $linking = 'linkonly', $return = true ); } // output each track if any foreach( $tracks as $trackid => $info ) { list( $kind, $srclang ) = array_map( 'hsc', $info ); $out .= "<track kind=\"$kind\" srclang=\"$srclang\" "; $out .= "label=\"$srclang\" "; $out .= 'src="'.ml($trackid, '', true).'">'.NL; } // finish $out .= $fallback; $out .= '</video>'.NL; return $out; } /** * Embed audio in HTML * * @author Anika Henke <anika@selfthinker.org> * * @param string $src - ID of audio to embed * @param array $atts - additional attributes for the <audio> tag * @return string */ public function _audio($src, $atts = array()) { $files = array(); $isExternal = media_isexternal($src); if ($isExternal) { // take direct source for external files list(/*ext*/, $srcMime) = mimetype($src); $files[$srcMime] = $src; } else { // prepare alternative formats $extensions = array('ogg', 'mp3', 'wav'); $files = media_alternativefiles($src, $extensions); } $out = ''; // open audio tag $out .= '<audio '.buildAttributes($atts).' controls="controls">'.NL; $fallback = ''; // output source for each alternative audio format foreach($files as $mime => $file) { if ($isExternal) { $url = $file; $linkType = 'externalmedia'; } else { $url = ml($file, '', true, '&'); $linkType = 'internalmedia'; } $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file))); $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; // alternative content (just a link to the file) $fallback .= $this->$linkType( $file, $title, null, null, null, $cache = null, $linking = 'linkonly', $return = true ); } // finish $out .= $fallback; $out .= '</audio>'.NL; return $out; } /** * _getLastMediaRevisionAt is a helperfunction to internalmedia() and _media() * which returns an existing media revision less or equal to rev or date_at * * @author lisps * @param string $media_id * @access protected * @return string revision ('' for current) */ protected function _getLastMediaRevisionAt($media_id){ if(!$this->date_at || media_isexternal($media_id)) return ''; $pagelog = new MediaChangeLog($media_id); return $pagelog->getLastRevisionAt($this->date_at); } #endregion } //Setup VIM: ex: et ts=4 : parser/code.php 0000644 00000003716 15233462216 0007476 0 ustar 00 <?php /** * A simple renderer that allows downloading of code and file snippets * * @author Andreas Gohr <andi@splitbrain.org> */ class Doku_Renderer_code extends Doku_Renderer { protected $_codeblock = 0; /** * Send the wanted code block to the browser * * When the correct block was found it exits the script. * * @param string $text * @param string $language * @param string $filename */ public function code($text, $language = null, $filename = '') { global $INPUT; if(!$language) $language = 'txt'; $language = preg_replace(PREG_PATTERN_VALID_LANGUAGE, '', $language); if(!$filename) $filename = 'snippet.'.$language; $filename = \dokuwiki\Utf8\PhpString::basename($filename); $filename = \dokuwiki\Utf8\Clean::stripspecials($filename, '_'); // send CRLF to Windows clients if(strpos($INPUT->server->str('HTTP_USER_AGENT'), 'Windows') !== false) { $text = str_replace("\n", "\r\n", $text); } if($this->_codeblock == $INPUT->str('codeblock')) { header("Content-Type: text/plain; charset=utf-8"); header("Content-Disposition: attachment; filename=$filename"); header("X-Robots-Tag: noindex"); echo trim($text, "\r\n"); exit; } $this->_codeblock++; } /** * Wraps around code() * * @param string $text * @param string $language * @param string $filename */ public function file($text, $language = null, $filename = '') { $this->code($text, $language, $filename); } /** * This should never be reached, if it is send a 404 */ public function document_end() { http_status(404); echo '404 - Not found'; exit; } /** * Return the format of the renderer * * @returns string 'code' */ public function getFormat() { return 'code'; } } parser/xhtmlsummary.php 0000644 00000004551 15233462216 0011334 0 ustar 00 <?php /** * The summary XHTML form selects either up to the first two paragraphs * it find in a page or the first section (whichever comes first) * It strips out the table of contents if one exists * Section divs are not used - everything should be nested in a single * div with CSS class "page" * Headings have their a name link removed and section editing links * removed * It also attempts to capture the first heading in a page for * use as the title of the page. * * * @author Harry Fuecks <hfuecks@gmail.com> * @todo Is this currently used anywhere? Should it? */ class Doku_Renderer_xhtmlsummary extends Doku_Renderer_xhtml { // Namespace these variables to // avoid clashes with parent classes protected $sum_paragraphs = 0; protected $sum_capture = true; protected $sum_inSection = false; protected $sum_summary = ''; protected $sum_pageTitle = false; /** @inheritdoc */ public function document_start() { $this->doc .= DOKU_LF.'<div>'.DOKU_LF; } /** @inheritdoc */ public function document_end() { $this->doc = $this->sum_summary; $this->doc .= DOKU_LF.'</div>'.DOKU_LF; } /** @inheritdoc */ public function header($text, $level, $pos) { if ( !$this->sum_pageTitle ) { $this->info['sum_pagetitle'] = $text; $this->sum_pageTitle = true; } $this->doc .= DOKU_LF.'<h'.$level.'>'; $this->doc .= $this->_xmlEntities($text); $this->doc .= "</h$level>".DOKU_LF; } /** @inheritdoc */ public function section_open($level) { if ( $this->sum_capture ) { $this->sum_inSection = true; } } /** @inheritdoc */ public function section_close() { if ( $this->sum_capture && $this->sum_inSection ) { $this->sum_summary .= $this->doc; $this->sum_capture = false; } } /** @inheritdoc */ public function p_open() { if ( $this->sum_capture && $this->sum_paragraphs < 2 ) { $this->sum_paragraphs++; } parent :: p_open(); } /** @inheritdoc */ public function p_close() { parent :: p_close(); if ( $this->sum_capture && $this->sum_paragraphs >= 2 ) { $this->sum_summary .= $this->doc; $this->sum_capture = false; } } } //Setup VIM: ex: et ts=2 : fetch.functions.php 0000644 00000015067 15233462216 0010372 0 ustar 00 <?php /** * Functions used by lib/exe/fetch.php * (not included by other parts of dokuwiki) */ /** * Set headers and send the file to the client * * The $cache parameter influences how long files may be kept in caches, the $public parameter * influences if this caching may happen in public proxis or in the browser cache only FS#2734 * * This function will abort the current script when a 304 is sent or file sending is handled * through x-sendfile * * @author Andreas Gohr <andi@splitbrain.org> * @author Ben Coburn <btcoburn@silicodon.net> * @author Gerry Weissbach <dokuwiki@gammaproduction.de> * * @param string $file local file to send * @param string $mime mime type of the file * @param bool $dl set to true to force a browser download * @param int $cache remaining cache time in seconds (-1 for $conf['cache'], 0 for no-cache) * @param bool $public is this a public ressource or a private one? * @param string $orig original file to send - the file name will be used for the Content-Disposition */ function sendFile($file, $mime, $dl, $cache, $public = false, $orig = null) { global $conf; // send mime headers header("Content-Type: $mime"); // calculate cache times if($cache == -1) { $maxage = max($conf['cachetime'], 3600); // cachetime or one hour $expires = time() + $maxage; } else if($cache > 0) { $maxage = $cache; // given time $expires = time() + $maxage; } else { // $cache == 0 $maxage = 0; $expires = 0; // 1970-01-01 } // smart http caching headers if($maxage) { if($public) { // cache publically header('Expires: '.gmdate("D, d M Y H:i:s", $expires).' GMT'); header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.$maxage); } else { // cache in browser header('Expires: '.gmdate("D, d M Y H:i:s", $expires).' GMT'); header('Cache-Control: private, no-transform, max-age='.$maxage); } } else { // no cache at all header('Expires: Thu, 01 Jan 1970 00:00:00 GMT'); header('Cache-Control: no-cache, no-transform'); } //send important headers first, script stops here if '304 Not Modified' response $fmtime = @filemtime($file); http_conditionalRequest($fmtime); // Use the current $file if is $orig is not set. if ( $orig == null ) { $orig = $file; } //download or display? if ($dl) { header('Content-Disposition: attachment;' . rfc2231_encode( 'filename', \dokuwiki\Utf8\PhpString::basename($orig)) . ';' ); } else { header('Content-Disposition: inline;' . rfc2231_encode( 'filename', \dokuwiki\Utf8\PhpString::basename($orig)) . ';' ); } //use x-sendfile header to pass the delivery to compatible webservers http_sendfile($file); // send file contents $fp = @fopen($file, "rb"); if($fp) { http_rangeRequest($fp, filesize($file), $mime); } else { http_status(500); print "Could not read $file - bad permissions?"; } } /** * Try an rfc2231 compatible encoding. This ensures correct * interpretation of filenames outside of the ASCII set. * This seems to be needed for file names with e.g. umlauts that * would otherwise decode wrongly in IE. * * There is no additional checking, just the encoding and setting the key=value for usage in headers * * @author Gerry Weissbach <gerry.w@gammaproduction.de> * @param string $name name of the field to be set in the header() call * @param string $value value of the field to be set in the header() call * @param string $charset used charset for the encoding of value * @param string $lang language used. * @return string in the format " name=value" for values WITHOUT special characters * @return string in the format " name*=charset'lang'value" for values WITH special characters */ function rfc2231_encode($name, $value, $charset='utf-8', $lang='en') { $internal = preg_replace_callback( '/[\x00-\x20*\'%()<>@,;:\\\\"\/[\]?=\x80-\xFF]/', function ($match) { return rawurlencode($match[0]); }, $value ); if ( $value != $internal ) { return ' '.$name.'*='.$charset."'".$lang."'".$internal; } else { return ' '.$name.'="'.$value.'"'; } } /** * Check for media for preconditions and return correct status code * * READ: MEDIA, MIME, EXT, CACHE * WRITE: MEDIA, FILE, array( STATUS, STATUSMESSAGE ) * * @author Gerry Weissbach <gerry.w@gammaproduction.de> * * @param string $media reference to the media id * @param string $file reference to the file variable * @param string $rev * @param int $width * @param int $height * @return array as array(STATUS, STATUSMESSAGE) */ function checkFileStatus(&$media, &$file, $rev = '', $width=0, $height=0) { global $MIME, $EXT, $CACHE, $INPUT; //media to local file if(media_isexternal($media)) { //check token for external image and additional for resized and cached images if(media_get_token($media, $width, $height) !== $INPUT->str('tok')) { return array(412, 'Precondition Failed'); } //handle external images if(strncmp($MIME, 'image/', 6) == 0) $file = media_get_from_URL($media, $EXT, $CACHE); if(!$file) { //download failed - redirect to original URL return array(302, $media); } } else { $media = cleanID($media); if(empty($media)) { return array(400, 'Bad request'); } // check token for resized images if (($width || $height) && media_get_token($media, $width, $height) !== $INPUT->str('tok')) { return array(412, 'Precondition Failed'); } //check permissions (namespace only) if(auth_quickaclcheck(getNS($media).':X') < AUTH_READ) { return array(403, 'Forbidden'); } $file = mediaFN($media, $rev); } //check file existance if(!file_exists($file)) { return array(404, 'Not Found'); } return array(200, null); } /** * Returns the wanted cachetime in seconds * * Resolves named constants * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $cache * @return int cachetime in seconds */ function calc_cache($cache) { global $conf; if(strtolower($cache) == 'nocache') return 0; //never cache if(strtolower($cache) == 'recache') return $conf['cachetime']; //use standard cache return -1; //cache endless } ActionRouter.php 0000644 00000015507 15233462216 0007707 0 ustar 00 <?php namespace dokuwiki; use dokuwiki\Action\AbstractAction; use dokuwiki\Action\Exception\ActionDisabledException; use dokuwiki\Action\Exception\ActionException; use dokuwiki\Action\Exception\FatalException; use dokuwiki\Action\Exception\NoActionException; use dokuwiki\Action\Plugin; /** * Class ActionRouter * @package dokuwiki */ class ActionRouter { /** @var AbstractAction */ protected $action; /** @var ActionRouter */ protected static $instance = null; /** @var int transition counter */ protected $transitions = 0; /** maximum loop */ const MAX_TRANSITIONS = 5; /** @var string[] the actions disabled in the configuration */ protected $disabled; /** * ActionRouter constructor. Singleton, thus protected! * * Sets up the correct action based on the $ACT global. Writes back * the selected action to $ACT */ protected function __construct() { global $ACT; global $conf; $this->disabled = explode(',', $conf['disableactions']); $this->disabled = array_map('trim', $this->disabled); $this->transitions = 0; $ACT = act_clean($ACT); $this->setupAction($ACT); $ACT = $this->action->getActionName(); } /** * Get the singleton instance * * @param bool $reinit * @return ActionRouter */ public static function getInstance($reinit = false) { if((self::$instance === null) || $reinit) { self::$instance = new ActionRouter(); } return self::$instance; } /** * Setup the given action * * Instantiates the right class, runs permission checks and pre-processing and * sets $action * * @param string $actionname this is passed as a reference to $ACT, for plugin backward compatibility * @triggers ACTION_ACT_PREPROCESS */ protected function setupAction(&$actionname) { $presetup = $actionname; try { // give plugins an opportunity to process the actionname $evt = new Extension\Event('ACTION_ACT_PREPROCESS', $actionname); if ($evt->advise_before()) { $this->action = $this->loadAction($actionname); $this->checkAction($this->action); $this->action->preProcess(); } else { // event said the action should be kept, assume action plugin will handle it later $this->action = new Plugin($actionname); } $evt->advise_after(); } catch(ActionException $e) { // we should have gotten a new action $actionname = $e->getNewAction(); // this one should trigger a user message if(is_a($e, ActionDisabledException::class)) { msg('Action disabled: ' . hsc($presetup), -1); } // some actions may request the display of a message if($e->displayToUser()) { msg(hsc($e->getMessage()), -1); } // do setup for new action $this->transitionAction($presetup, $actionname); } catch(NoActionException $e) { msg('Action unknown: ' . hsc($actionname), -1); $actionname = 'show'; $this->transitionAction($presetup, $actionname); } catch(\Exception $e) { $this->handleFatalException($e); } } /** * Transitions from one action to another * * Basically just calls setupAction() again but does some checks before. * * @param string $from current action name * @param string $to new action name * @param null|ActionException $e any previous exception that caused the transition */ protected function transitionAction($from, $to, $e = null) { $this->transitions++; // no infinite recursion if($from == $to) { $this->handleFatalException(new FatalException('Infinite loop in actions', 500, $e)); } // larger loops will be caught here if($this->transitions >= self::MAX_TRANSITIONS) { $this->handleFatalException(new FatalException('Maximum action transitions reached', 500, $e)); } // do the recursion $this->setupAction($to); } /** * Aborts all processing with a message * * When a FataException instanc is passed, the code is treated as Status code * * @param \Exception|FatalException $e * @throws FatalException during unit testing */ protected function handleFatalException(\Exception $e) { if(is_a($e, FatalException::class)) { http_status($e->getCode()); } else { http_status(500); } if(defined('DOKU_UNITTEST')) { throw $e; } $msg = 'Something unforeseen has happened: ' . $e->getMessage(); nice_die(hsc($msg)); } /** * Load the given action * * This translates the given name to a class name by uppercasing the first letter. * Underscores translate to camelcase names. For actions with underscores, the different * parts are removed beginning from the end until a matching class is found. The instatiated * Action will always have the full original action set as Name * * Example: 'export_raw' -> ExportRaw then 'export' -> 'Export' * * @param $actionname * @return AbstractAction * @throws NoActionException */ public function loadAction($actionname) { $actionname = strtolower($actionname); // FIXME is this needed here? should we run a cleanup somewhere else? $parts = explode('_', $actionname); while(!empty($parts)) { $load = join('_', $parts); $class = 'dokuwiki\\Action\\' . str_replace('_', '', ucwords($load, '_')); if(class_exists($class)) { return new $class($actionname); } array_pop($parts); } throw new NoActionException(); } /** * Execute all the checks to see if this action can be executed * * @param AbstractAction $action * @throws ActionDisabledException * @throws ActionException */ public function checkAction(AbstractAction $action) { global $INFO; global $ID; if(in_array($action->getActionName(), $this->disabled)) { throw new ActionDisabledException(); } $action->checkPreconditions(); if(isset($INFO)) { $perm = $INFO['perm']; } else { $perm = auth_quickaclcheck($ID); } if($perm < $action->minimumPermission()) { throw new ActionException('denied'); } } /** * Returns the action handling the current request * * @return AbstractAction */ public function getAction() { return $this->action; } } form.php 0000644 00000106473 15233462216 0006237 0 ustar 00 <?php /** * DokuWiki XHTML Form * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Tom N Harris <tnharris@whoopdedo.org> */ // phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps // phpcs:disable PSR2.Classes.PropertyDeclaration.Underscore /** * Class for creating simple HTML forms. * * The forms is built from a list of pseudo-tags (arrays with expected keys). * Every pseudo-tag must have the key '_elem' set to the name of the element. * When printed, the form class calls functions named 'form_$type' for each * element it contains. * * Standard practice is for non-attribute keys in a pseudo-element to start * with '_'. Other keys are HTML attributes that will be included in the element * tag. That way, the element output functions can pass the pseudo-element * directly to buildAttributes. * * See the form_make* functions later in this file. * * Please note that even though this class is technically deprecated (use dokuwiki\Form instead), * it is still widely used in the core and the related form events. Until those have been rewritten, * this will continue to be used * * @deprecated 2019-07-14 * @author Tom N Harris <tnharris@whoopdedo.org> */ class Doku_Form { // Form id attribute public $params = array(); // Draw a border around form fields. // Adds <fieldset></fieldset> around the elements public $_infieldset = false; // Hidden form fields. public $_hidden = array(); // Array of pseudo-tags public $_content = array(); /** * Constructor * * Sets parameters and autoadds a security token. The old calling convention * with up to four parameters is deprecated, instead the first parameter * should be an array with parameters. * * @param mixed $params Parameters for the HTML form element; Using the deprecated * calling convention this is the ID attribute of the form * @param bool|string $action (optional, deprecated) submit URL, defaults to current page * @param bool|string $method (optional, deprecated) 'POST' or 'GET', default is POST * @param bool|string $enctype (optional, deprecated) Encoding type of the data * * @author Tom N Harris <tnharris@whoopdedo.org> */ public function __construct($params, $action=false, $method=false, $enctype=false) { if(!is_array($params)) { $this->params = array('id' => $params); if ($action !== false) $this->params['action'] = $action; if ($method !== false) $this->params['method'] = strtolower($method); if ($enctype !== false) $this->params['enctype'] = $enctype; } else { $this->params = $params; } if (!isset($this->params['method'])) { $this->params['method'] = 'post'; } else { $this->params['method'] = strtolower($this->params['method']); } if (!isset($this->params['action'])) { $this->params['action'] = ''; } $this->addHidden('sectok', getSecurityToken()); } /** * startFieldset * * Add <fieldset></fieldset> tags around fields. * Usually results in a border drawn around the form. * * @param string $legend Label that will be printed with the border. * * @author Tom N Harris <tnharris@whoopdedo.org> */ public function startFieldset($legend) { if ($this->_infieldset) { $this->addElement(array('_elem'=>'closefieldset')); } $this->addElement(array('_elem'=>'openfieldset', '_legend'=>$legend)); $this->_infieldset = true; } /** * endFieldset * * @author Tom N Harris <tnharris@whoopdedo.org> */ public function endFieldset() { if ($this->_infieldset) { $this->addElement(array('_elem'=>'closefieldset')); } $this->_infieldset = false; } /** * addHidden * * Adds a name/value pair as a hidden field. * The value of the field (but not the name) will be passed to * formText() before printing. * * @param string $name Field name. * @param string $value Field value. If null, remove a previously added field. * * @author Tom N Harris <tnharris@whoopdedo.org> */ public function addHidden($name, $value) { if (is_null($value)) unset($this->_hidden[$name]); else $this->_hidden[$name] = $value; } /** * addElement * * Appends a content element to the form. * The element can be either a pseudo-tag or string. * If string, it is printed without escaping special chars. * * * @param string|array $elem Pseudo-tag or string to add to the form. * * @author Tom N Harris <tnharris@whoopdedo.org> */ public function addElement($elem) { $this->_content[] = $elem; } /** * insertElement * * Inserts a content element at a position. * * @param string $pos 0-based index where the element will be inserted. * @param string|array $elem Pseudo-tag or string to add to the form. * * @author Tom N Harris <tnharris@whoopdedo.org> */ public function insertElement($pos, $elem) { array_splice($this->_content, $pos, 0, array($elem)); } /** * replaceElement * * Replace with NULL to remove an element. * * @param int $pos 0-based index the element will be placed at. * @param string|array $elem Pseudo-tag or string to add to the form. * * @author Tom N Harris <tnharris@whoopdedo.org> */ public function replaceElement($pos, $elem) { $rep = array(); if (!is_null($elem)) $rep[] = $elem; array_splice($this->_content, $pos, 1, $rep); } /** * findElementByType * * Gets the position of the first of a type of element. * * @param string $type Element type to look for. * @return int|false position of element if found, otherwise false * * @author Tom N Harris <tnharris@whoopdedo.org> */ public function findElementByType($type) { foreach ($this->_content as $pos=>$elem) { if (is_array($elem) && $elem['_elem'] == $type) return $pos; } return false; } /** * findElementById * * Gets the position of the element with an ID attribute. * * @param string $id ID of the element to find. * @return int|false position of element if found, otherwise false * * @author Tom N Harris <tnharris@whoopdedo.org> */ public function findElementById($id) { foreach ($this->_content as $pos=>$elem) { if (is_array($elem) && isset($elem['id']) && $elem['id'] == $id) return $pos; } return false; } /** * findElementByAttribute * * Gets the position of the first element with a matching attribute value. * * @param string $name Attribute name. * @param string $value Attribute value. * @return int|false position of element if found, otherwise false * * @author Tom N Harris <tnharris@whoopdedo.org> */ public function findElementByAttribute($name, $value) { foreach ($this->_content as $pos=>$elem) { if (is_array($elem) && isset($elem[$name]) && $elem[$name] == $value) return $pos; } return false; } /** * getElementAt * * Returns a reference to the element at a position. * A position out-of-bounds will return either the * first (underflow) or last (overflow) element. * * @param int $pos 0-based index * @return array reference pseudo-element * * @author Tom N Harris <tnharris@whoopdedo.org> */ public function &getElementAt($pos) { if ($pos < 0) $pos = count($this->_content) + $pos; if ($pos < 0) $pos = 0; if ($pos >= count($this->_content)) $pos = count($this->_content) - 1; return $this->_content[$pos]; } /** * Return the assembled HTML for the form. * * Each element in the form will be passed to a function named * 'form_$type'. The function should return the HTML to be printed. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @return string html of the form */ public function getForm() { global $lang; $form = ''; $this->params['accept-charset'] = $lang['encoding']; $form .= '<form ' . buildAttributes($this->params,false) . '><div class="no">' . DOKU_LF; if (!empty($this->_hidden)) { foreach ($this->_hidden as $name=>$value) $form .= form_hidden(array('name'=>$name, 'value'=>$value)); } foreach ($this->_content as $element) { if (is_array($element)) { $elem_type = $element['_elem']; if (function_exists('form_'.$elem_type)) { $form .= call_user_func('form_'.$elem_type, $element).DOKU_LF; } } else { $form .= $element; } } if ($this->_infieldset) $form .= form_closefieldset().DOKU_LF; $form .= '</div></form>'.DOKU_LF; return $form; } /** * Print the assembled form * * wraps around getForm() */ public function printForm(){ echo $this->getForm(); } /** * Add a radio set * * This function adds a set of radio buttons to the form. If $_POST[$name] * is set, this radio is preselected, else the first radio button. * * @param string $name The HTML field name * @param array $entries An array of entries $value => $caption * * @author Adrian Lang <lang@cosmocode.de> */ public function addRadioSet($name, $entries) { global $INPUT; $value = (array_key_exists($INPUT->post->str($name), $entries)) ? $INPUT->str($name) : key($entries); foreach($entries as $val => $cap) { $data = ($value === $val) ? array('checked' => 'checked') : array(); $this->addElement(form_makeRadioField($name, $val, $cap, '', '', $data)); } } } /** * form_makeTag * * Create a form element for a non-specific empty tag. * * @param string $tag Tag name. * @param array $attrs Optional attributes. * @return array pseudo-tag * * @author Tom N Harris <tnharris@whoopdedo.org> */ function form_makeTag($tag, $attrs=array()) { $elem = array('_elem'=>'tag', '_tag'=>$tag); return array_merge($elem, $attrs); } /** * form_makeOpenTag * * Create a form element for a non-specific opening tag. * Remember to put a matching close tag after this as well. * * @param string $tag Tag name. * @param array $attrs Optional attributes. * @return array pseudo-tag * * @author Tom N Harris <tnharris@whoopdedo.org> */ function form_makeOpenTag($tag, $attrs=array()) { $elem = array('_elem'=>'opentag', '_tag'=>$tag); return array_merge($elem, $attrs); } /** * form_makeCloseTag * * Create a form element for a non-specific closing tag. * Careless use of this will result in invalid XHTML. * * @param string $tag Tag name. * @return array pseudo-tag * * @author Tom N Harris <tnharris@whoopdedo.org> */ function form_makeCloseTag($tag) { return array('_elem'=>'closetag', '_tag'=>$tag); } /** * form_makeWikiText * * Create a form element for a textarea containing wiki text. * Only one wikitext element is allowed on a page. It will have * a name of 'wikitext' and id 'wiki__text'. The text will * be passed to formText() before printing. * * @param string $text Text to fill the field with. * @param array $attrs Optional attributes. * @return array pseudo-tag * * @author Tom N Harris <tnharris@whoopdedo.org> */ function form_makeWikiText($text, $attrs=array()) { $elem = array('_elem'=>'wikitext', '_text'=>$text, 'class'=>'edit', 'cols'=>'80', 'rows'=>'10'); return array_merge($elem, $attrs); } /** * form_makeButton * * Create a form element for an action button. * A title will automatically be generated using the value and * accesskey attributes, unless you provide one. * * @param string $type Type attribute. 'submit' or 'cancel' * @param string $act Wiki action of the button, will be used as the do= parameter * @param string $value (optional) Displayed label. Uses $act if not provided. * @param array $attrs Optional attributes. * @return array pseudo-tag * * @author Tom N Harris <tnharris@whoopdedo.org> */ function form_makeButton($type, $act, $value='', $attrs=array()) { if ($value == '') $value = $act; $elem = array('_elem'=>'button', 'type'=>$type, '_action'=>$act, 'value'=>$value); if (!empty($attrs['accesskey']) && empty($attrs['title'])) { $attrs['title'] = $value . ' ['.strtoupper($attrs['accesskey']).']'; } return array_merge($elem, $attrs); } /** * form_makeField * * Create a form element for a labelled input element. * The label text will be printed before the input. * * @param string $type Type attribute of input. * @param string $name Name attribute of the input. * @param string $value (optional) Default value. * @param string $class Class attribute of the label. If this is 'block', * then a line break will be added after the field. * @param string $label Label that will be printed before the input. * @param string $id ID attribute of the input. If set, the label will * reference it with a 'for' attribute. * @param array $attrs Optional attributes. * @return array pseudo-tag * * @author Tom N Harris <tnharris@whoopdedo.org> */ function form_makeField($type, $name, $value='', $label=null, $id='', $class='', $attrs=array()) { if (is_null($label)) $label = $name; $elem = array('_elem'=>'field', '_text'=>$label, '_class'=>$class, 'type'=>$type, 'id'=>$id, 'name'=>$name, 'value'=>$value); return array_merge($elem, $attrs); } /** * form_makeFieldRight * * Create a form element for a labelled input element. * The label text will be printed after the input. * * @see form_makeField * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $type * @param string $name * @param string $value * @param null|string $label * @param string $id * @param string $class * @param array $attrs * * @return array */ function form_makeFieldRight($type, $name, $value='', $label=null, $id='', $class='', $attrs=array()) { if (is_null($label)) $label = $name; $elem = array('_elem'=>'fieldright', '_text'=>$label, '_class'=>$class, 'type'=>$type, 'id'=>$id, 'name'=>$name, 'value'=>$value); return array_merge($elem, $attrs); } /** * form_makeTextField * * Create a form element for a text input element with label. * * @see form_makeField * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $name * @param string $value * @param null|string $label * @param string $id * @param string $class * @param array $attrs * * @return array */ function form_makeTextField($name, $value='', $label=null, $id='', $class='', $attrs=array()) { if (is_null($label)) $label = $name; $elem = array('_elem'=>'textfield', '_text'=>$label, '_class'=>$class, 'id'=>$id, 'name'=>$name, 'value'=>$value, 'class'=>'edit'); return array_merge($elem, $attrs); } /** * form_makePasswordField * * Create a form element for a password input element with label. * Password elements have no default value, for obvious reasons. * * @see form_makeField * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $name * @param null|string $label * @param string $id * @param string $class * @param array $attrs * * @return array */ function form_makePasswordField($name, $label=null, $id='', $class='', $attrs=array()) { if (is_null($label)) $label = $name; $elem = array('_elem'=>'passwordfield', '_text'=>$label, '_class'=>$class, 'id'=>$id, 'name'=>$name, 'class'=>'edit'); return array_merge($elem, $attrs); } /** * form_makeFileField * * Create a form element for a file input element with label * * @see form_makeField * @author Michael Klier <chi@chimeric.de> * * @param string $name * @param null|string $label * @param string $id * @param string $class * @param array $attrs * * @return array */ function form_makeFileField($name, $label=null, $id='', $class='', $attrs=array()) { if (is_null($label)) $label = $name; $elem = array('_elem'=>'filefield', '_text'=>$label, '_class'=>$class, 'id'=>$id, 'name'=>$name, 'class'=>'edit'); return array_merge($elem, $attrs); } /** * form_makeCheckboxField * * Create a form element for a checkbox input element with label. * If $value is an array, a hidden field with the same name and the value * $value[1] is constructed as well. * * @see form_makeFieldRight * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $name * @param string $value * @param null|string $label * @param string $id * @param string $class * @param array $attrs * * @return array */ function form_makeCheckboxField($name, $value='1', $label=null, $id='', $class='', $attrs=array()) { if (is_null($label)) $label = $name; if (is_null($value) || $value=='') $value='0'; $elem = array('_elem'=>'checkboxfield', '_text'=>$label, '_class'=>$class, 'id'=>$id, 'name'=>$name, 'value'=>$value); return array_merge($elem, $attrs); } /** * form_makeRadioField * * Create a form element for a radio button input element with label. * * @see form_makeFieldRight * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $name * @param string $value * @param null|string $label * @param string $id * @param string $class * @param array $attrs * * @return array */ function form_makeRadioField($name, $value='1', $label=null, $id='', $class='', $attrs=array()) { if (is_null($label)) $label = $name; if (is_null($value) || $value=='') $value='0'; $elem = array('_elem'=>'radiofield', '_text'=>$label, '_class'=>$class, 'id'=>$id, 'name'=>$name, 'value'=>$value); return array_merge($elem, $attrs); } /** * form_makeMenuField * * Create a form element for a drop-down menu with label. * The list of values can be strings, arrays of (value,text), * or an associative array with the values as keys and labels as values. * An item is selected by supplying its value or integer index. * If the list of values is an associative array, the selected item must be * a string. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $name Name attribute of the input. * @param string[]|array[] $values The list of values can be strings, arrays of (value,text), * or an associative array with the values as keys and labels as values. * @param string|int $selected default selected value, string or index number * @param string $class Class attribute of the label. If this is 'block', * then a line break will be added after the field. * @param string $label Label that will be printed before the input. * @param string $id ID attribute of the input. If set, the label will * reference it with a 'for' attribute. * @param array $attrs Optional attributes. * @return array pseudo-tag */ function form_makeMenuField($name, $values, $selected='', $label=null, $id='', $class='', $attrs=array()) { if (is_null($label)) $label = $name; $options = array(); reset($values); // FIXME: php doesn't know the difference between a string and an integer if (is_string(key($values))) { foreach ($values as $val=>$text) { $options[] = array($val,$text, (!is_null($selected) && $val==$selected)); } } else { if (is_integer($selected)) $selected = $values[$selected]; foreach ($values as $val) { if (is_array($val)) @list($val,$text) = $val; else $text = null; $options[] = array($val,$text,$val===$selected); } } $elem = array('_elem'=>'menufield', '_options'=>$options, '_text'=>$label, '_class'=>$class, 'id'=>$id, 'name'=>$name); return array_merge($elem, $attrs); } /** * form_makeListboxField * * Create a form element for a list box with label. * The list of values can be strings, arrays of (value,text), * or an associative array with the values as keys and labels as values. * Items are selected by supplying its value or an array of values. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $name Name attribute of the input. * @param string[]|array[] $values The list of values can be strings, arrays of (value,text), * or an associative array with the values as keys and labels as values. * @param array|string $selected value or array of values of the items that need to be selected * @param string $class Class attribute of the label. If this is 'block', * then a line break will be added after the field. * @param string $label Label that will be printed before the input. * @param string $id ID attribute of the input. If set, the label will * reference it with a 'for' attribute. * @param array $attrs Optional attributes. * @return array pseudo-tag */ function form_makeListboxField($name, $values, $selected='', $label=null, $id='', $class='', $attrs=array()) { if (is_null($label)) $label = $name; $options = array(); reset($values); if (is_null($selected) || $selected == '') { $selected = array(); } elseif (!is_array($selected)) { $selected = array($selected); } // FIXME: php doesn't know the difference between a string and an integer if (is_string(key($values))) { foreach ($values as $val=>$text) { $options[] = array($val,$text,in_array($val,$selected)); } } else { foreach ($values as $val) { $disabled = false; if (is_array($val)) { @list($val,$text,$disabled) = $val; } else { $text = null; } $options[] = array($val,$text,in_array($val,$selected),$disabled); } } $elem = array('_elem'=>'listboxfield', '_options'=>$options, '_text'=>$label, '_class'=>$class, 'id'=>$id, 'name'=>$name); return array_merge($elem, $attrs); } /** * form_tag * * Print the HTML for a generic empty tag. * Requires '_tag' key with name of the tag. * Attributes are passed to buildAttributes() * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html of tag */ function form_tag($attrs) { return '<'.$attrs['_tag'].' '.buildAttributes($attrs,true).'/>'; } /** * form_opentag * * Print the HTML for a generic opening tag. * Requires '_tag' key with name of the tag. * Attributes are passed to buildAttributes() * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html of tag */ function form_opentag($attrs) { return '<'.$attrs['_tag'].' '.buildAttributes($attrs,true).'>'; } /** * form_closetag * * Print the HTML for a generic closing tag. * Requires '_tag' key with name of the tag. * There are no attributes. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html of tag */ function form_closetag($attrs) { return '</'.$attrs['_tag'].'>'; } /** * form_openfieldset * * Print the HTML for an opening fieldset tag. * Uses the '_legend' key. * Attributes are passed to buildAttributes() * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html */ function form_openfieldset($attrs) { $s = '<fieldset '.buildAttributes($attrs,true).'>'; if (!is_null($attrs['_legend'])) $s .= '<legend>'.$attrs['_legend'].'</legend>'; return $s; } /** * form_closefieldset * * Print the HTML for a closing fieldset tag. * There are no attributes. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @return string html */ function form_closefieldset() { return '</fieldset>'; } /** * form_hidden * * Print the HTML for a hidden input element. * Uses only 'name' and 'value' attributes. * Value is passed to formText() * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html */ function form_hidden($attrs) { return '<input type="hidden" name="'.$attrs['name'].'" value="'.formText($attrs['value']).'" />'; } /** * form_wikitext * * Print the HTML for the wiki textarea. * Requires '_text' with default text of the field. * Text will be passed to formText(), attributes to buildAttributes() * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html */ function form_wikitext($attrs) { // mandatory attributes unset($attrs['name']); unset($attrs['id']); return '<textarea name="wikitext" id="wiki__text" dir="auto" ' .buildAttributes($attrs,true).'>'.DOKU_LF .formText($attrs['_text']) .'</textarea>'; } /** * form_button * * Print the HTML for a form button. * If '_action' is set, the button name will be "do[_action]". * Other attributes are passed to buildAttributes() * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html */ function form_button($attrs) { $p = (!empty($attrs['_action'])) ? 'name="do['.$attrs['_action'].']" ' : ''; $value = $attrs['value']; unset($attrs['value']); return '<button '.$p.buildAttributes($attrs,true).'>'.$value.'</button>'; } /** * form_field * * Print the HTML for a form input field. * _class : class attribute used on the label tag * _text : Text to display before the input. Not escaped. * Other attributes are passed to buildAttributes() for the input tag. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html */ function form_field($attrs) { $s = '<label'; if ($attrs['_class']) $s .= ' class="'.$attrs['_class'].'"'; if (!empty($attrs['id'])) $s .= ' for="'.$attrs['id'].'"'; $s .= '><span>'.$attrs['_text'].'</span>'; $s .= ' <input '.buildAttributes($attrs,true).' /></label>'; if (preg_match('/(^| )block($| )/', $attrs['_class'])) $s .= '<br />'; return $s; } /** * form_fieldright * * Print the HTML for a form input field. (right-aligned) * _class : class attribute used on the label tag * _text : Text to display after the input. Not escaped. * Other attributes are passed to buildAttributes() for the input tag. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html */ function form_fieldright($attrs) { $s = '<label'; if ($attrs['_class']) $s .= ' class="'.$attrs['_class'].'"'; if (!empty($attrs['id'])) $s .= ' for="'.$attrs['id'].'"'; $s .= '><input '.buildAttributes($attrs,true).' />'; $s .= ' <span>'.$attrs['_text'].'</span></label>'; if (preg_match('/(^| )block($| )/', $attrs['_class'])) $s .= '<br />'; return $s; } /** * form_textfield * * Print the HTML for a text input field. * _class : class attribute used on the label tag * _text : Text to display before the input. Not escaped. * Other attributes are passed to buildAttributes() for the input tag. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html */ function form_textfield($attrs) { // mandatory attributes unset($attrs['type']); $s = '<label'; if ($attrs['_class']) $s .= ' class="'.$attrs['_class'].'"'; if (!empty($attrs['id'])) $s .= ' for="'.$attrs['id'].'"'; $s .= '><span>'.$attrs['_text'].'</span> '; $s .= '<input type="text" '.buildAttributes($attrs,true).' /></label>'; if (preg_match('/(^| )block($| )/', $attrs['_class'])) $s .= '<br />'; return $s; } /** * form_passwordfield * * Print the HTML for a password input field. * _class : class attribute used on the label tag * _text : Text to display before the input. Not escaped. * Other attributes are passed to buildAttributes() for the input tag. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html */ function form_passwordfield($attrs) { // mandatory attributes unset($attrs['type']); $s = '<label'; if ($attrs['_class']) $s .= ' class="'.$attrs['_class'].'"'; if (!empty($attrs['id'])) $s .= ' for="'.$attrs['id'].'"'; $s .= '><span>'.$attrs['_text'].'</span> '; $s .= '<input type="password" '.buildAttributes($attrs,true).' /></label>'; if (preg_match('/(^| )block($| )/', $attrs['_class'])) $s .= '<br />'; return $s; } /** * form_filefield * * Print the HTML for a file input field. * _class : class attribute used on the label tag * _text : Text to display before the input. Not escaped * _maxlength : Allowed size in byte * _accept : Accepted mime-type * Other attributes are passed to buildAttributes() for the input tag * * @author Michael Klier <chi@chimeric.de> * * @param array $attrs attributes * @return string html */ function form_filefield($attrs) { $s = '<label'; if ($attrs['_class']) $s .= ' class="'.$attrs['_class'].'"'; if (!empty($attrs['id'])) $s .= ' for="'.$attrs['id'].'"'; $s .= '><span>'.$attrs['_text'].'</span> '; $s .= '<input type="file" '.buildAttributes($attrs,true); if (!empty($attrs['_maxlength'])) $s .= ' maxlength="'.$attrs['_maxlength'].'"'; if (!empty($attrs['_accept'])) $s .= ' accept="'.$attrs['_accept'].'"'; $s .= ' /></label>'; if (preg_match('/(^| )block($| )/', $attrs['_class'])) $s .= '<br />'; return $s; } /** * form_checkboxfield * * Print the HTML for a checkbox input field. * _class : class attribute used on the label tag * _text : Text to display after the input. Not escaped. * Other attributes are passed to buildAttributes() for the input tag. * If value is an array, a hidden field with the same name and the value * $attrs['value'][1] is constructed as well. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html */ function form_checkboxfield($attrs) { // mandatory attributes unset($attrs['type']); $s = '<label'; if ($attrs['_class']) $s .= ' class="'.$attrs['_class'].'"'; if (!empty($attrs['id'])) $s .= ' for="'.$attrs['id'].'"'; $s .= '>'; if (is_array($attrs['value'])) { echo '<input type="hidden" name="' . hsc($attrs['name']) .'"' . ' value="' . hsc($attrs['value'][1]) . '" />'; $attrs['value'] = $attrs['value'][0]; } $s .= '<input type="checkbox" '.buildAttributes($attrs,true).' />'; $s .= ' <span>'.$attrs['_text'].'</span></label>'; if (preg_match('/(^| )block($| )/', $attrs['_class'])) $s .= '<br />'; return $s; } /** * form_radiofield * * Print the HTML for a radio button input field. * _class : class attribute used on the label tag * _text : Text to display after the input. Not escaped. * Other attributes are passed to buildAttributes() for the input tag. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html */ function form_radiofield($attrs) { // mandatory attributes unset($attrs['type']); $s = '<label'; if ($attrs['_class']) $s .= ' class="'.$attrs['_class'].'"'; if (!empty($attrs['id'])) $s .= ' for="'.$attrs['id'].'"'; $s .= '><input type="radio" '.buildAttributes($attrs,true).' />'; $s .= ' <span>'.$attrs['_text'].'</span></label>'; if (preg_match('/(^| )block($| )/', $attrs['_class'])) $s .= '<br />'; return $s; } /** * form_menufield * * Print the HTML for a drop-down menu. * _options : Array of (value,text,selected) for the menu. * Text can be omitted. Text and value are passed to formText() * Only one item can be selected. * _class : class attribute used on the label tag * _text : Text to display before the menu. Not escaped. * Other attributes are passed to buildAttributes() for the input tag. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html */ function form_menufield($attrs) { $attrs['size'] = '1'; $s = '<label'; if ($attrs['_class']) $s .= ' class="'.$attrs['_class'].'"'; if (!empty($attrs['id'])) $s .= ' for="'.$attrs['id'].'"'; $s .= '><span>'.$attrs['_text'].'</span>'; $s .= ' <select '.buildAttributes($attrs,true).'>'.DOKU_LF; if (!empty($attrs['_options'])) { $selected = false; $cnt = count($attrs['_options']); for($n=0; $n < $cnt; $n++){ @list($value,$text,$select) = $attrs['_options'][$n]; $p = ''; if (!is_null($text)) $p .= ' value="'.formText($value).'"'; else $text = $value; if (!empty($select) && !$selected) { $p .= ' selected="selected"'; $selected = true; } $s .= '<option'.$p.'>'.formText($text).'</option>'; } } else { $s .= '<option></option>'; } $s .= DOKU_LF.'</select></label>'; if (preg_match('/(^| )block($| )/', $attrs['_class'])) $s .= '<br />'; return $s; } /** * form_listboxfield * * Print the HTML for a list box. * _options : Array of (value,text,selected) for the list. * Text can be omitted. Text and value are passed to formText() * _class : class attribute used on the label tag * _text : Text to display before the menu. Not escaped. * Other attributes are passed to buildAttributes() for the input tag. * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param array $attrs attributes * @return string html */ function form_listboxfield($attrs) { $s = '<label'; if ($attrs['_class']) $s .= ' class="'.$attrs['_class'].'"'; if (!empty($attrs['id'])) $s .= ' for="'.$attrs['id'].'"'; $s .= '><span>'.$attrs['_text'].'</span> '; $s .= '<select '.buildAttributes($attrs,true).'>'.DOKU_LF; if (!empty($attrs['_options'])) { foreach ($attrs['_options'] as $opt) { @list($value,$text,$select,$disabled) = $opt; $p = ''; if(is_null($text)) $text = $value; $p .= ' value="'.formText($value).'"'; if (!empty($select)) $p .= ' selected="selected"'; if ($disabled) $p .= ' disabled="disabled"'; $s .= '<option'.$p.'>'.formText($text).'</option>'; } } else { $s .= '<option></option>'; } $s .= DOKU_LF.'</select></label>'; if (preg_match('/(^| )block($| )/', $attrs['_class'])) $s .= '<br />'; return $s; } html.php 0000644 00000223630 15233462216 0006233 0 ustar 00 <?php /** * HTML output functions * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ use dokuwiki\ChangeLog\MediaChangeLog; use dokuwiki\ChangeLog\PageChangeLog; use dokuwiki\Extension\AuthPlugin; use dokuwiki\Extension\Event; if (!defined('SEC_EDIT_PATTERN')) { define('SEC_EDIT_PATTERN', '#<!-- EDIT({.*?}) -->#'); } /** * Convenience function to quickly build a wikilink * * @author Andreas Gohr <andi@splitbrain.org> * @param string $id id of the target page * @param string $name the name of the link, i.e. the text that is displayed * @param string|array $search search string(s) that shall be highlighted in the target page * @return string the HTML code of the link */ function html_wikilink($id,$name=null,$search=''){ /** @var Doku_Renderer_xhtml $xhtml_renderer */ static $xhtml_renderer = null; if(is_null($xhtml_renderer)){ $xhtml_renderer = p_get_renderer('xhtml'); } return $xhtml_renderer->internallink($id,$name,$search,true,'navigation'); } /** * The loginform * * @author Andreas Gohr <andi@splitbrain.org> * * @param bool $svg Whether to show svg icons in the register and resendpwd links or not */ function html_login($svg = false){ global $lang; global $conf; global $ID; global $INPUT; print p_locale_xhtml('login'); print '<div class="centeralign">'.NL; $form = new Doku_Form(array('id' => 'dw__login', 'action'=>wl($ID))); $form->startFieldset($lang['btn_login']); $form->addHidden('id', $ID); $form->addHidden('do', 'login'); $form->addElement(form_makeTextField( 'u', ((!$INPUT->bool('http_credentials')) ? $INPUT->str('u') : ''), $lang['user'], 'focus__this', 'block') ); $form->addElement(form_makePasswordField('p', $lang['pass'], '', 'block')); if($conf['rememberme']) { $form->addElement(form_makeCheckboxField('r', '1', $lang['remember'], 'remember__me', 'simple')); } $form->addElement(form_makeButton('submit', '', $lang['btn_login'])); $form->endFieldset(); if(actionOK('register')){ $registerLink = (new \dokuwiki\Menu\Item\Register())->asHtmlLink('', $svg); $form->addElement('<p>'.$lang['reghere'].': '. $registerLink .'</p>'); } if (actionOK('resendpwd')) { $resendPwLink = (new \dokuwiki\Menu\Item\Resendpwd())->asHtmlLink('', $svg); $form->addElement('<p>'.$lang['pwdforget'].': '. $resendPwLink .'</p>'); } html_form('login', $form); print '</div>'.NL; } /** * Denied page content * * @return string html */ function html_denied() { print p_locale_xhtml('denied'); if(empty($_SERVER['REMOTE_USER']) && actionOK('login')){ html_login(); } } /** * inserts section edit buttons if wanted or removes the markers * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $text * @param bool $show show section edit buttons? * @return string */ function html_secedit($text,$show=true){ global $INFO; if((isset($INFO) && !$INFO['writable']) || !$show || (isset($INFO) && $INFO['rev'])){ return preg_replace(SEC_EDIT_PATTERN,'',$text); } return preg_replace_callback(SEC_EDIT_PATTERN, 'html_secedit_button', $text); } /** * prepares section edit button data for event triggering * used as a callback in html_secedit * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $matches matches with regexp * @return string * @triggers HTML_SECEDIT_BUTTON */ function html_secedit_button($matches){ $json = htmlspecialchars_decode($matches[1], ENT_QUOTES); $data = json_decode($json, true); if ($data == NULL) { return; } $data ['target'] = strtolower($data['target']); $data ['hid'] = strtolower($data['hid']); return Event::createAndTrigger('HTML_SECEDIT_BUTTON', $data, 'html_secedit_get_button'); } /** * prints a section editing button * used as default action form HTML_SECEDIT_BUTTON * * @author Adrian Lang <lang@cosmocode.de> * * @param array $data name, section id and target * @return string html */ function html_secedit_get_button($data) { global $ID; global $INFO; if (!isset($data['name']) || $data['name'] === '') return ''; $name = $data['name']; unset($data['name']); $secid = $data['secid']; unset($data['secid']); return "<div class='secedit editbutton_" . $data['target'] . " editbutton_" . $secid . "'>" . html_btn('secedit', $ID, '', array_merge(array('do' => 'edit', 'rev' => $INFO['lastmod'], 'summary' => '['.$name.'] '), $data), 'post', $name) . '</div>'; } /** * Just the back to top button (in its own form) * * @author Andreas Gohr <andi@splitbrain.org> * * @return string html */ function html_topbtn(){ global $lang; $ret = '<a class="nolink" href="#dokuwiki__top">' . '<button class="button" onclick="window.scrollTo(0, 0)" title="' . $lang['btn_top'] . '">' . $lang['btn_top'] . '</button></a>'; return $ret; } /** * Displays a button (using its own form) * If tooltip exists, the access key tooltip is replaced. * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $name * @param string $id * @param string $akey access key * @param string[] $params key-value pairs added as hidden inputs * @param string $method * @param string $tooltip * @param bool|string $label label text, false: lookup btn_$name in localization * @param string $svg (optional) svg code, inserted into the button * @return string */ function html_btn($name, $id, $akey, $params, $method='get', $tooltip='', $label=false, $svg=null){ global $conf; global $lang; if (!$label) $label = $lang['btn_'.$name]; $ret = ''; //filter id (without urlencoding) $id = idfilter($id,false); //make nice URLs even for buttons if($conf['userewrite'] == 2){ $script = DOKU_BASE.DOKU_SCRIPT.'/'.$id; }elseif($conf['userewrite']){ $script = DOKU_BASE.$id; }else{ $script = DOKU_BASE.DOKU_SCRIPT; $params['id'] = $id; } $ret .= '<form class="button btn_'.$name.'" method="'.$method.'" action="'.$script.'"><div class="no">'; if(is_array($params)){ foreach($params as $key => $val) { $ret .= '<input type="hidden" name="'.$key.'" '; $ret .= 'value="'.hsc($val).'" />'; } } if ($tooltip!='') { $tip = hsc($tooltip); }else{ $tip = hsc($label); } $ret .= '<button type="submit" '; if($akey){ $tip .= ' ['.strtoupper($akey).']'; $ret .= 'accesskey="'.$akey.'" '; } $ret .= 'title="'.$tip.'">'; if ($svg) { $ret .= '<span>' . hsc($label) . '</span>'; $ret .= inlineSVG($svg); } else { $ret .= hsc($label); } $ret .= '</button>'; $ret .= '</div></form>'; return $ret; } /** * show a revision warning * * @author Szymon Olewniczak <dokuwiki@imz.re> */ function html_showrev() { print p_locale_xhtml('showrev'); } /** * Show a wiki page * * @author Andreas Gohr <andi@splitbrain.org> * * @param null|string $txt wiki text or null for showing $ID */ function html_show($txt=null){ global $ID; global $REV; global $HIGH; global $INFO; global $DATE_AT; //disable section editing for old revisions or in preview if($txt || $REV){ $secedit = false; }else{ $secedit = true; } if (!is_null($txt)){ //PreviewHeader echo '<br id="scroll__here" />'; echo p_locale_xhtml('preview'); echo '<div class="preview"><div class="pad">'; $html = html_secedit(p_render('xhtml',p_get_instructions($txt),$info),$secedit); if($INFO['prependTOC']) $html = tpl_toc(true).$html; echo $html; echo '<div class="clearer"></div>'; echo '</div></div>'; }else{ if ($REV||$DATE_AT){ $data = array('rev' => &$REV, 'date_at' => &$DATE_AT); Event::createAndTrigger('HTML_SHOWREV_OUTPUT', $data, 'html_showrev'); } $html = p_wiki_xhtml($ID,$REV,true,$DATE_AT); $html = html_secedit($html,$secedit); if($INFO['prependTOC']) $html = tpl_toc(true).$html; $html = html_hilight($html,$HIGH); echo $html; } } /** * ask the user about how to handle an exisiting draft * * @author Andreas Gohr <andi@splitbrain.org> */ function html_draft(){ global $INFO; global $ID; global $lang; $draft = new \dokuwiki\Draft($ID, $INFO['client']); $text = $draft->getDraftText(); print p_locale_xhtml('draft'); html_diff($text, false); $form = new Doku_Form(array('id' => 'dw__editform')); $form->addHidden('id', $ID); $form->addHidden('date', $draft->getDraftDate()); $form->addHidden('wikitext', $text); $form->addElement(form_makeOpenTag('div', array('id'=>'draft__status'))); $form->addElement($draft->getDraftMessage()); $form->addElement(form_makeCloseTag('div')); $form->addElement(form_makeButton('submit', 'recover', $lang['btn_recover'], array('tabindex'=>'1'))); $form->addElement(form_makeButton('submit', 'draftdel', $lang['btn_draftdel'], array('tabindex'=>'2'))); $form->addElement(form_makeButton('submit', 'show', $lang['btn_cancel'], array('tabindex'=>'3'))); html_form('draft', $form); } /** * Highlights searchqueries in HTML code * * @author Andreas Gohr <andi@splitbrain.org> * @author Harry Fuecks <hfuecks@gmail.com> * * @param string $html * @param array|string $phrases * @return string html */ function html_hilight($html,$phrases){ $phrases = (array) $phrases; $phrases = array_map('preg_quote_cb', $phrases); $phrases = array_map('ft_snippet_re_preprocess', $phrases); $phrases = array_filter($phrases); $regex = join('|',$phrases); if ($regex === '') return $html; if (!\dokuwiki\Utf8\Clean::isUtf8($regex)) return $html; $html = @preg_replace_callback("/((<[^>]*)|$regex)/ui",'html_hilight_callback',$html); return $html; } /** * Callback used by html_hilight() * * @author Harry Fuecks <hfuecks@gmail.com> * * @param array $m matches * @return string html */ function html_hilight_callback($m) { $hlight = unslash($m[0]); if ( !isset($m[2])) { $hlight = '<span class="search_hit">'.$hlight.'</span>'; } return $hlight; } /** * Display error on locked pages * * @author Andreas Gohr <andi@splitbrain.org> */ function html_locked(){ global $ID; global $conf; global $lang; global $INFO; $locktime = filemtime(wikiLockFN($ID)); $expire = dformat($locktime + $conf['locktime']); $min = round(($conf['locktime'] - (time() - $locktime) )/60); print p_locale_xhtml('locked'); print '<ul>'; print '<li><div class="li"><strong>'.$lang['lockedby'].'</strong> '.editorinfo($INFO['locked']).'</div></li>'; print '<li><div class="li"><strong>'.$lang['lockexpire'].'</strong> '.$expire.' ('.$min.' min)</div></li>'; print '</ul>'; } /** * list old revisions * * @author Andreas Gohr <andi@splitbrain.org> * @author Ben Coburn <btcoburn@silicodon.net> * @author Kate Arzamastseva <pshns@ukr.net> * * @param int $first skip the first n changelog lines * @param bool|string $media_id id of media, or false for current page */ function html_revisions($first=0, $media_id = false){ global $ID; global $INFO; global $conf; global $lang; $id = $ID; if ($media_id) { $id = $media_id; $changelog = new MediaChangeLog($id); } else { $changelog = new PageChangeLog($id); } /* we need to get one additional log entry to be able to * decide if this is the last page or is there another one. * see html_recent() */ $revisions = $changelog->getRevisions($first, $conf['recent']+1); if(count($revisions)==0 && $first!=0){ $first=0; $revisions = $changelog->getRevisions($first, $conf['recent']+1); } $hasNext = false; if (count($revisions)>$conf['recent']) { $hasNext = true; array_pop($revisions); // remove extra log entry } if (!$media_id) print p_locale_xhtml('revisions'); $params = array('id' => 'page__revisions', 'class' => 'changes'); if($media_id) { $params['action'] = media_managerURL(array('image' => $media_id), '&'); } if(!$media_id) { $exists = $INFO['exists']; $display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id; if(!$display_name) { $display_name = $id; } } else { $exists = file_exists(mediaFN($id)); $display_name = $id; } $form = new Doku_Form($params); $form->addElement(form_makeOpenTag('ul')); if($exists && $first == 0) { $minor = false; if($media_id) { $date = dformat(@filemtime(mediaFN($id))); $href = media_managerURL(array('image' => $id, 'tab_details' => 'view'), '&'); $changelog->setChunkSize(1024); $revinfo = $changelog->getRevisionInfo(@filemtime(fullpath(mediaFN($id)))); $summary = $revinfo['sum']; if($revinfo['user']) { $editor = $revinfo['user']; } else { $editor = $revinfo['ip']; } $sizechange = $revinfo['sizechange']; } else { $date = dformat($INFO['lastmod']); if(isset($INFO['meta']) && isset($INFO['meta']['last_change'])) { if($INFO['meta']['last_change']['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) { $minor = true; } if(isset($INFO['meta']['last_change']['sizechange'])) { $sizechange = $INFO['meta']['last_change']['sizechange']; } else { $sizechange = null; } } $pagelog = new PageChangeLog($ID); $latestrev = $pagelog->getRevisions(-1, 1); $latestrev = array_pop($latestrev); $href = wl($id,"rev=$latestrev",false,'&'); $summary = $INFO['sum']; $editor = $INFO['editor']; } $form->addElement(form_makeOpenTag('li', array('class' => ($minor ? 'minor' : '')))); $form->addElement(form_makeOpenTag('div', array('class' => 'li'))); $form->addElement(form_makeTag('input', array( 'type' => 'checkbox', 'name' => 'rev2[]', 'value' => 'current'))); $form->addElement(form_makeOpenTag('span', array('class' => 'date'))); $form->addElement($date); $form->addElement(form_makeCloseTag('span')); $form->addElement('<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />'); $form->addElement(form_makeOpenTag('a', array( 'class' => 'wikilink1', 'href' => $href))); $form->addElement($display_name); $form->addElement(form_makeCloseTag('a')); if ($media_id) $form->addElement(form_makeOpenTag('div')); if($summary) { $form->addElement(form_makeOpenTag('span', array('class' => 'sum'))); if(!$media_id) $form->addElement(' – '); $form->addElement('<bdi>' . hsc($summary) . '</bdi>'); $form->addElement(form_makeCloseTag('span')); } $form->addElement(form_makeOpenTag('span', array('class' => 'user'))); $form->addElement((empty($editor))?('('.$lang['external_edit'].')'):'<bdi>'.editorinfo($editor).'</bdi>'); $form->addElement(form_makeCloseTag('span')); html_sizechange($sizechange, $form); $form->addElement('('.$lang['current'].')'); if ($media_id) $form->addElement(form_makeCloseTag('div')); $form->addElement(form_makeCloseTag('div')); $form->addElement(form_makeCloseTag('li')); } foreach($revisions as $rev) { $date = dformat($rev); $info = $changelog->getRevisionInfo($rev); if($media_id) { $exists = file_exists(mediaFN($id, $rev)); } else { $exists = page_exists($id, $rev); } $class = ''; if($info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) { $class = 'minor'; } $form->addElement(form_makeOpenTag('li', array('class' => $class))); $form->addElement(form_makeOpenTag('div', array('class' => 'li'))); if($exists){ $form->addElement(form_makeTag('input', array( 'type' => 'checkbox', 'name' => 'rev2[]', 'value' => $rev))); }else{ $form->addElement('<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />'); } $form->addElement(form_makeOpenTag('span', array('class' => 'date'))); $form->addElement($date); $form->addElement(form_makeCloseTag('span')); if($exists){ if (!$media_id) { $href = wl($id,"rev=$rev,do=diff", false, '&'); } else { $href = media_managerURL(array('image' => $id, 'rev' => $rev, 'mediado' => 'diff'), '&'); } $form->addElement(form_makeOpenTag('a', array( 'class' => 'diff_link', 'href' => $href))); $form->addElement(form_makeTag('img', array( 'src' => DOKU_BASE.'lib/images/diff.png', 'width' => 15, 'height' => 11, 'title' => $lang['diff'], 'alt' => $lang['diff']))); $form->addElement(form_makeCloseTag('a')); if (!$media_id) { $href = wl($id,"rev=$rev",false,'&'); } else { $href = media_managerURL(array('image' => $id, 'tab_details' => 'view', 'rev' => $rev), '&'); } $form->addElement(form_makeOpenTag('a', array( 'class' => 'wikilink1', 'href' => $href))); $form->addElement($display_name); $form->addElement(form_makeCloseTag('a')); }else{ $form->addElement('<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />'); $form->addElement($display_name); } if ($media_id) $form->addElement(form_makeOpenTag('div')); if ($info['sum']) { $form->addElement(form_makeOpenTag('span', array('class' => 'sum'))); if(!$media_id) $form->addElement(' – '); $form->addElement('<bdi>'.hsc($info['sum']).'</bdi>'); $form->addElement(form_makeCloseTag('span')); } $form->addElement(form_makeOpenTag('span', array('class' => 'user'))); if($info['user']){ $form->addElement('<bdi>'.editorinfo($info['user']).'</bdi>'); if(auth_ismanager()){ $form->addElement(' <bdo dir="ltr">('.$info['ip'].')</bdo>'); } }else{ $form->addElement('<bdo dir="ltr">'.$info['ip'].'</bdo>'); } $form->addElement(form_makeCloseTag('span')); html_sizechange($info['sizechange'], $form); if ($media_id) $form->addElement(form_makeCloseTag('div')); $form->addElement(form_makeCloseTag('div')); $form->addElement(form_makeCloseTag('li')); } $form->addElement(form_makeCloseTag('ul')); if (!$media_id) { $form->addElement(form_makeButton('submit', 'diff', $lang['diff2'])); } else { $form->addHidden('mediado', 'diff'); $form->addElement(form_makeButton('submit', '', $lang['diff2'])); } html_form('revisions', $form); print '<div class="pagenav">'; $last = $first + $conf['recent']; if ($first > 0) { $first -= $conf['recent']; if ($first < 0) $first = 0; print '<div class="pagenav-prev">'; if ($media_id) { print html_btn('newer',$media_id,"p",media_managerURL(array('first' => $first), '&', false, true)); } else { print html_btn('newer',$id,"p",array('do' => 'revisions', 'first' => $first)); } print '</div>'; } if ($hasNext) { print '<div class="pagenav-next">'; if ($media_id) { print html_btn('older',$media_id,"n",media_managerURL(array('first' => $last), '&', false, true)); } else { print html_btn('older',$id,"n",array('do' => 'revisions', 'first' => $last)); } print '</div>'; } print '</div>'; } /** * display recent changes * * @author Andreas Gohr <andi@splitbrain.org> * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> * @author Ben Coburn <btcoburn@silicodon.net> * @author Kate Arzamastseva <pshns@ukr.net> * * @param int $first * @param string $show_changes */ function html_recent($first = 0, $show_changes = 'both') { global $conf; global $lang; global $ID; /* we need to get one additionally log entry to be able to * decide if this is the last page or is there another one. * This is the cheapest solution to get this information. */ $flags = 0; if($show_changes == 'mediafiles' && $conf['mediarevisions']) { $flags = RECENTS_MEDIA_CHANGES; } elseif($show_changes == 'pages') { $flags = 0; } elseif($conf['mediarevisions']) { $show_changes = 'both'; $flags = RECENTS_MEDIA_PAGES_MIXED; } $recents = getRecents($first, $conf['recent'] + 1, getNS($ID), $flags); if(count($recents) == 0 && $first != 0) { $first = 0; $recents = getRecents($first, $conf['recent'] + 1, getNS($ID), $flags); } $hasNext = false; if(count($recents) > $conf['recent']) { $hasNext = true; array_pop($recents); // remove extra log entry } print p_locale_xhtml('recent'); if(getNS($ID) != '') { print '<div class="level1"><p>' . sprintf($lang['recent_global'], getNS($ID), wl('', 'do=recent')) . '</p></div>'; } $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); if($conf['mediarevisions']) { $form->addElement('<div class="changeType">'); $form->addElement(form_makeListboxField( 'show_changes', array( 'pages' => $lang['pages_changes'], 'mediafiles' => $lang['media_changes'], 'both' => $lang['both_changes'] ), $show_changes, $lang['changes_type'], '', '', array('class' => 'quickselect'))); $form->addElement(form_makeButton('submit', 'recent', $lang['btn_apply'])); $form->addElement('</div>'); } $form->addElement(form_makeOpenTag('ul')); foreach($recents as $recent) { $date = dformat($recent['date']); $class = ''; if($recent['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) { $class = 'minor'; } $form->addElement(form_makeOpenTag('li', array('class' => $class))); $form->addElement(form_makeOpenTag('div', array('class' => 'li'))); if(!empty($recent['media'])) { $form->addElement(media_printicon($recent['id'])); } else { $icon = DOKU_BASE . 'lib/images/fileicons/file.png'; $form->addElement('<img src="' . $icon . '" alt="' . $recent['id'] . '" class="icon" />'); } $form->addElement(form_makeOpenTag('span', array('class' => 'date'))); $form->addElement($date); $form->addElement(form_makeCloseTag('span')); $diff = false; $href = ''; if(!empty($recent['media'])) { $changelog = new MediaChangeLog($recent['id']); $revs = $changelog->getRevisions(0, 1); $diff = (count($revs) && file_exists(mediaFN($recent['id']))); if($diff) { $href = media_managerURL(array( 'tab_details' => 'history', 'mediado' => 'diff', 'image' => $recent['id'], 'ns' => getNS($recent['id']) ), '&'); } } else { $href = wl($recent['id'], "do=diff", false, '&'); } if(!empty($recent['media']) && !$diff) { $form->addElement('<img src="' . DOKU_BASE . 'lib/images/blank.gif" width="15" height="11" alt="" />'); } else { $form->addElement(form_makeOpenTag('a', array('class' => 'diff_link', 'href' => $href))); $form->addElement(form_makeTag('img', array( 'src' => DOKU_BASE . 'lib/images/diff.png', 'width' => 15, 'height' => 11, 'title' => $lang['diff'], 'alt' => $lang['diff'] ))); $form->addElement(form_makeCloseTag('a')); } if(!empty($recent['media'])) { $href = media_managerURL( array( 'tab_details' => 'history', 'image' => $recent['id'], 'ns' => getNS($recent['id']) ), '&' ); } else { $href = wl($recent['id'], "do=revisions", false, '&'); } $form->addElement(form_makeOpenTag('a', array( 'class' => 'revisions_link', 'href' => $href))); $form->addElement(form_makeTag('img', array( 'src' => DOKU_BASE . 'lib/images/history.png', 'width' => 12, 'height' => 14, 'title' => $lang['btn_revs'], 'alt' => $lang['btn_revs'] ))); $form->addElement(form_makeCloseTag('a')); if(!empty($recent['media'])) { $href = media_managerURL( array( 'tab_details' => 'view', 'image' => $recent['id'], 'ns' => getNS($recent['id']) ), '&' ); $class = file_exists(mediaFN($recent['id'])) ? 'wikilink1' : 'wikilink2'; $form->addElement(form_makeOpenTag('a', array( 'class' => $class, 'href' => $href))); $form->addElement($recent['id']); $form->addElement(form_makeCloseTag('a')); } else { $form->addElement(html_wikilink(':' . $recent['id'], useHeading('navigation') ? null : $recent['id'])); } $form->addElement(form_makeOpenTag('span', array('class' => 'sum'))); $form->addElement(' – ' . hsc($recent['sum'])); $form->addElement(form_makeCloseTag('span')); $form->addElement(form_makeOpenTag('span', array('class' => 'user'))); if($recent['user']) { $form->addElement('<bdi>' . editorinfo($recent['user']) . '</bdi>'); if(auth_ismanager()) { $form->addElement(' <bdo dir="ltr">(' . $recent['ip'] . ')</bdo>'); } } else { $form->addElement('<bdo dir="ltr">' . $recent['ip'] . '</bdo>'); } $form->addElement(form_makeCloseTag('span')); html_sizechange($recent['sizechange'], $form); $form->addElement(form_makeCloseTag('div')); $form->addElement(form_makeCloseTag('li')); } $form->addElement(form_makeCloseTag('ul')); $form->addElement(form_makeOpenTag('div', array('class' => 'pagenav'))); $last = $first + $conf['recent']; if($first > 0) { $first -= $conf['recent']; if($first < 0) $first = 0; $form->addElement(form_makeOpenTag('div', array('class' => 'pagenav-prev'))); $form->addElement(form_makeOpenTag('button', array( 'type' => 'submit', 'name' => 'first[' . $first . ']', 'accesskey' => 'n', 'title' => $lang['btn_newer'] . ' [N]', 'class' => 'button show' ))); $form->addElement($lang['btn_newer']); $form->addElement(form_makeCloseTag('button')); $form->addElement(form_makeCloseTag('div')); } if($hasNext) { $form->addElement(form_makeOpenTag('div', array('class' => 'pagenav-next'))); $form->addElement(form_makeOpenTag('button', array( 'type' => 'submit', 'name' => 'first[' . $last . ']', 'accesskey' => 'p', 'title' => $lang['btn_older'] . ' [P]', 'class' => 'button show' ))); $form->addElement($lang['btn_older']); $form->addElement(form_makeCloseTag('button')); $form->addElement(form_makeCloseTag('div')); } $form->addElement(form_makeCloseTag('div')); html_form('recent', $form); } /** * Display page index * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $ns */ function html_index($ns){ global $conf; global $ID; $ns = cleanID($ns); if(empty($ns)){ $ns = getNS($ID); if($ns === false) $ns =''; } $ns = utf8_encodeFN(str_replace(':','/',$ns)); echo p_locale_xhtml('index'); echo '<div id="index__tree" class="index__tree">'; $data = array(); search($data,$conf['datadir'],'search_index',array('ns' => $ns)); echo html_buildlist($data,'idx','html_list_index','html_li_index'); echo '</div>'; } /** * Index item formatter * * User function for html_buildlist() * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $item * @return string */ function html_list_index($item){ global $ID, $conf; // prevent searchbots needlessly following links $nofollow = ($ID != $conf['start'] || $conf['sitemap']) ? 'rel="nofollow"' : ''; $ret = ''; $base = ':'.$item['id']; $base = substr($base,strrpos($base,':')+1); if($item['type']=='d'){ // FS#2766, no need for search bots to follow namespace links in the index $link = wl($ID, 'idx=' . rawurlencode($item['id'])); $ret .= '<a href="' . $link . '" title="' . $item['id'] . '" class="idx_dir" ' . $nofollow . '><strong>'; $ret .= $base; $ret .= '</strong></a>'; }else{ // default is noNSorNS($id), but we want noNS($id) when useheading is off FS#2605 $ret .= html_wikilink(':'.$item['id'], useHeading('navigation') ? null : noNS($item['id'])); } return $ret; } /** * Index List item * * This user function is used in html_buildlist to build the * <li> tags for namespaces when displaying the page index * it gives different classes to opened or closed "folders" * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $item * @return string html */ function html_li_index($item){ global $INFO; global $ACT; $class = ''; $id = ''; if($item['type'] == "f"){ // scroll to the current item if(isset($INFO) && $item['id'] == $INFO['id'] && $ACT == 'index') { $id = ' id="scroll__here"'; $class = ' bounce'; } return '<li class="level'.$item['level'].$class.'" '.$id.'>'; }elseif($item['open']){ return '<li class="open">'; }else{ return '<li class="closed">'; } } /** * Default List item * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $item * @return string html */ function html_li_default($item){ return '<li class="level'.$item['level'].'">'; } /** * Build an unordered list * * Build an unordered list from the given $data array * Each item in the array has to have a 'level' property * the item itself gets printed by the given $func user * function. The second and optional function is used to * print the <li> tag. Both user function need to accept * a single item. * * Both user functions can be given as array to point to * a member of an object. * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $data array with item arrays * @param string $class class of ul wrapper * @param callable $func callback to print an list item * @param callable $lifunc callback to the opening li tag * @param bool $forcewrapper Trigger building a wrapper ul if the first level is * 0 (we have a root object) or 1 (just the root content) * @return string html of an unordered list */ function html_buildlist($data,$class,$func,$lifunc='html_li_default',$forcewrapper=false){ if (count($data) === 0) { return ''; } $firstElement = reset($data); $start_level = $firstElement['level']; $level = $start_level; $ret = ''; $open = 0; foreach ($data as $item){ if( $item['level'] > $level ){ //open new list for($i=0; $i<($item['level'] - $level); $i++){ if ($i) $ret .= "<li class=\"clear\">"; $ret .= "\n<ul class=\"$class\">\n"; $open++; } $level = $item['level']; }elseif( $item['level'] < $level ){ //close last item $ret .= "</li>\n"; while( $level > $item['level'] && $open > 0 ){ //close higher lists $ret .= "</ul>\n</li>\n"; $level--; $open--; } } elseif ($ret !== '') { //close previous item $ret .= "</li>\n"; } //print item $ret .= call_user_func($lifunc,$item); $ret .= '<div class="li">'; $ret .= call_user_func($func,$item); $ret .= '</div>'; } //close remaining items and lists $ret .= "</li>\n"; while($open-- > 0) { $ret .= "</ul></li>\n"; } if ($forcewrapper || $start_level < 2) { // Trigger building a wrapper ul if the first level is // 0 (we have a root object) or 1 (just the root content) $ret = "\n<ul class=\"$class\">\n".$ret."</ul>\n"; } return $ret; } /** * display backlinks * * @author Andreas Gohr <andi@splitbrain.org> * @author Michael Klier <chi@chimeric.de> */ function html_backlinks(){ global $ID; global $lang; print p_locale_xhtml('backlinks'); $data = ft_backlinks($ID); if(!empty($data)) { print '<ul class="idx">'; foreach($data as $blink){ print '<li><div class="li">'; print html_wikilink(':'.$blink,useHeading('navigation')?null:$blink); print '</div></li>'; } print '</ul>'; } else { print '<div class="level1"><p>' . $lang['nothingfound'] . '</p></div>'; } } /** * Get header of diff HTML * * @param string $l_rev Left revisions * @param string $r_rev Right revision * @param string $id Page id, if null $ID is used * @param bool $media If it is for media files * @param bool $inline Return the header on a single line * @return string[] HTML snippets for diff header */ function html_diff_head($l_rev, $r_rev, $id = null, $media = false, $inline = false) { global $lang; if ($id === null) { global $ID; $id = $ID; } $head_separator = $inline ? ' ' : '<br />'; $media_or_wikiFN = $media ? 'mediaFN' : 'wikiFN'; $ml_or_wl = $media ? 'ml' : 'wl'; $l_minor = $r_minor = ''; if($media) { $changelog = new MediaChangeLog($id); } else { $changelog = new PageChangeLog($id); } if(!$l_rev){ $l_head = '—'; }else{ $l_info = $changelog->getRevisionInfo($l_rev); if($l_info['user']){ $l_user = '<bdi>'.editorinfo($l_info['user']).'</bdi>'; if(auth_ismanager()) $l_user .= ' <bdo dir="ltr">('.$l_info['ip'].')</bdo>'; } else { $l_user = '<bdo dir="ltr">'.$l_info['ip'].'</bdo>'; } $l_user = '<span class="user">'.$l_user.'</span>'; $l_sum = ($l_info['sum']) ? '<span class="sum"><bdi>'.hsc($l_info['sum']).'</bdi></span>' : ''; if ($l_info['type']===DOKU_CHANGE_TYPE_MINOR_EDIT) $l_minor = 'class="minor"'; $l_head_title = ($media) ? dformat($l_rev) : $id.' ['.dformat($l_rev).']'; $l_head = '<bdi><a class="wikilink1" href="'.$ml_or_wl($id,"rev=$l_rev").'">'. $l_head_title.'</a></bdi>'. $head_separator.$l_user.' '.$l_sum; } if($r_rev){ $r_info = $changelog->getRevisionInfo($r_rev); if($r_info['user']){ $r_user = '<bdi>'.editorinfo($r_info['user']).'</bdi>'; if(auth_ismanager()) $r_user .= ' <bdo dir="ltr">('.$r_info['ip'].')</bdo>'; } else { $r_user = '<bdo dir="ltr">'.$r_info['ip'].'</bdo>'; } $r_user = '<span class="user">'.$r_user.'</span>'; $r_sum = ($r_info['sum']) ? '<span class="sum"><bdi>'.hsc($r_info['sum']).'</bdi></span>' : ''; if ($r_info['type']===DOKU_CHANGE_TYPE_MINOR_EDIT) $r_minor = 'class="minor"'; $r_head_title = ($media) ? dformat($r_rev) : $id.' ['.dformat($r_rev).']'; $r_head = '<bdi><a class="wikilink1" href="'.$ml_or_wl($id,"rev=$r_rev").'">'. $r_head_title.'</a></bdi>'. $head_separator.$r_user.' '.$r_sum; }elseif($_rev = @filemtime($media_or_wikiFN($id))){ $_info = $changelog->getRevisionInfo($_rev); if($_info['user']){ $_user = '<bdi>'.editorinfo($_info['user']).'</bdi>'; if(auth_ismanager()) $_user .= ' <bdo dir="ltr">('.$_info['ip'].')</bdo>'; } else { $_user = '<bdo dir="ltr">'.$_info['ip'].'</bdo>'; } $_user = '<span class="user">'.$_user.'</span>'; $_sum = ($_info['sum']) ? '<span class="sum"><bdi>'.hsc($_info['sum']).'</span></bdi>' : ''; if ($_info['type']===DOKU_CHANGE_TYPE_MINOR_EDIT) $r_minor = 'class="minor"'; $r_head_title = ($media) ? dformat($_rev) : $id.' ['.dformat($_rev).']'; $r_head = '<bdi><a class="wikilink1" href="'.$ml_or_wl($id).'">'. $r_head_title.'</a></bdi> '. '('.$lang['current'].')'. $head_separator.$_user.' '.$_sum; }else{ $r_head = '— ('.$lang['current'].')'; } return array($l_head, $r_head, $l_minor, $r_minor); } /** * Show diff * between current page version and provided $text * or between the revisions provided via GET or POST * * @author Andreas Gohr <andi@splitbrain.org> * @param string $text when non-empty: compare with this text with most current version * @param bool $intro display the intro text * @param string $type type of the diff (inline or sidebyside) */ function html_diff($text = '', $intro = true, $type = null) { global $ID; global $REV; global $lang; global $INPUT; global $INFO; $pagelog = new PageChangeLog($ID); /* * Determine diff type */ if(!$type) { $type = $INPUT->str('difftype'); if(empty($type)) { $type = get_doku_pref('difftype', $type); if(empty($type) && $INFO['ismobile']) { $type = 'inline'; } } } if($type != 'inline') $type = 'sidebyside'; /* * Determine requested revision(s) */ // we're trying to be clever here, revisions to compare can be either // given as rev and rev2 parameters, with rev2 being optional. Or in an // array in rev2. $rev1 = $REV; $rev2 = $INPUT->ref('rev2'); if(is_array($rev2)) { $rev1 = (int) $rev2[0]; $rev2 = (int) $rev2[1]; if(!$rev1) { $rev1 = $rev2; unset($rev2); } } else { $rev2 = $INPUT->int('rev2'); } /* * Determine left and right revision, its texts and the header */ $r_minor = ''; $l_minor = ''; if($text) { // compare text to the most current revision $l_rev = ''; $l_text = rawWiki($ID, ''); $l_head = '<a class="wikilink1" href="' . wl($ID) . '">' . $ID . ' ' . dformat((int) @filemtime(wikiFN($ID))) . '</a> ' . $lang['current']; $r_rev = ''; $r_text = cleanText($text); $r_head = $lang['yours']; } else { if($rev1 && isset($rev2) && $rev2) { // two specific revisions wanted // make sure order is correct (older on the left) if($rev1 < $rev2) { $l_rev = $rev1; $r_rev = $rev2; } else { $l_rev = $rev2; $r_rev = $rev1; } } elseif($rev1) { // single revision given, compare to current $r_rev = ''; $l_rev = $rev1; } else { // no revision was given, compare previous to current $r_rev = ''; $revs = $pagelog->getRevisions(0, 1); $l_rev = $revs[0]; $REV = $l_rev; // store revision back in $REV } // when both revisions are empty then the page was created just now if(!$l_rev && !$r_rev) { $l_text = ''; } else { $l_text = rawWiki($ID, $l_rev); } $r_text = rawWiki($ID, $r_rev); list($l_head, $r_head, $l_minor, $r_minor) = html_diff_head($l_rev, $r_rev, null, false, $type == 'inline'); } /* * Build navigation */ $l_nav = ''; $r_nav = ''; if(!$text) { list($l_nav, $r_nav) = html_diff_navigation($pagelog, $type, $l_rev, $r_rev); } /* * Create diff object and the formatter */ $diff = new Diff(explode("\n", $l_text), explode("\n", $r_text)); if($type == 'inline') { $diffformatter = new InlineDiffFormatter(); } else { $diffformatter = new TableDiffFormatter(); } /* * Display intro */ if($intro) print p_locale_xhtml('diff'); /* * Display type and exact reference */ if(!$text) { ptln('<div class="diffoptions group">'); $form = new Doku_Form(array('action' => wl())); $form->addHidden('id', $ID); $form->addHidden('rev2[0]', $l_rev); $form->addHidden('rev2[1]', $r_rev); $form->addHidden('do', 'diff'); $form->addElement( form_makeListboxField( 'difftype', array( 'sidebyside' => $lang['diff_side'], 'inline' => $lang['diff_inline'] ), $type, $lang['diff_type'], '', '', array('class' => 'quickselect') ) ); $form->addElement(form_makeButton('submit', 'diff', 'Go')); $form->printForm(); ptln('<p>'); // link to exactly this view FS#2835 echo html_diff_navigationlink($type, 'difflink', $l_rev, $r_rev ? $r_rev : $INFO['currentrev']); ptln('</p>'); ptln('</div>'); // .diffoptions } /* * Display diff view table */ ?> <div class="table"> <table class="diff diff_<?php echo $type ?>"> <?php //navigation and header if($type == 'inline') { if(!$text) { ?> <tr> <td class="diff-lineheader">-</td> <td class="diffnav"><?php echo $l_nav ?></td> </tr> <tr> <th class="diff-lineheader">-</th> <th <?php echo $l_minor ?>> <?php echo $l_head ?> </th> </tr> <?php } ?> <tr> <td class="diff-lineheader">+</td> <td class="diffnav"><?php echo $r_nav ?></td> </tr> <tr> <th class="diff-lineheader">+</th> <th <?php echo $r_minor ?>> <?php echo $r_head ?> </th> </tr> <?php } else { if(!$text) { ?> <tr> <td colspan="2" class="diffnav"><?php echo $l_nav ?></td> <td colspan="2" class="diffnav"><?php echo $r_nav ?></td> </tr> <?php } ?> <tr> <th colspan="2" <?php echo $l_minor ?>> <?php echo $l_head ?> </th> <th colspan="2" <?php echo $r_minor ?>> <?php echo $r_head ?> </th> </tr> <?php } //diff view echo html_insert_softbreaks($diffformatter->format($diff)); ?> </table> </div> <?php } /** * Create html for revision navigation * * @param PageChangeLog $pagelog changelog object of current page * @param string $type inline vs sidebyside * @param int $l_rev left revision timestamp * @param int $r_rev right revision timestamp * @return string[] html of left and right navigation elements */ function html_diff_navigation($pagelog, $type, $l_rev, $r_rev) { global $INFO, $ID; // last timestamp is not in changelog, retrieve timestamp from metadata // note: when page is removed, the metadata timestamp is zero if(!$r_rev) { if(isset($INFO['meta']['last_change']['date'])) { $r_rev = $INFO['meta']['last_change']['date']; } else { $r_rev = 0; } } //retrieve revisions with additional info list($l_revs, $r_revs) = $pagelog->getRevisionsAround($l_rev, $r_rev); $l_revisions = array(); if(!$l_rev) { $l_revisions[0] = array(0, "", false); //no left revision given, add dummy } foreach($l_revs as $rev) { $info = $pagelog->getRevisionInfo($rev); $l_revisions[$rev] = array( $rev, dformat($info['date']) . ' ' . editorinfo($info['user'], true) . ' ' . $info['sum'], $r_rev ? $rev >= $r_rev : false //disable? ); } $r_revisions = array(); if(!$r_rev) { $r_revisions[0] = array(0, "", false); //no right revision given, add dummy } foreach($r_revs as $rev) { $info = $pagelog->getRevisionInfo($rev); $r_revisions[$rev] = array( $rev, dformat($info['date']) . ' ' . editorinfo($info['user'], true) . ' ' . $info['sum'], $rev <= $l_rev //disable? ); } //determine previous/next revisions $l_index = array_search($l_rev, $l_revs); $l_prev = $l_revs[$l_index + 1]; $l_next = $l_revs[$l_index - 1]; if($r_rev) { $r_index = array_search($r_rev, $r_revs); $r_prev = $r_revs[$r_index + 1]; $r_next = $r_revs[$r_index - 1]; } else { //removed page if($l_next) { $r_prev = $r_revs[0]; } else { $r_prev = null; } $r_next = null; } /* * Left side: */ $l_nav = ''; //move back if($l_prev) { $l_nav .= html_diff_navigationlink($type, 'diffbothprevrev', $l_prev, $r_prev); $l_nav .= html_diff_navigationlink($type, 'diffprevrev', $l_prev, $r_rev); } //dropdown $form = new Doku_Form(array('action' => wl())); $form->addHidden('id', $ID); $form->addHidden('difftype', $type); $form->addHidden('rev2[1]', $r_rev); $form->addHidden('do', 'diff'); $form->addElement( form_makeListboxField( 'rev2[0]', $l_revisions, $l_rev, '', '', '', array('class' => 'quickselect') ) ); $form->addElement(form_makeButton('submit', 'diff', 'Go')); $l_nav .= $form->getForm(); //move forward if($l_next && ($l_next < $r_rev || !$r_rev)) { $l_nav .= html_diff_navigationlink($type, 'diffnextrev', $l_next, $r_rev); } /* * Right side: */ $r_nav = ''; //move back if($l_rev < $r_prev) { $r_nav .= html_diff_navigationlink($type, 'diffprevrev', $l_rev, $r_prev); } //dropdown $form = new Doku_Form(array('action' => wl())); $form->addHidden('id', $ID); $form->addHidden('rev2[0]', $l_rev); $form->addHidden('difftype', $type); $form->addHidden('do', 'diff'); $form->addElement( form_makeListboxField( 'rev2[1]', $r_revisions, $r_rev, '', '', '', array('class' => 'quickselect') ) ); $form->addElement(form_makeButton('submit', 'diff', 'Go')); $r_nav .= $form->getForm(); //move forward if($r_next) { if($pagelog->isCurrentRevision($r_next)) { $r_nav .= html_diff_navigationlink($type, 'difflastrev', $l_rev); //last revision is diff with current page } else { $r_nav .= html_diff_navigationlink($type, 'diffnextrev', $l_rev, $r_next); } $r_nav .= html_diff_navigationlink($type, 'diffbothnextrev', $l_next, $r_next); } return array($l_nav, $r_nav); } /** * Create html link to a diff defined by two revisions * * @param string $difftype display type * @param string $linktype * @param int $lrev oldest revision * @param int $rrev newest revision or null for diff with current revision * @return string html of link to a diff */ function html_diff_navigationlink($difftype, $linktype, $lrev, $rrev = null) { global $ID, $lang; if(!$rrev) { $urlparam = array( 'do' => 'diff', 'rev' => $lrev, 'difftype' => $difftype, ); } else { $urlparam = array( 'do' => 'diff', 'rev2[0]' => $lrev, 'rev2[1]' => $rrev, 'difftype' => $difftype, ); } return '<a class="' . $linktype . '" href="' . wl($ID, $urlparam) . '" title="' . $lang[$linktype] . '">' . '<span>' . $lang[$linktype] . '</span>' . '</a>' . "\n"; } /** * Insert soft breaks in diff html * * @param string $diffhtml * @return string */ function html_insert_softbreaks($diffhtml) { // search the diff html string for both: // - html tags, so these can be ignored // - long strings of characters without breaking characters return preg_replace_callback('/<[^>]*>|[^<> ]{12,}/','html_softbreak_callback',$diffhtml); } /** * callback which adds softbreaks * * @param array $match array with first the complete match * @return string the replacement */ function html_softbreak_callback($match){ // if match is an html tag, return it intact if ($match[0][0] == '<') return $match[0]; // its a long string without a breaking character, // make certain characters into breaking characters by inserting a // word break opportunity (<wbr> tag) in front of them. $regex = <<< REGEX (?(?= # start a conditional expression with a positive look ahead ... &\#?\\w{1,6};) # ... for html entities - we don't want to split them (ok to catch some invalid combinations) &\#?\\w{1,6}; # yes pattern - a quicker match for the html entity, since we know we have one | [?/,&\#;:] # no pattern - any other group of 'special' characters to insert a breaking character after )+ # end conditional expression REGEX; return preg_replace('<'.$regex.'>xu','\0<wbr>',$match[0]); } /** * show warning on conflict detection * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $text * @param string $summary */ function html_conflict($text,$summary){ global $ID; global $lang; print p_locale_xhtml('conflict'); $form = new Doku_Form(array('id' => 'dw__editform')); $form->addHidden('id', $ID); $form->addHidden('wikitext', $text); $form->addHidden('summary', $summary); $form->addElement(form_makeButton('submit', 'save', $lang['btn_save'], array('accesskey'=>'s'))); $form->addElement(form_makeButton('submit', 'cancel', $lang['btn_cancel'])); html_form('conflict', $form); print '<br /><br /><br /><br />'.NL; } /** * Prints the global message array * * @author Andreas Gohr <andi@splitbrain.org> */ function html_msgarea(){ global $MSG, $MSG_shown; /** @var array $MSG */ // store if the global $MSG has already been shown and thus HTML output has been started $MSG_shown = true; if(!isset($MSG)) return; $shown = array(); foreach($MSG as $msg){ $hash = md5($msg['msg']); if(isset($shown[$hash])) continue; // skip double messages if(info_msg_allowed($msg)){ print '<div class="'.$msg['lvl'].'">'; print $msg['msg']; print '</div>'; } $shown[$hash] = 1; } unset($GLOBALS['MSG']); } /** * Prints the registration form * * @author Andreas Gohr <andi@splitbrain.org> */ function html_register(){ global $lang; global $conf; global $INPUT; $base_attrs = array('size'=>50,'required'=>'required'); $email_attrs = $base_attrs + array('type'=>'email','class'=>'edit'); print p_locale_xhtml('register'); print '<div class="centeralign">'.NL; $form = new Doku_Form(array('id' => 'dw__register')); $form->startFieldset($lang['btn_register']); $form->addHidden('do', 'register'); $form->addHidden('save', '1'); $form->addElement( form_makeTextField( 'login', $INPUT->post->str('login'), $lang['user'], '', 'block', $base_attrs ) ); if (!$conf['autopasswd']) { $form->addElement(form_makePasswordField('pass', $lang['pass'], '', 'block', $base_attrs)); $form->addElement(form_makePasswordField('passchk', $lang['passchk'], '', 'block', $base_attrs)); } $form->addElement( form_makeTextField( 'fullname', $INPUT->post->str('fullname'), $lang['fullname'], '', 'block', $base_attrs ) ); $form->addElement( form_makeField( 'email', 'email', $INPUT->post->str('email'), $lang['email'], '', 'block', $email_attrs ) ); $form->addElement(form_makeButton('submit', '', $lang['btn_register'])); $form->endFieldset(); html_form('register', $form); print '</div>'.NL; } /** * Print the update profile form * * @author Christopher Smith <chris@jalakai.co.uk> * @author Andreas Gohr <andi@splitbrain.org> */ function html_updateprofile(){ global $lang; global $conf; global $INPUT; global $INFO; /** @var AuthPlugin $auth */ global $auth; print p_locale_xhtml('updateprofile'); print '<div class="centeralign">'.NL; $fullname = $INPUT->post->str('fullname', $INFO['userinfo']['name'], true); $email = $INPUT->post->str('email', $INFO['userinfo']['mail'], true); $form = new Doku_Form(array('id' => 'dw__register')); $form->startFieldset($lang['profile']); $form->addHidden('do', 'profile'); $form->addHidden('save', '1'); $form->addElement( form_makeTextField( 'login', $_SERVER['REMOTE_USER'], $lang['user'], '', 'block', array('size' => '50', 'disabled' => 'disabled') ) ); $attr = array('size'=>'50'); if (!$auth->canDo('modName')) $attr['disabled'] = 'disabled'; $form->addElement(form_makeTextField('fullname', $fullname, $lang['fullname'], '', 'block', $attr)); $attr = array('size'=>'50', 'class'=>'edit'); if (!$auth->canDo('modMail')) $attr['disabled'] = 'disabled'; $form->addElement(form_makeField('email','email', $email, $lang['email'], '', 'block', $attr)); $form->addElement(form_makeTag('br')); if ($auth->canDo('modPass')) { $form->addElement(form_makePasswordField('newpass', $lang['newpass'], '', 'block', array('size'=>'50'))); $form->addElement(form_makePasswordField('passchk', $lang['passchk'], '', 'block', array('size'=>'50'))); } if ($conf['profileconfirm']) { $form->addElement(form_makeTag('br')); $form->addElement( form_makePasswordField( 'oldpass', $lang['oldpass'], '', 'block', array('size' => '50', 'required' => 'required') ) ); } $form->addElement(form_makeButton('submit', '', $lang['btn_save'])); $form->addElement(form_makeButton('reset', '', $lang['btn_reset'])); $form->endFieldset(); html_form('updateprofile', $form); if ($auth->canDo('delUser') && actionOK('profile_delete')) { $form_profiledelete = new Doku_Form(array('id' => 'dw__profiledelete')); $form_profiledelete->startFieldset($lang['profdeleteuser']); $form_profiledelete->addHidden('do', 'profile_delete'); $form_profiledelete->addHidden('delete', '1'); $form_profiledelete->addElement( form_makeCheckboxField( 'confirm_delete', '1', $lang['profconfdelete'], 'dw__confirmdelete', '', array('required' => 'required') ) ); if ($conf['profileconfirm']) { $form_profiledelete->addElement(form_makeTag('br')); $form_profiledelete->addElement( form_makePasswordField( 'oldpass', $lang['oldpass'], '', 'block', array('size' => '50', 'required' => 'required') ) ); } $form_profiledelete->addElement(form_makeButton('submit', '', $lang['btn_deleteuser'])); $form_profiledelete->endFieldset(); html_form('profiledelete', $form_profiledelete); } print '</div>'.NL; } /** * Preprocess edit form data * * @author Andreas Gohr <andi@splitbrain.org> * * @triggers HTML_EDITFORM_OUTPUT */ function html_edit(){ global $INPUT; global $ID; global $REV; global $DATE; global $PRE; global $SUF; global $INFO; global $SUM; global $lang; global $conf; global $TEXT; if ($INPUT->has('changecheck')) { $check = $INPUT->str('changecheck'); } elseif(!$INFO['exists']){ // $TEXT has been loaded from page template $check = md5(''); } else { $check = md5($TEXT); } $mod = md5($TEXT) !== $check; $wr = $INFO['writable'] && !$INFO['locked']; $include = 'edit'; if($wr){ if ($REV) $include = 'editrev'; }else{ // check pseudo action 'source' if(!actionOK('source')){ msg('Command disabled: source',-1); return; } $include = 'read'; } global $license; $form = new Doku_Form(array('id' => 'dw__editform')); $form->addHidden('id', $ID); $form->addHidden('rev', $REV); $form->addHidden('date', $DATE); $form->addHidden('prefix', $PRE . '.'); $form->addHidden('suffix', $SUF); $form->addHidden('changecheck', $check); $data = array('form' => $form, 'wr' => $wr, 'media_manager' => true, 'target' => ($INPUT->has('target') && $wr) ? $INPUT->str('target') : 'section', 'intro_locale' => $include); if ($data['target'] !== 'section') { // Only emit event if page is writable, section edit data is valid and // edit target is not section. Event::createAndTrigger('HTML_EDIT_FORMSELECTION', $data, 'html_edit_form', true); } else { html_edit_form($data); } if (isset($data['intro_locale'])) { echo p_locale_xhtml($data['intro_locale']); } $form->addHidden('target', $data['target']); if ($INPUT->has('hid')) { $form->addHidden('hid', $INPUT->str('hid')); } if ($INPUT->has('codeblockOffset')) { $form->addHidden('codeblockOffset', $INPUT->str('codeblockOffset')); } $form->addElement(form_makeOpenTag('div', array('id'=>'wiki__editbar', 'class'=>'editBar'))); $form->addElement(form_makeOpenTag('div', array('id'=>'size__ctl'))); $form->addElement(form_makeCloseTag('div')); if ($wr) { $form->addElement(form_makeOpenTag('div', array('class'=>'editButtons'))); $form->addElement( form_makeButton( 'submit', 'save', $lang['btn_save'], array('id' => 'edbtn__save', 'accesskey' => 's', 'tabindex' => '4') ) ); $form->addElement( form_makeButton( 'submit', 'preview', $lang['btn_preview'], array('id' => 'edbtn__preview', 'accesskey' => 'p', 'tabindex' => '5') ) ); $form->addElement(form_makeButton('submit', 'cancel', $lang['btn_cancel'], array('tabindex'=>'6'))); $form->addElement(form_makeCloseTag('div')); $form->addElement(form_makeOpenTag('div', array('class'=>'summary'))); $form->addElement( form_makeTextField( 'summary', $SUM, $lang['summary'], 'edit__summary', 'nowrap', array('size' => '50', 'tabindex' => '2') ) ); $elem = html_minoredit(); if ($elem) $form->addElement($elem); $form->addElement(form_makeCloseTag('div')); } $form->addElement(form_makeCloseTag('div')); if($wr && $conf['license']){ $form->addElement(form_makeOpenTag('div', array('class'=>'license'))); $out = $lang['licenseok']; $out .= ' <a href="'.$license[$conf['license']]['url'].'" rel="license" class="urlextern"'; if($conf['target']['extern']) $out .= ' target="'.$conf['target']['extern'].'"'; $out .= '>'.$license[$conf['license']]['name'].'</a>'; $form->addElement($out); $form->addElement(form_makeCloseTag('div')); } if ($wr) { // sets changed to true when previewed echo '<script>/*<![CDATA[*/'. NL; echo 'textChanged = ' . ($mod ? 'true' : 'false'); echo '/*!]]>*/</script>' . NL; } ?> <div class="editBox" role="application"> <div class="toolbar group"> <div id="tool__bar" class="tool__bar"><?php if ($wr && $data['media_manager']){ ?><a href="<?php echo DOKU_BASE?>lib/exe/mediamanager.php?ns=<?php echo $INFO['namespace']?>" target="_blank"><?php echo $lang['mediaselect'] ?></a><?php }?> </div> </div> <div id="draft__status" class="draft__status"> <?php $draft = new \dokuwiki\Draft($ID, $INFO['client']); if ($draft->isDraftAvailable()) { echo $draft->getDraftMessage(); } ?> </div> <?php html_form('edit', $form); print '</div>'.NL; } /** * Display the default edit form * * Is the default action for HTML_EDIT_FORMSELECTION. * * @param mixed[] $param */ function html_edit_form($param) { global $TEXT; if ($param['target'] !== 'section') { msg('No editor for edit target ' . hsc($param['target']) . ' found.', -1); } $attr = array('tabindex'=>'1'); if (!$param['wr']) $attr['readonly'] = 'readonly'; $param['form']->addElement(form_makeWikiText($TEXT, $attr)); } /** * Adds a checkbox for minor edits for logged in users * * @author Andreas Gohr <andi@splitbrain.org> * * @return array|bool */ function html_minoredit(){ global $conf; global $lang; global $INPUT; // minor edits are for logged in users only if(!$conf['useacl'] || !$_SERVER['REMOTE_USER']){ return false; } $p = array(); $p['tabindex'] = 3; if($INPUT->bool('minor')) $p['checked']='checked'; return form_makeCheckboxField('minor', '1', $lang['minoredit'], 'minoredit', 'nowrap', $p); } /** * prints some debug info * * @author Andreas Gohr <andi@splitbrain.org> */ function html_debug(){ global $conf; global $lang; /** @var AuthPlugin $auth */ global $auth; global $INFO; //remove sensitive data $cnf = $conf; debug_guard($cnf); $nfo = $INFO; debug_guard($nfo); $ses = $_SESSION; debug_guard($ses); print '<html><body>'; print '<p>When reporting bugs please send all the following '; print 'output as a mail to andi@splitbrain.org '; print 'The best way to do this is to save this page in your browser</p>'; print '<b>$INFO:</b><pre>'; print_r($nfo); print '</pre>'; print '<b>$_SERVER:</b><pre>'; print_r($_SERVER); print '</pre>'; print '<b>$conf:</b><pre>'; print_r($cnf); print '</pre>'; print '<b>DOKU_BASE:</b><pre>'; print DOKU_BASE; print '</pre>'; print '<b>abs DOKU_BASE:</b><pre>'; print DOKU_URL; print '</pre>'; print '<b>rel DOKU_BASE:</b><pre>'; print dirname($_SERVER['PHP_SELF']).'/'; print '</pre>'; print '<b>PHP Version:</b><pre>'; print phpversion(); print '</pre>'; print '<b>locale:</b><pre>'; print setlocale(LC_ALL,0); print '</pre>'; print '<b>encoding:</b><pre>'; print $lang['encoding']; print '</pre>'; if($auth){ print '<b>Auth backend capabilities:</b><pre>'; foreach ($auth->getCapabilities() as $cando){ print ' '.str_pad($cando,16) . ' => ' . (int)$auth->canDo($cando) . NL; } print '</pre>'; } print '<b>$_SESSION:</b><pre>'; print_r($ses); print '</pre>'; print '<b>Environment:</b><pre>'; print_r($_ENV); print '</pre>'; print '<b>PHP settings:</b><pre>'; $inis = ini_get_all(); print_r($inis); print '</pre>'; if (function_exists('apache_get_version')) { $apache = array(); $apache['version'] = apache_get_version(); if (function_exists('apache_get_modules')) { $apache['modules'] = apache_get_modules(); } print '<b>Apache</b><pre>'; print_r($apache); print '</pre>'; } print '</body></html>'; } /** * Form to request a new password for an existing account * * @author Benoit Chesneau <benoit@bchesneau.info> * @author Andreas Gohr <gohr@cosmocode.de> */ function html_resendpwd() { global $lang; global $conf; global $INPUT; $token = preg_replace('/[^a-f0-9]+/','',$INPUT->str('pwauth')); if(!$conf['autopasswd'] && $token){ print p_locale_xhtml('resetpwd'); print '<div class="centeralign">'.NL; $form = new Doku_Form(array('id' => 'dw__resendpwd')); $form->startFieldset($lang['btn_resendpwd']); $form->addHidden('token', $token); $form->addHidden('do', 'resendpwd'); $form->addElement(form_makePasswordField('pass', $lang['pass'], '', 'block', array('size'=>'50'))); $form->addElement(form_makePasswordField('passchk', $lang['passchk'], '', 'block', array('size'=>'50'))); $form->addElement(form_makeButton('submit', '', $lang['btn_resendpwd'])); $form->endFieldset(); html_form('resendpwd', $form); print '</div>'.NL; }else{ print p_locale_xhtml('resendpwd'); print '<div class="centeralign">'.NL; $form = new Doku_Form(array('id' => 'dw__resendpwd')); $form->startFieldset($lang['resendpwd']); $form->addHidden('do', 'resendpwd'); $form->addHidden('save', '1'); $form->addElement(form_makeTag('br')); $form->addElement(form_makeTextField('login', $INPUT->post->str('login'), $lang['user'], '', 'block')); $form->addElement(form_makeTag('br')); $form->addElement(form_makeTag('br')); $form->addElement(form_makeButton('submit', '', $lang['btn_resendpwd'])); $form->endFieldset(); html_form('resendpwd', $form); print '</div>'.NL; } } /** * Return the TOC rendered to XHTML * * @author Andreas Gohr <andi@splitbrain.org> * * @param array $toc * @return string html */ function html_TOC($toc){ if(!count($toc)) return ''; global $lang; $out = '<!-- TOC START -->'.DOKU_LF; $out .= '<div id="dw__toc" class="dw__toc">'.DOKU_LF; $out .= '<h3 class="toggle">'; $out .= $lang['toc']; $out .= '</h3>'.DOKU_LF; $out .= '<div>'.DOKU_LF; $out .= html_buildlist($toc,'toc','html_list_toc','html_li_default',true); $out .= '</div>'.DOKU_LF.'</div>'.DOKU_LF; $out .= '<!-- TOC END -->'.DOKU_LF; return $out; } /** * Callback for html_buildlist * * @param array $item * @return string html */ function html_list_toc($item){ if(isset($item['hid'])){ $link = '#'.$item['hid']; }else{ $link = $item['link']; } return '<a href="'.$link.'">'.hsc($item['title']).'</a>'; } /** * Helper function to build TOC items * * Returns an array ready to be added to a TOC array * * @param string $link - where to link (if $hash set to '#' it's a local anchor) * @param string $text - what to display in the TOC * @param int $level - nesting level * @param string $hash - is prepended to the given $link, set blank if you want full links * @return array the toc item */ function html_mktocitem($link, $text, $level, $hash='#'){ return array( 'link' => $hash.$link, 'title' => $text, 'type' => 'ul', 'level' => $level); } /** * Output a Doku_Form object. * Triggers an event with the form name: HTML_{$name}FORM_OUTPUT * * @author Tom N Harris <tnharris@whoopdedo.org> * * @param string $name The name of the form * @param Doku_Form $form The form */ function html_form($name, &$form) { // Safety check in case the caller forgets. $form->endFieldset(); Event::createAndTrigger('HTML_'.strtoupper($name).'FORM_OUTPUT', $form, 'html_form_output', false); } /** * Form print function. * Just calls printForm() on the data object. * * @param Doku_Form $data The form */ function html_form_output($data) { $data->printForm(); } /** * Embed a flash object in HTML * * This will create the needed HTML to embed a flash movie in a cross browser * compatble way using valid XHTML * * The parameters $params, $flashvars and $atts need to be associative arrays. * No escaping needs to be done for them. The alternative content *has* to be * escaped because it is used as is. If no alternative content is given * $lang['noflash'] is used. * * @author Andreas Gohr <andi@splitbrain.org> * @link http://latrine.dgx.cz/how-to-correctly-insert-a-flash-into-xhtml * * @param string $swf - the SWF movie to embed * @param int $width - width of the flash movie in pixels * @param int $height - height of the flash movie in pixels * @param array $params - additional parameters (<param>) * @param array $flashvars - parameters to be passed in the flashvar parameter * @param array $atts - additional attributes for the <object> tag * @param string $alt - alternative content (is NOT automatically escaped!) * @return string - the XHTML markup */ function html_flashobject($swf,$width,$height,$params=null,$flashvars=null,$atts=null,$alt=''){ global $lang; $out = ''; // prepare the object attributes if(is_null($atts)) $atts = array(); $atts['width'] = (int) $width; $atts['height'] = (int) $height; if(!$atts['width']) $atts['width'] = 425; if(!$atts['height']) $atts['height'] = 350; // add object attributes for standard compliant browsers $std = $atts; $std['type'] = 'application/x-shockwave-flash'; $std['data'] = $swf; // add object attributes for IE $ie = $atts; $ie['classid'] = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'; // open object (with conditional comments) $out .= '<!--[if !IE]> -->'.NL; $out .= '<object '.buildAttributes($std).'>'.NL; $out .= '<!-- <![endif]-->'.NL; $out .= '<!--[if IE]>'.NL; $out .= '<object '.buildAttributes($ie).'>'.NL; $out .= ' <param name="movie" value="'.hsc($swf).'" />'.NL; $out .= '<!--><!-- -->'.NL; // print params if(is_array($params)) foreach($params as $key => $val){ $out .= ' <param name="'.hsc($key).'" value="'.hsc($val).'" />'.NL; } // add flashvars if(is_array($flashvars)){ $out .= ' <param name="FlashVars" value="'.buildURLparams($flashvars).'" />'.NL; } // alternative content if($alt){ $out .= $alt.NL; }else{ $out .= $lang['noflash'].NL; } // finish $out .= '</object>'.NL; $out .= '<!-- <![endif]-->'.NL; return $out; } /** * Prints HTML code for the given tab structure * * @param array $tabs tab structure * @param string $current_tab the current tab id */ function html_tabs($tabs, $current_tab = null) { echo '<ul class="tabs">'.NL; foreach($tabs as $id => $tab) { html_tab($tab['href'], $tab['caption'], $id === $current_tab); } echo '</ul>'.NL; } /** * Prints a single tab * * @author Kate Arzamastseva <pshns@ukr.net> * @author Adrian Lang <mail@adrianlang.de> * * @param string $href - tab href * @param string $caption - tab caption * @param boolean $selected - is tab selected */ function html_tab($href, $caption, $selected=false) { $tab = '<li>'; if ($selected) { $tab .= '<strong>'; } else { $tab .= '<a href="' . hsc($href) . '">'; } $tab .= hsc($caption) . '</' . ($selected ? 'strong' : 'a') . '>' . '</li>'.NL; echo $tab; } /** * Display size change * * @param int $sizechange - size of change in Bytes * @param Doku_Form $form - form to add elements to */ function html_sizechange($sizechange, Doku_Form $form) { if(isset($sizechange)) { $class = 'sizechange'; $value = filesize_h(abs($sizechange)); if($sizechange > 0) { $class .= ' positive'; $value = '+' . $value; } elseif($sizechange < 0) { $class .= ' negative'; $value = '-' . $value; } else { $value = '±' . $value; } $form->addElement(form_makeOpenTag('span', array('class' => $class))); $form->addElement($value); $form->addElement(form_makeCloseTag('span')); } } cache.php 0000644 00000002617 15233462216 0006332 0 ustar 00 <?php // phpcs:ignoreFile use dokuwiki\Cache\CacheParser; use dokuwiki\Cache\CacheInstructions; use dokuwiki\Cache\CacheRenderer; use dokuwiki\Debug\DebugHelper; /** * @deprecated since 2019-02-02 use \dokuwiki\Cache\Cache instead! */ class cache extends \dokuwiki\Cache\Cache { public function __construct($key, $ext) { DebugHelper::dbgDeprecatedFunction(dokuwiki\Cache\Cache::class); parent::__construct($key, $ext); } } /** * @deprecated since 2019-02-02 use \dokuwiki\Cache\CacheParser instead! */ class cache_parser extends \dokuwiki\Cache\CacheParser { public function __construct($id, $file, $mode) { DebugHelper::dbgDeprecatedFunction(CacheParser::class); parent::__construct($id, $file, $mode); } } /** * @deprecated since 2019-02-02 use \dokuwiki\Cache\CacheRenderer instead! */ class cache_renderer extends \dokuwiki\Cache\CacheRenderer { public function __construct($id, $file, $mode) { DebugHelper::dbgDeprecatedFunction(CacheRenderer::class); parent::__construct($id, $file, $mode); } } /** * @deprecated since 2019-02-02 use \dokuwiki\Cache\CacheInstructions instead! */ class cache_instructions extends \dokuwiki\Cache\CacheInstructions { public function __construct($id, $file) { DebugHelper::dbgDeprecatedFunction(CacheInstructions::class); parent::__construct($id, $file); } } fulltext.php 0000644 00000075272 15233462216 0007145 0 ustar 00 <?php /** * DokuWiki fulltextsearch functions using the index * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ use dokuwiki\Extension\Event; /** * create snippets for the first few results only */ if(!defined('FT_SNIPPET_NUMBER')) define('FT_SNIPPET_NUMBER',15); /** * The fulltext search * * Returns a list of matching documents for the given query * * refactored into ft_pageSearch(), _ft_pageSearch() and trigger_event() * * @param string $query * @param array $highlight * @param string $sort * @param int|string $after only show results with mtime after this date, accepts timestap or strtotime arguments * @param int|string $before only show results with mtime before this date, accepts timestap or strtotime arguments * * @return array */ function ft_pageSearch($query,&$highlight, $sort = null, $after = null, $before = null){ if ($sort === null) { $sort = 'hits'; } $data = [ 'query' => $query, 'sort' => $sort, 'after' => $after, 'before' => $before ]; $data['highlight'] =& $highlight; return Event::createAndTrigger('SEARCH_QUERY_FULLPAGE', $data, '_ft_pageSearch'); } /** * Returns a list of matching documents for the given query * * @author Andreas Gohr <andi@splitbrain.org> * @author Kazutaka Miyasaka <kazmiya@gmail.com> * * @param array $data event data * @return array matching documents */ function _ft_pageSearch(&$data) { $Indexer = idx_get_indexer(); // parse the given query $q = ft_queryParser($Indexer, $data['query']); $data['highlight'] = $q['highlight']; if (empty($q['parsed_ary'])) return array(); // lookup all words found in the query $lookup = $Indexer->lookup($q['words']); // get all pages in this dokuwiki site (!: includes nonexistent pages) $pages_all = array(); foreach ($Indexer->getPages() as $id) { $pages_all[$id] = 0; // base: 0 hit } // process the query $stack = array(); foreach ($q['parsed_ary'] as $token) { switch (substr($token, 0, 3)) { case 'W+:': case 'W-:': case 'W_:': // word $word = substr($token, 3); $stack[] = (array) $lookup[$word]; break; case 'P+:': case 'P-:': // phrase $phrase = substr($token, 3); // since phrases are always parsed as ((W1)(W2)...(P)), // the end($stack) always points the pages that contain // all words in this phrase $pages = end($stack); $pages_matched = array(); foreach(array_keys($pages) as $id){ $evdata = array( 'id' => $id, 'phrase' => $phrase, 'text' => rawWiki($id) ); $evt = new Event('FULLTEXT_PHRASE_MATCH',$evdata); if ($evt->advise_before() && $evt->result !== true) { $text = \dokuwiki\Utf8\PhpString::strtolower($evdata['text']); if (strpos($text, $phrase) !== false) { $evt->result = true; } } $evt->advise_after(); if ($evt->result === true) { $pages_matched[$id] = 0; // phrase: always 0 hit } } $stack[] = $pages_matched; break; case 'N+:': case 'N-:': // namespace $ns = cleanID(substr($token, 3)) . ':'; $pages_matched = array(); foreach (array_keys($pages_all) as $id) { if (strpos($id, $ns) === 0) { $pages_matched[$id] = 0; // namespace: always 0 hit } } $stack[] = $pages_matched; break; case 'AND': // and operation list($pages1, $pages2) = array_splice($stack, -2); $stack[] = ft_resultCombine(array($pages1, $pages2)); break; case 'OR': // or operation list($pages1, $pages2) = array_splice($stack, -2); $stack[] = ft_resultUnite(array($pages1, $pages2)); break; case 'NOT': // not operation (unary) $pages = array_pop($stack); $stack[] = ft_resultComplement(array($pages_all, $pages)); break; } } $docs = array_pop($stack); if (empty($docs)) return array(); // check: settings, acls, existence foreach (array_keys($docs) as $id) { if (isHiddenPage($id) || auth_quickaclcheck($id) < AUTH_READ || !page_exists($id, '', false)) { unset($docs[$id]); } } $docs = _ft_filterResultsByTime($docs, $data['after'], $data['before']); if ($data['sort'] === 'mtime') { uksort($docs, 'ft_pagemtimesorter'); } else { // sort docs by count arsort($docs); } return $docs; } /** * Returns the backlinks for a given page * * Uses the metadata index. * * @param string $id The id for which links shall be returned * @param bool $ignore_perms Ignore the fact that pages are hidden or read-protected * @return array The pages that contain links to the given page */ function ft_backlinks($id, $ignore_perms = false){ $result = idx_get_indexer()->lookupKey('relation_references', $id); if(!count($result)) return $result; // check ACL permissions foreach(array_keys($result) as $idx){ if(($ignore_perms !== true && ( isHiddenPage($result[$idx]) || auth_quickaclcheck($result[$idx]) < AUTH_READ )) || !page_exists($result[$idx], '', false)){ unset($result[$idx]); } } sort($result); return $result; } /** * Returns the pages that use a given media file * * Uses the relation media metadata property and the metadata index. * * Note that before 2013-07-31 the second parameter was the maximum number of results and * permissions were ignored. That's why the parameter is now checked to be explicitely set * to true (with type bool) in order to be compatible with older uses of the function. * * @param string $id The media id to look for * @param bool $ignore_perms Ignore hidden pages and acls (optional, default: false) * @return array A list of pages that use the given media file */ function ft_mediause($id, $ignore_perms = false){ $result = idx_get_indexer()->lookupKey('relation_media', $id); if(!count($result)) return $result; // check ACL permissions foreach(array_keys($result) as $idx){ if(($ignore_perms !== true && ( isHiddenPage($result[$idx]) || auth_quickaclcheck($result[$idx]) < AUTH_READ )) || !page_exists($result[$idx], '', false)){ unset($result[$idx]); } } sort($result); return $result; } /** * Quicksearch for pagenames * * By default it only matches the pagename and ignores the * namespace. This can be changed with the second parameter. * The third parameter allows to search in titles as well. * * The function always returns titles as well * * @triggers SEARCH_QUERY_PAGELOOKUP * @author Andreas Gohr <andi@splitbrain.org> * @author Adrian Lang <lang@cosmocode.de> * * @param string $id page id * @param bool $in_ns match against namespace as well? * @param bool $in_title search in title? * @param int|string $after only show results with mtime after this date, accepts timestap or strtotime arguments * @param int|string $before only show results with mtime before this date, accepts timestap or strtotime arguments * * @return string[] */ function ft_pageLookup($id, $in_ns=false, $in_title=false, $after = null, $before = null){ $data = [ 'id' => $id, 'in_ns' => $in_ns, 'in_title' => $in_title, 'after' => $after, 'before' => $before ]; $data['has_titles'] = true; // for plugin backward compatibility check return Event::createAndTrigger('SEARCH_QUERY_PAGELOOKUP', $data, '_ft_pageLookup'); } /** * Returns list of pages as array(pageid => First Heading) * * @param array &$data event data * @return string[] */ function _ft_pageLookup(&$data){ // split out original parameters $id = $data['id']; $Indexer = idx_get_indexer(); $parsedQuery = ft_queryParser($Indexer, $id); if (count($parsedQuery['ns']) > 0) { $ns = cleanID($parsedQuery['ns'][0]) . ':'; $id = implode(' ', $parsedQuery['highlight']); } $in_ns = $data['in_ns']; $in_title = $data['in_title']; $cleaned = cleanID($id); $Indexer = idx_get_indexer(); $page_idx = $Indexer->getPages(); $pages = array(); if ($id !== '' && $cleaned !== '') { foreach ($page_idx as $p_id) { if ((strpos($in_ns ? $p_id : noNSorNS($p_id), $cleaned) !== false)) { if (!isset($pages[$p_id])) $pages[$p_id] = p_get_first_heading($p_id, METADATA_DONT_RENDER); } } if ($in_title) { foreach ($Indexer->lookupKey('title', $id, '_ft_pageLookupTitleCompare') as $p_id) { if (!isset($pages[$p_id])) $pages[$p_id] = p_get_first_heading($p_id, METADATA_DONT_RENDER); } } } if (isset($ns)) { foreach (array_keys($pages) as $p_id) { if (strpos($p_id, $ns) !== 0) { unset($pages[$p_id]); } } } // discard hidden pages // discard nonexistent pages // check ACL permissions foreach(array_keys($pages) as $idx){ if(!isVisiblePage($idx) || !page_exists($idx) || auth_quickaclcheck($idx) < AUTH_READ) { unset($pages[$idx]); } } $pages = _ft_filterResultsByTime($pages, $data['after'], $data['before']); uksort($pages,'ft_pagesorter'); return $pages; } /** * @param array $results search results in the form pageid => value * @param int|string $after only returns results with mtime after this date, accepts timestap or strtotime arguments * @param int|string $before only returns results with mtime after this date, accepts timestap or strtotime arguments * * @return array */ function _ft_filterResultsByTime(array $results, $after, $before) { if ($after || $before) { $after = is_int($after) ? $after : strtotime($after); $before = is_int($before) ? $before : strtotime($before); foreach ($results as $id => $value) { $mTime = filemtime(wikiFN($id)); if ($after && $after > $mTime) { unset($results[$id]); continue; } if ($before && $before < $mTime) { unset($results[$id]); } } } return $results; } /** * Tiny helper function for comparing the searched title with the title * from the search index. This function is a wrapper around stripos with * adapted argument order and return value. * * @param string $search searched title * @param string $title title from index * @return bool */ function _ft_pageLookupTitleCompare($search, $title) { return stripos($title, $search) !== false; } /** * Sort pages based on their namespace level first, then on their string * values. This makes higher hierarchy pages rank higher than lower hierarchy * pages. * * @param string $a * @param string $b * @return int Returns < 0 if $a is less than $b; > 0 if $a is greater than $b, and 0 if they are equal. */ function ft_pagesorter($a, $b){ $ac = count(explode(':',$a)); $bc = count(explode(':',$b)); if($ac < $bc){ return -1; }elseif($ac > $bc){ return 1; } return strcmp ($a,$b); } /** * Sort pages by their mtime, from newest to oldest * * @param string $a * @param string $b * * @return int Returns < 0 if $a is newer than $b, > 0 if $b is newer than $a and 0 if they are of the same age */ function ft_pagemtimesorter($a, $b) { $mtimeA = filemtime(wikiFN($a)); $mtimeB = filemtime(wikiFN($b)); return $mtimeB - $mtimeA; } /** * Creates a snippet extract * * @author Andreas Gohr <andi@splitbrain.org> * @triggers FULLTEXT_SNIPPET_CREATE * * @param string $id page id * @param array $highlight * @return mixed */ function ft_snippet($id,$highlight){ $text = rawWiki($id); $text = str_replace("\xC2\xAD",'',$text); // remove soft-hyphens $evdata = array( 'id' => $id, 'text' => &$text, 'highlight' => &$highlight, 'snippet' => '', ); $evt = new Event('FULLTEXT_SNIPPET_CREATE',$evdata); if ($evt->advise_before()) { $match = array(); $snippets = array(); $utf8_offset = $offset = $end = 0; $len = \dokuwiki\Utf8\PhpString::strlen($text); // build a regexp from the phrases to highlight $re1 = '(' . join( '|', array_map( 'ft_snippet_re_preprocess', array_map( 'preg_quote_cb', array_filter((array) $highlight) ) ) ) . ')'; $re2 = "$re1.{0,75}(?!\\1)$re1"; $re3 = "$re1.{0,45}(?!\\1)$re1.{0,45}(?!\\1)(?!\\2)$re1"; for ($cnt=4; $cnt--;) { if (0) { } else if (preg_match('/'.$re3.'/iu',$text,$match,PREG_OFFSET_CAPTURE,$offset)) { } else if (preg_match('/'.$re2.'/iu',$text,$match,PREG_OFFSET_CAPTURE,$offset)) { } else if (preg_match('/'.$re1.'/iu',$text,$match,PREG_OFFSET_CAPTURE,$offset)) { } else { break; } list($str,$idx) = $match[0]; // convert $idx (a byte offset) into a utf8 character offset $utf8_idx = \dokuwiki\Utf8\PhpString::strlen(substr($text,0,$idx)); $utf8_len = \dokuwiki\Utf8\PhpString::strlen($str); // establish context, 100 bytes surrounding the match string // first look to see if we can go 100 either side, // then drop to 50 adding any excess if the other side can't go to 50, $pre = min($utf8_idx-$utf8_offset,100); $post = min($len-$utf8_idx-$utf8_len,100); if ($pre>50 && $post>50) { $pre = $post = 50; } else if ($pre>50) { $pre = min($pre,100-$post); } else if ($post>50) { $post = min($post, 100-$pre); } else if ($offset == 0) { // both are less than 50, means the context is the whole string // make it so and break out of this loop - there is no need for the // complex snippet calculations $snippets = array($text); break; } // establish context start and end points, try to append to previous // context if possible $start = $utf8_idx - $pre; $append = ($start < $end) ? $end : false; // still the end of the previous context snippet $end = $utf8_idx + $utf8_len + $post; // now set it to the end of this context if ($append) { $snippets[count($snippets)-1] .= \dokuwiki\Utf8\PhpString::substr($text,$append,$end-$append); } else { $snippets[] = \dokuwiki\Utf8\PhpString::substr($text,$start,$end-$start); } // set $offset for next match attempt // continue matching after the current match // if the current match is not the longest possible match starting at the current offset // this prevents further matching of this snippet but for possible matches of length // smaller than match length + context (at least 50 characters) this match is part of the context $utf8_offset = $utf8_idx + $utf8_len; $offset = $idx + strlen(\dokuwiki\Utf8\PhpString::substr($text,$utf8_idx,$utf8_len)); $offset = \dokuwiki\Utf8\Clean::correctIdx($text,$offset); } $m = "\1"; $snippets = preg_replace('/'.$re1.'/iu',$m.'$1'.$m,$snippets); $snippet = preg_replace( '/' . $m . '([^' . $m . ']*?)' . $m . '/iu', '<strong class="search_hit">$1</strong>', hsc(join('... ', $snippets)) ); $evdata['snippet'] = $snippet; } $evt->advise_after(); unset($evt); return $evdata['snippet']; } /** * Wraps a search term in regex boundary checks. * * @param string $term * @return string */ function ft_snippet_re_preprocess($term) { // do not process asian terms where word boundaries are not explicit if(\dokuwiki\Utf8\Asian::isAsianWords($term)) return $term; if (UTF8_PROPERTYSUPPORT) { // unicode word boundaries // see http://stackoverflow.com/a/2449017/172068 $BL = '(?<!\pL)'; $BR = '(?!\pL)'; } else { // not as correct as above, but at least won't break $BL = '\b'; $BR = '\b'; } if(substr($term,0,2) == '\\*'){ $term = substr($term,2); }else{ $term = $BL.$term; } if(substr($term,-2,2) == '\\*'){ $term = substr($term,0,-2); }else{ $term = $term.$BR; } if($term == $BL || $term == $BR || $term == $BL.$BR) $term = ''; return $term; } /** * Combine found documents and sum up their scores * * This function is used to combine searched words with a logical * AND. Only documents available in all arrays are returned. * * based upon PEAR's PHP_Compat function for array_intersect_key() * * @param array $args An array of page arrays * @return array */ function ft_resultCombine($args){ $array_count = count($args); if($array_count == 1){ return $args[0]; } $result = array(); if ($array_count > 1) { foreach ($args[0] as $key => $value) { $result[$key] = $value; for ($i = 1; $i !== $array_count; $i++) { if (!isset($args[$i][$key])) { unset($result[$key]); break; } $result[$key] += $args[$i][$key]; } } } return $result; } /** * Unites found documents and sum up their scores * * based upon ft_resultCombine() function * * @param array $args An array of page arrays * @return array * * @author Kazutaka Miyasaka <kazmiya@gmail.com> */ function ft_resultUnite($args) { $array_count = count($args); if ($array_count === 1) { return $args[0]; } $result = $args[0]; for ($i = 1; $i !== $array_count; $i++) { foreach (array_keys($args[$i]) as $id) { $result[$id] += $args[$i][$id]; } } return $result; } /** * Computes the difference of documents using page id for comparison * * nearly identical to PHP5's array_diff_key() * * @param array $args An array of page arrays * @return array * * @author Kazutaka Miyasaka <kazmiya@gmail.com> */ function ft_resultComplement($args) { $array_count = count($args); if ($array_count === 1) { return $args[0]; } $result = $args[0]; foreach (array_keys($result) as $id) { for ($i = 1; $i !== $array_count; $i++) { if (isset($args[$i][$id])) unset($result[$id]); } } return $result; } /** * Parses a search query and builds an array of search formulas * * @author Andreas Gohr <andi@splitbrain.org> * @author Kazutaka Miyasaka <kazmiya@gmail.com> * * @param dokuwiki\Search\Indexer $Indexer * @param string $query search query * @return array of search formulas */ function ft_queryParser($Indexer, $query){ /** * parse a search query and transform it into intermediate representation * * in a search query, you can use the following expressions: * * words: * include * -exclude * phrases: * "phrase to be included" * -"phrase you want to exclude" * namespaces: * @include:namespace (or ns:include:namespace) * ^exclude:namespace (or -ns:exclude:namespace) * groups: * () * -() * operators: * and ('and' is the default operator: you can always omit this) * or (or pipe symbol '|', lower precedence than 'and') * * e.g. a query [ aa "bb cc" @dd:ee ] means "search pages which contain * a word 'aa', a phrase 'bb cc' and are within a namespace 'dd:ee'". * this query is equivalent to [ -(-aa or -"bb cc" or -ns:dd:ee) ] * as long as you don't mind hit counts. * * intermediate representation consists of the following parts: * * ( ) - group * AND - logical and * OR - logical or * NOT - logical not * W+:, W-:, W_: - word (underscore: no need to highlight) * P+:, P-: - phrase (minus sign: logically in NOT group) * N+:, N-: - namespace */ $parsed_query = ''; $parens_level = 0; $terms = preg_split('/(-?".*?")/u', \dokuwiki\Utf8\PhpString::strtolower($query), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); foreach ($terms as $term) { $parsed = ''; if (preg_match('/^(-?)"(.+)"$/u', $term, $matches)) { // phrase-include and phrase-exclude $not = $matches[1] ? 'NOT' : ''; $parsed = $not.ft_termParser($Indexer, $matches[2], false, true); } else { // fix incomplete phrase $term = str_replace('"', ' ', $term); // fix parentheses $term = str_replace(')' , ' ) ', $term); $term = str_replace('(' , ' ( ', $term); $term = str_replace('- (', ' -(', $term); // treat pipe symbols as 'OR' operators $term = str_replace('|', ' or ', $term); // treat ideographic spaces (U+3000) as search term separators // FIXME: some more separators? $term = preg_replace('/[ \x{3000}]+/u', ' ', $term); $term = trim($term); if ($term === '') continue; $tokens = explode(' ', $term); foreach ($tokens as $token) { if ($token === '(') { // parenthesis-include-open $parsed .= '('; ++$parens_level; } elseif ($token === '-(') { // parenthesis-exclude-open $parsed .= 'NOT('; ++$parens_level; } elseif ($token === ')') { // parenthesis-any-close if ($parens_level === 0) continue; $parsed .= ')'; $parens_level--; } elseif ($token === 'and') { // logical-and (do nothing) } elseif ($token === 'or') { // logical-or $parsed .= 'OR'; } elseif (preg_match('/^(?:\^|-ns:)(.+)$/u', $token, $matches)) { // namespace-exclude $parsed .= 'NOT(N+:'.$matches[1].')'; } elseif (preg_match('/^(?:@|ns:)(.+)$/u', $token, $matches)) { // namespace-include $parsed .= '(N+:'.$matches[1].')'; } elseif (preg_match('/^-(.+)$/', $token, $matches)) { // word-exclude $parsed .= 'NOT('.ft_termParser($Indexer, $matches[1]).')'; } else { // word-include $parsed .= ft_termParser($Indexer, $token); } } } $parsed_query .= $parsed; } // cleanup (very sensitive) $parsed_query .= str_repeat(')', $parens_level); do { $parsed_query_old = $parsed_query; $parsed_query = preg_replace('/(NOT)?\(\)/u', '', $parsed_query); } while ($parsed_query !== $parsed_query_old); $parsed_query = preg_replace('/(NOT|OR)+\)/u', ')' , $parsed_query); $parsed_query = preg_replace('/(OR)+/u' , 'OR' , $parsed_query); $parsed_query = preg_replace('/\(OR/u' , '(' , $parsed_query); $parsed_query = preg_replace('/^OR|OR$/u' , '' , $parsed_query); $parsed_query = preg_replace('/\)(NOT)?\(/u' , ')AND$1(', $parsed_query); // adjustment: make highlightings right $parens_level = 0; $notgrp_levels = array(); $parsed_query_new = ''; $tokens = preg_split('/(NOT\(|[()])/u', $parsed_query, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); foreach ($tokens as $token) { if ($token === 'NOT(') { $notgrp_levels[] = ++$parens_level; } elseif ($token === '(') { ++$parens_level; } elseif ($token === ')') { if ($parens_level-- === end($notgrp_levels)) array_pop($notgrp_levels); } elseif (count($notgrp_levels) % 2 === 1) { // turn highlight-flag off if terms are logically in "NOT" group $token = preg_replace('/([WPN])\+\:/u', '$1-:', $token); } $parsed_query_new .= $token; } $parsed_query = $parsed_query_new; /** * convert infix notation string into postfix (Reverse Polish notation) array * by Shunting-yard algorithm * * see: http://en.wikipedia.org/wiki/Reverse_Polish_notation * see: http://en.wikipedia.org/wiki/Shunting-yard_algorithm */ $parsed_ary = array(); $ope_stack = array(); $ope_precedence = array(')' => 1, 'OR' => 2, 'AND' => 3, 'NOT' => 4, '(' => 5); $ope_regex = '/([()]|OR|AND|NOT)/u'; $tokens = preg_split($ope_regex, $parsed_query, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); foreach ($tokens as $token) { if (preg_match($ope_regex, $token)) { // operator $last_ope = end($ope_stack); while ($last_ope !== false && $ope_precedence[$token] <= $ope_precedence[$last_ope] && $last_ope != '(') { $parsed_ary[] = array_pop($ope_stack); $last_ope = end($ope_stack); } if ($token == ')') { array_pop($ope_stack); // this array_pop always deletes '(' } else { $ope_stack[] = $token; } } else { // operand $token_decoded = str_replace(array('OP', 'CP'), array('(', ')'), $token); $parsed_ary[] = $token_decoded; } } $parsed_ary = array_values(array_merge($parsed_ary, array_reverse($ope_stack))); // cleanup: each double "NOT" in RPN array actually does nothing $parsed_ary_count = count($parsed_ary); for ($i = 1; $i < $parsed_ary_count; ++$i) { if ($parsed_ary[$i] === 'NOT' && $parsed_ary[$i - 1] === 'NOT') { unset($parsed_ary[$i], $parsed_ary[$i - 1]); } } $parsed_ary = array_values($parsed_ary); // build return value $q = array(); $q['query'] = $query; $q['parsed_str'] = $parsed_query; $q['parsed_ary'] = $parsed_ary; foreach ($q['parsed_ary'] as $token) { if ($token[2] !== ':') continue; $body = substr($token, 3); switch (substr($token, 0, 3)) { case 'N+:': $q['ns'][] = $body; // for backward compatibility break; case 'N-:': $q['notns'][] = $body; // for backward compatibility break; case 'W_:': $q['words'][] = $body; break; case 'W-:': $q['words'][] = $body; $q['not'][] = $body; // for backward compatibility break; case 'W+:': $q['words'][] = $body; $q['highlight'][] = $body; $q['and'][] = $body; // for backward compatibility break; case 'P-:': $q['phrases'][] = $body; break; case 'P+:': $q['phrases'][] = $body; $q['highlight'][] = $body; break; } } foreach (array('words', 'phrases', 'highlight', 'ns', 'notns', 'and', 'not') as $key) { $q[$key] = empty($q[$key]) ? array() : array_values(array_unique($q[$key])); } return $q; } /** * Transforms given search term into intermediate representation * * This function is used in ft_queryParser() and not for general purpose use. * * @author Kazutaka Miyasaka <kazmiya@gmail.com> * * @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) { $parsed = ''; if ($consider_asian) { // successive asian characters need to be searched as a phrase $words = \dokuwiki\Utf8\Asian::splitAsianWords($term); foreach ($words as $word) { $phrase_mode = $phrase_mode ? true : \dokuwiki\Utf8\Asian::isAsianWords($word); $parsed .= ft_termParser($Indexer, $word, false, $phrase_mode); } } else { $term_noparen = str_replace(array('(', ')'), ' ', $term); $words = $Indexer->tokenizer($term_noparen, true); // W_: no need to highlight if (empty($words)) { $parsed = '()'; // important: do not remove } elseif ($words[0] === $term) { $parsed = '(W+:'.$words[0].')'; } elseif ($phrase_mode) { $term_encoded = str_replace(array('(', ')'), array('OP', 'CP'), $term); $parsed = '((W_:'.implode(')(W_:', $words).')(P+:'.$term_encoded.'))'; } else { $parsed = '((W+:'.implode(')(W+:', $words).'))'; } } return $parsed; } /** * Recreate a search query string based on parsed parts, doesn't support negated phrases and `OR` searches * * @param array $and * @param array $not * @param array $phrases * @param array $ns * @param array $notns * * @return string */ function ft_queryUnparser_simple(array $and, array $not, array $phrases, array $ns, array $notns) { $query = implode(' ', $and); if (!empty($not)) { $query .= ' -' . implode(' -', $not); } if (!empty($phrases)) { $query .= ' "' . implode('" "', $phrases) . '"'; } if (!empty($ns)) { $query .= ' @' . implode(' @', $ns); } if (!empty($notns)) { $query .= ' ^' . implode(' ^', $notns); } return $query; } //Setup VIM: ex: et ts=4 : preload.php.dist 0000644 00000001167 15233462216 0007656 0 ustar 00 <?php /** * This is an example for a farm setup. Simply copy this file to preload.php and * uncomment what you need. See http://www.dokuwiki.org/farms for more information. * You can also use preload.php for other things than farming, e.g. for moving * local configuration files out of the main ./conf directory. */ // set this to your farm directory //if(!defined('DOKU_FARMDIR')) define('DOKU_FARMDIR', '/var/www/farm'); // include this after DOKU_FARMDIR if you want to use farms //include(fullpath(dirname(__FILE__)).'/farm.php'); // you can overwrite the $config_cascade to your liking //$config_cascade = array( //); JpegMeta.php 0000644 00000333662 15233462216 0006772 0 ustar 00 <?php /** * JPEG metadata reader/writer * * @license BSD <http://www.opensource.org/licenses/bsd-license.php> * @link http://github.com/sd/jpeg-php * @author Sebastian Delmont <sdelmont@zonageek.com> * @author Andreas Gohr <andi@splitbrain.org> * @author Hakan Sandell <hakan.sandell@mydata.se> * @todo Add support for Maker Notes, Extend for GIF and PNG metadata */ // Original copyright notice: // // Copyright (c) 2003 Sebastian Delmont <sdelmont@zonageek.com> // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // 3. Neither the name of the author nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE class JpegMeta { var $_fileName; var $_fp = null; var $_fpout = null; var $_type = 'unknown'; var $_markers; var $_info; /** * Constructor * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @param $fileName */ function __construct($fileName) { $this->_fileName = $fileName; $this->_fp = null; $this->_type = 'unknown'; unset($this->_info); unset($this->_markers); } /** * Returns all gathered info as multidim array * * @author Sebastian Delmont <sdelmont@zonageek.com> */ function & getRawInfo() { $this->_parseAll(); if ($this->_markers == null) { return false; } return $this->_info; } /** * Returns basic image info * * @author Sebastian Delmont <sdelmont@zonageek.com> */ function & getBasicInfo() { $this->_parseAll(); $info = array(); if ($this->_markers == null) { return false; } $info['Name'] = $this->_info['file']['Name']; if (isset($this->_info['file']['Url'])) { $info['Url'] = $this->_info['file']['Url']; $info['NiceSize'] = "???KB"; } else { $info['Size'] = $this->_info['file']['Size']; $info['NiceSize'] = $this->_info['file']['NiceSize']; } if (@isset($this->_info['sof']['Format'])) { $info['Format'] = $this->_info['sof']['Format'] . " JPEG"; } else { $info['Format'] = $this->_info['sof']['Format'] . " JPEG"; } if (@isset($this->_info['sof']['ColorChannels'])) { $info['ColorMode'] = ($this->_info['sof']['ColorChannels'] > 1) ? "Color" : "B&W"; } $info['Width'] = $this->getWidth(); $info['Height'] = $this->getHeight(); $info['DimStr'] = $this->getDimStr(); $dates = $this->getDates(); $info['DateTime'] = $dates['EarliestTime']; $info['DateTimeStr'] = $dates['EarliestTimeStr']; $info['HasThumbnail'] = $this->hasThumbnail(); return $info; } /** * Convinience function to access nearly all available Data * through one function * * @author Andreas Gohr <andi@splitbrain.org> * * @param array|string $fields field name or array with field names * @return bool|string */ function getField($fields) { if(!is_array($fields)) $fields = array($fields); $info = false; foreach($fields as $field){ if(strtolower(substr($field,0,5)) == 'iptc.'){ $info = $this->getIPTCField(substr($field,5)); }elseif(strtolower(substr($field,0,5)) == 'exif.'){ $info = $this->getExifField(substr($field,5)); }elseif(strtolower(substr($field,0,4)) == 'xmp.'){ $info = $this->getXmpField(substr($field,4)); }elseif(strtolower(substr($field,0,5)) == 'file.'){ $info = $this->getFileField(substr($field,5)); }elseif(strtolower(substr($field,0,5)) == 'date.'){ $info = $this->getDateField(substr($field,5)); }elseif(strtolower($field) == 'simple.camera'){ $info = $this->getCamera(); }elseif(strtolower($field) == 'simple.raw'){ return $this->getRawInfo(); }elseif(strtolower($field) == 'simple.title'){ $info = $this->getTitle(); }elseif(strtolower($field) == 'simple.shutterspeed'){ $info = $this->getShutterSpeed(); }else{ $info = $this->getExifField($field); } if($info != false) break; } if($info === false) $info = ''; if(is_array($info)){ if(isset($info['val'])){ $info = $info['val']; }else{ $info = join(', ',$info); } } return trim($info); } /** * Convinience function to set nearly all available Data * through one function * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $field field name * @param string $value * @return bool success or fail */ function setField($field, $value) { if(strtolower(substr($field,0,5)) == 'iptc.'){ return $this->setIPTCField(substr($field,5),$value); }elseif(strtolower(substr($field,0,5)) == 'exif.'){ return $this->setExifField(substr($field,5),$value); }else{ return $this->setExifField($field,$value); } } /** * Convinience function to delete nearly all available Data * through one function * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $field field name * @return bool */ function deleteField($field) { if(strtolower(substr($field,0,5)) == 'iptc.'){ return $this->deleteIPTCField(substr($field,5)); }elseif(strtolower(substr($field,0,5)) == 'exif.'){ return $this->deleteExifField(substr($field,5)); }else{ return $this->deleteExifField($field); } } /** * Return a date field * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $field * @return false|string */ function getDateField($field) { if (!isset($this->_info['dates'])) { $this->_info['dates'] = $this->getDates(); } if (isset($this->_info['dates'][$field])) { return $this->_info['dates'][$field]; } return false; } /** * Return a file info field * * @author Andreas Gohr <andi@splitbrain.org> * * @param string $field field name * @return false|string */ function getFileField($field) { if (!isset($this->_info['file'])) { $this->_parseFileInfo(); } if (isset($this->_info['file'][$field])) { return $this->_info['file'][$field]; } return false; } /** * Return the camera info (Maker and Model) * * @author Andreas Gohr <andi@splitbrain.org> * @todo handle makernotes * * @return false|string */ function getCamera(){ $make = $this->getField(array('Exif.Make','Exif.TIFFMake')); $model = $this->getField(array('Exif.Model','Exif.TIFFModel')); $cam = trim("$make $model"); if(empty($cam)) return false; return $cam; } /** * Return shutter speed as a ratio * * @author Joe Lapp <joe.lapp@pobox.com> * * @return string */ function getShutterSpeed() { if (!isset($this->_info['exif'])) { $this->_parseMarkerExif(); } if(!isset($this->_info['exif']['ExposureTime'])){ return ''; } $field = $this->_info['exif']['ExposureTime']; if($field['den'] == 1) return $field['num']; return $field['num'].'/'.$field['den']; } /** * Return an EXIF field * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @param string $field field name * @return false|string */ function getExifField($field) { if (!isset($this->_info['exif'])) { $this->_parseMarkerExif(); } if ($this->_markers == null) { return false; } if (isset($this->_info['exif'][$field])) { return $this->_info['exif'][$field]; } return false; } /** * Return an XMP field * * @author Hakan Sandell <hakan.sandell@mydata.se> * * @param string $field field name * @return false|string */ function getXmpField($field) { if (!isset($this->_info['xmp'])) { $this->_parseMarkerXmp(); } if ($this->_markers == null) { return false; } if (isset($this->_info['xmp'][$field])) { return $this->_info['xmp'][$field]; } return false; } /** * Return an Adobe Field * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @param string $field field name * @return false|string */ function getAdobeField($field) { if (!isset($this->_info['adobe'])) { $this->_parseMarkerAdobe(); } if ($this->_markers == null) { return false; } if (isset($this->_info['adobe'][$field])) { return $this->_info['adobe'][$field]; } return false; } /** * Return an IPTC field * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @param string $field field name * @return false|string */ function getIPTCField($field) { if (!isset($this->_info['iptc'])) { $this->_parseMarkerAdobe(); } if ($this->_markers == null) { return false; } if (isset($this->_info['iptc'][$field])) { return $this->_info['iptc'][$field]; } return false; } /** * Set an EXIF field * * @author Sebastian Delmont <sdelmont@zonageek.com> * @author Joe Lapp <joe.lapp@pobox.com> * * @param string $field field name * @param string $value * @return bool */ function setExifField($field, $value) { if (!isset($this->_info['exif'])) { $this->_parseMarkerExif(); } if ($this->_markers == null) { return false; } if ($this->_info['exif'] == false) { $this->_info['exif'] = array(); } // 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] != ':') { $value = date('Y:m:d H:i:s', strtotime($value)); } } $this->_info['exif'][$field] = $value; return true; } /** * Set an Adobe Field * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @param string $field field name * @param string $value * @return bool */ function setAdobeField($field, $value) { if (!isset($this->_info['adobe'])) { $this->_parseMarkerAdobe(); } if ($this->_markers == null) { return false; } if ($this->_info['adobe'] == false) { $this->_info['adobe'] = array(); } $this->_info['adobe'][$field] = $value; return true; } /** * Calculates the multiplier needed to resize the image to the given * dimensions * * @author Andreas Gohr <andi@splitbrain.org> * * @param int $maxwidth * @param int $maxheight * @return float|int */ function getResizeRatio($maxwidth,$maxheight=0){ if(!$maxheight) $maxheight = $maxwidth; $w = $this->getField('File.Width'); $h = $this->getField('File.Height'); $ratio = 1; if($w >= $h){ if($w >= $maxwidth){ $ratio = $maxwidth/$w; }elseif($h > $maxheight){ $ratio = $maxheight/$h; } }else{ if($h >= $maxheight){ $ratio = $maxheight/$h; }elseif($w > $maxwidth){ $ratio = $maxwidth/$w; } } return $ratio; } /** * Set an IPTC field * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @param string $field field name * @param string $value * @return bool */ function setIPTCField($field, $value) { if (!isset($this->_info['iptc'])) { $this->_parseMarkerAdobe(); } if ($this->_markers == null) { return false; } if ($this->_info['iptc'] == false) { $this->_info['iptc'] = array(); } $this->_info['iptc'][$field] = $value; return true; } /** * Delete an EXIF field * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @param string $field field name * @return bool */ function deleteExifField($field) { if (!isset($this->_info['exif'])) { $this->_parseMarkerAdobe(); } if ($this->_markers == null) { return false; } if ($this->_info['exif'] != false) { unset($this->_info['exif'][$field]); } return true; } /** * Delete an Adobe field * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @param string $field field name * @return bool */ function deleteAdobeField($field) { if (!isset($this->_info['adobe'])) { $this->_parseMarkerAdobe(); } if ($this->_markers == null) { return false; } if ($this->_info['adobe'] != false) { unset($this->_info['adobe'][$field]); } return true; } /** * Delete an IPTC field * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @param string $field field name * @return bool */ function deleteIPTCField($field) { if (!isset($this->_info['iptc'])) { $this->_parseMarkerAdobe(); } if ($this->_markers == null) { return false; } if ($this->_info['iptc'] != false) { unset($this->_info['iptc'][$field]); } return true; } /** * Get the image's title, tries various fields * * @param int $max maximum number chars (keeps words) * @return false|string * * @author Andreas Gohr <andi@splitbrain.org> */ function getTitle($max=80){ // try various fields $cap = $this->getField(array('Iptc.Headline', 'Iptc.Caption', 'Xmp.dc:title', 'Exif.UserComment', 'Exif.TIFFUserComment', 'Exif.TIFFImageDescription', 'File.Name')); if (empty($cap)) return false; if(!$max) return $cap; // Shorten to 80 chars (keeping words) $new = preg_replace('/\n.+$/','',wordwrap($cap, $max)); if($new != $cap) $new .= '...'; return $new; } /** * Gather various date fields * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @return array|bool */ function getDates() { $this->_parseAll(); if ($this->_markers == null) { if (@isset($this->_info['file']['UnixTime'])) { $dates = array(); $dates['FileModified'] = $this->_info['file']['UnixTime']; $dates['Time'] = $this->_info['file']['UnixTime']; $dates['TimeSource'] = 'FileModified'; $dates['TimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']); $dates['EarliestTime'] = $this->_info['file']['UnixTime']; $dates['EarliestTimeSource'] = 'FileModified'; $dates['EarliestTimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']); $dates['LatestTime'] = $this->_info['file']['UnixTime']; $dates['LatestTimeSource'] = 'FileModified'; $dates['LatestTimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']); return $dates; } return false; } $dates = array(); $latestTime = 0; $latestTimeSource = ""; $earliestTime = time(); $earliestTimeSource = ""; if (@isset($this->_info['exif']['DateTime'])) { $dates['ExifDateTime'] = $this->_info['exif']['DateTime']; $aux = $this->_info['exif']['DateTime']; $aux[4] = "-"; $aux[7] = "-"; $t = strtotime($aux); if ($t && $t > $latestTime) { $latestTime = $t; $latestTimeSource = "ExifDateTime"; } if ($t && $t < $earliestTime) { $earliestTime = $t; $earliestTimeSource = "ExifDateTime"; } } if (@isset($this->_info['exif']['DateTimeOriginal'])) { $dates['ExifDateTimeOriginal'] = $this->_info['exif']['DateTime']; $aux = $this->_info['exif']['DateTimeOriginal']; $aux[4] = "-"; $aux[7] = "-"; $t = strtotime($aux); if ($t && $t > $latestTime) { $latestTime = $t; $latestTimeSource = "ExifDateTimeOriginal"; } if ($t && $t < $earliestTime) { $earliestTime = $t; $earliestTimeSource = "ExifDateTimeOriginal"; } } if (@isset($this->_info['exif']['DateTimeDigitized'])) { $dates['ExifDateTimeDigitized'] = $this->_info['exif']['DateTime']; $aux = $this->_info['exif']['DateTimeDigitized']; $aux[4] = "-"; $aux[7] = "-"; $t = strtotime($aux); if ($t && $t > $latestTime) { $latestTime = $t; $latestTimeSource = "ExifDateTimeDigitized"; } if ($t && $t < $earliestTime) { $earliestTime = $t; $earliestTimeSource = "ExifDateTimeDigitized"; } } if (@isset($this->_info['iptc']['DateCreated'])) { $dates['IPTCDateCreated'] = $this->_info['iptc']['DateCreated']; $aux = $this->_info['iptc']['DateCreated']; $aux = substr($aux, 0, 4) . "-" . substr($aux, 4, 2) . "-" . substr($aux, 6, 2); $t = strtotime($aux); if ($t && $t > $latestTime) { $latestTime = $t; $latestTimeSource = "IPTCDateCreated"; } if ($t && $t < $earliestTime) { $earliestTime = $t; $earliestTimeSource = "IPTCDateCreated"; } } if (@isset($this->_info['file']['UnixTime'])) { $dates['FileModified'] = $this->_info['file']['UnixTime']; $t = $this->_info['file']['UnixTime']; if ($t && $t > $latestTime) { $latestTime = $t; $latestTimeSource = "FileModified"; } if ($t && $t < $earliestTime) { $earliestTime = $t; $earliestTimeSource = "FileModified"; } } $dates['Time'] = $earliestTime; $dates['TimeSource'] = $earliestTimeSource; $dates['TimeStr'] = date("Y-m-d H:i:s", $earliestTime); $dates['EarliestTime'] = $earliestTime; $dates['EarliestTimeSource'] = $earliestTimeSource; $dates['EarliestTimeStr'] = date("Y-m-d H:i:s", $earliestTime); $dates['LatestTime'] = $latestTime; $dates['LatestTimeSource'] = $latestTimeSource; $dates['LatestTimeStr'] = date("Y-m-d H:i:s", $latestTime); return $dates; } /** * Get the image width, tries various fields * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @return false|string */ function getWidth() { if (!isset($this->_info['sof'])) { $this->_parseMarkerSOF(); } if ($this->_markers == null) { return false; } if (isset($this->_info['sof']['ImageWidth'])) { return $this->_info['sof']['ImageWidth']; } if (!isset($this->_info['exif'])) { $this->_parseMarkerExif(); } if (isset($this->_info['exif']['PixelXDimension'])) { return $this->_info['exif']['PixelXDimension']; } return false; } /** * Get the image height, tries various fields * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @return false|string */ function getHeight() { if (!isset($this->_info['sof'])) { $this->_parseMarkerSOF(); } if ($this->_markers == null) { return false; } if (isset($this->_info['sof']['ImageHeight'])) { return $this->_info['sof']['ImageHeight']; } if (!isset($this->_info['exif'])) { $this->_parseMarkerExif(); } if (isset($this->_info['exif']['PixelYDimension'])) { return $this->_info['exif']['PixelYDimension']; } return false; } /** * Get an dimension string for use in img tag * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @return false|string */ function getDimStr() { if ($this->_markers == null) { return false; } $w = $this->getWidth(); $h = $this->getHeight(); return "width='" . $w . "' height='" . $h . "'"; } /** * Checks for an embedded thumbnail * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @param string $which possible values: 'any', 'exif' or 'adobe' * @return false|string */ function hasThumbnail($which = 'any') { if (($which == 'any') || ($which == 'exif')) { if (!isset($this->_info['exif'])) { $this->_parseMarkerExif(); } if ($this->_markers == null) { return false; } if (isset($this->_info['exif']) && is_array($this->_info['exif'])) { if (isset($this->_info['exif']['JFIFThumbnail'])) { return 'exif'; } } } if ($which == 'adobe') { if (!isset($this->_info['adobe'])) { $this->_parseMarkerAdobe(); } if ($this->_markers == null) { return false; } if (isset($this->_info['adobe']) && is_array($this->_info['adobe'])) { if (isset($this->_info['adobe']['ThumbnailData'])) { return 'exif'; } } } return false; } /** * Send embedded thumbnail to browser * * @author Sebastian Delmont <sdelmont@zonageek.com> * * @param string $which possible values: 'any', 'exif' or 'adobe' * @return bool */ function sendThumbnail($which = 'any') { $data = null; if (($which == 'any') || ($which == 'exif')) { if (!isset($this->_info['exif'])) { $this->_parseMarkerExif(); } if ($this->_markers == null) { return false; } if (isset($this->_info['exif']) && is_array($this->_info['exif'])) { if (isset($this->_info['exif']['JFIFThumbnail'])) { $data =& $this->_info['exif']['JFIFThumbnail']; } } } if (($which == 'adobe') || ($data == null)){ if (!isset($this->_info['adobe'])) { $this->_parseMarkerAdobe(); } if ($this->_markers == null) { return false; } if (isset($this->_info['adobe']) && is_array($this->_info['adobe'])) { if (isset($this->_info['adobe']['ThumbnailData'])) { $data =& $this->_info['adobe']['ThumbnailData']; } } } if ($data != null) { header("Content-type: image/jpeg"); echo $data; return true; } return false; } /** * Save changed Metadata * * @author Sebastian Delmont <sdelmont@zonageek.com> * @author Andreas Gohr <andi@splitbrain.org> * * @param string $fileName file name or empty string for a random name * @return bool */ function save($fileName = "") { if ($fileName == "") { $tmpName = tempnam(dirname($this->_fileName),'_metatemp_'); $this->_writeJPEG($tmpName); if (file_exists($tmpName)) { return io_rename($tmpName, $this->_fileName); } } else { return $this->_writeJPEG($fileName); } return false; } /*************************************************************/ /* PRIVATE FUNCTIONS (Internal Use Only!) */ /*************************************************************/ /*************************************************************/ function _dispose($fileName = "") { $this->_fileName = $fileName; $this->_fp = null; $this->_type = 'unknown'; unset($this->_markers); unset($this->_info); } /*************************************************************/ function _readJPEG() { unset($this->_markers); //unset($this->_info); $this->_markers = array(); //$this->_info = array(); $this->_fp = @fopen($this->_fileName, 'rb'); if ($this->_fp) { if (file_exists($this->_fileName)) { $this->_type = 'file'; } else { $this->_type = 'url'; } } else { $this->_fp = null; return false; // ERROR: Can't open file } // Check for the JPEG signature $c1 = ord(fgetc($this->_fp)); $c2 = ord(fgetc($this->_fp)); if ($c1 != 0xFF || $c2 != 0xD8) { // (0xFF + SOI) $this->_markers = null; return false; // ERROR: File is not a JPEG } $count = 0; $done = false; $ok = true; while (!$done) { $capture = false; // First, skip any non 0xFF bytes $discarded = 0; $c = ord(fgetc($this->_fp)); while (!feof($this->_fp) && ($c != 0xFF)) { $discarded++; $c = ord(fgetc($this->_fp)); } // Then skip all 0xFF until the marker byte do { $marker = ord(fgetc($this->_fp)); } while (!feof($this->_fp) && ($marker == 0xFF)); if (feof($this->_fp)) { return false; // ERROR: Unexpected EOF } if ($discarded != 0) { return false; // ERROR: Extraneous data } $length = ord(fgetc($this->_fp)) * 256 + ord(fgetc($this->_fp)); if (feof($this->_fp)) { return false; // ERROR: Unexpected EOF } if ($length < 2) { return false; // ERROR: Extraneous data } $length = $length - 2; // The length we got counts itself switch ($marker) { case 0xC0: // SOF0 case 0xC1: // SOF1 case 0xC2: // SOF2 case 0xC9: // SOF9 case 0xE0: // APP0: JFIF data case 0xE1: // APP1: EXIF or XMP data case 0xED: // APP13: IPTC / Photoshop data $capture = true; break; case 0xDA: // SOS: Start of scan... the image itself and the last block on the file $capture = false; $length = -1; // This field has no length... it includes all data until EOF $done = true; break; default: $capture = true;//false; break; } $this->_markers[$count] = array(); $this->_markers[$count]['marker'] = $marker; $this->_markers[$count]['length'] = $length; if ($capture) { if ($length) $this->_markers[$count]['data'] = fread($this->_fp, $length); else $this->_markers[$count]['data'] = ""; } elseif (!$done) { $result = @fseek($this->_fp, $length, SEEK_CUR); // fseek doesn't seem to like HTTP 'files', but fgetc has no problem if (!($result === 0)) { for ($i = 0; $i < $length; $i++) { fgetc($this->_fp); } } } $count++; } if ($this->_fp) { fclose($this->_fp); $this->_fp = null; } return $ok; } /*************************************************************/ function _parseAll() { if (!isset($this->_info['file'])) { $this->_parseFileInfo(); } if (!isset($this->_markers)) { $this->_readJPEG(); } if ($this->_markers == null) { return false; } if (!isset($this->_info['jfif'])) { $this->_parseMarkerJFIF(); } if (!isset($this->_info['jpeg'])) { $this->_parseMarkerSOF(); } if (!isset($this->_info['exif'])) { $this->_parseMarkerExif(); } if (!isset($this->_info['xmp'])) { $this->_parseMarkerXmp(); } if (!isset($this->_info['adobe'])) { $this->_parseMarkerAdobe(); } } /*************************************************************/ /** * @param string $outputName * * @return bool */ function _writeJPEG($outputName) { $this->_parseAll(); $wroteEXIF = false; $wroteAdobe = false; $this->_fp = @fopen($this->_fileName, 'r'); if ($this->_fp) { if (file_exists($this->_fileName)) { $this->_type = 'file'; } else { $this->_type = 'url'; } } else { $this->_fp = null; return false; // ERROR: Can't open file } $this->_fpout = fopen($outputName, 'wb'); if (!$this->_fpout) { $this->_fpout = null; fclose($this->_fp); $this->_fp = null; return false; // ERROR: Can't open output file } // Check for the JPEG signature $c1 = ord(fgetc($this->_fp)); $c2 = ord(fgetc($this->_fp)); if ($c1 != 0xFF || $c2 != 0xD8) { // (0xFF + SOI) return false; // ERROR: File is not a JPEG } fputs($this->_fpout, chr(0xFF), 1); fputs($this->_fpout, chr(0xD8), 1); // (0xFF + SOI) $count = 0; $done = false; $ok = true; while (!$done) { // First, skip any non 0xFF bytes $discarded = 0; $c = ord(fgetc($this->_fp)); while (!feof($this->_fp) && ($c != 0xFF)) { $discarded++; $c = ord(fgetc($this->_fp)); } // Then skip all 0xFF until the marker byte do { $marker = ord(fgetc($this->_fp)); } while (!feof($this->_fp) && ($marker == 0xFF)); if (feof($this->_fp)) { $ok = false; break; // ERROR: Unexpected EOF } if ($discarded != 0) { $ok = false; break; // ERROR: Extraneous data } $length = ord(fgetc($this->_fp)) * 256 + ord(fgetc($this->_fp)); if (feof($this->_fp)) { $ok = false; break; // ERROR: Unexpected EOF } if ($length < 2) { $ok = false; break; // ERROR: Extraneous data } $length = $length - 2; // The length we got counts itself unset($data); if ($marker == 0xE1) { // APP1: EXIF data $data =& $this->_createMarkerEXIF(); $wroteEXIF = true; } elseif ($marker == 0xED) { // APP13: IPTC / Photoshop data $data =& $this->_createMarkerAdobe(); $wroteAdobe = true; } elseif ($marker == 0xDA) { // SOS: Start of scan... the image itself and the last block on the file $done = true; } if (!$wroteEXIF && (($marker < 0xE0) || ($marker > 0xEF))) { if (isset($this->_info['exif']) && is_array($this->_info['exif'])) { $exif =& $this->_createMarkerEXIF(); $this->_writeJPEGMarker(0xE1, strlen($exif), $exif, 0); unset($exif); } $wroteEXIF = true; } if (!$wroteAdobe && (($marker < 0xE0) || ($marker > 0xEF))) { if ((isset($this->_info['adobe']) && is_array($this->_info['adobe'])) || (isset($this->_info['iptc']) && is_array($this->_info['iptc']))) { $adobe =& $this->_createMarkerAdobe(); $this->_writeJPEGMarker(0xED, strlen($adobe), $adobe, 0); unset($adobe); } $wroteAdobe = true; } $origLength = $length; if (isset($data)) { $length = strlen($data); } if ($marker != -1) { $this->_writeJPEGMarker($marker, $length, $data, $origLength); } } if ($this->_fp) { fclose($this->_fp); $this->_fp = null; } if ($this->_fpout) { fclose($this->_fpout); $this->_fpout = null; } return $ok; } /*************************************************************/ /** * @param integer $marker * @param integer $length * @param string $data * @param integer $origLength * * @return bool */ function _writeJPEGMarker($marker, $length, &$data, $origLength) { if ($length <= 0) { return false; } fputs($this->_fpout, chr(0xFF), 1); fputs($this->_fpout, chr($marker), 1); fputs($this->_fpout, chr((($length + 2) & 0x0000FF00) >> 8), 1); fputs($this->_fpout, chr((($length + 2) & 0x000000FF) >> 0), 1); if (isset($data)) { // Copy the generated data fputs($this->_fpout, $data, $length); if ($origLength > 0) { // Skip the original data $result = @fseek($this->_fp, $origLength, SEEK_CUR); // fseek doesn't seem to like HTTP 'files', but fgetc has no problem if ($result != 0) { for ($i = 0; $i < $origLength; $i++) { fgetc($this->_fp); } } } } else { if ($marker == 0xDA) { // Copy until EOF while (!feof($this->_fp)) { $data = fread($this->_fp, 1024 * 16); fputs($this->_fpout, $data, strlen($data)); } } else { // Copy only $length bytes $data = @fread($this->_fp, $length); fputs($this->_fpout, $data, $length); } } return true; } /** * Gets basic info from the file - should work with non-JPEGs * * @author Sebastian Delmont <sdelmont@zonageek.com> * @author Andreas Gohr <andi@splitbrain.org> */ function _parseFileInfo() { if (file_exists($this->_fileName) && is_file($this->_fileName)) { $this->_info['file'] = array(); $this->_info['file']['Name'] = utf8_decodeFN(\dokuwiki\Utf8\PhpString::basename($this->_fileName)); $this->_info['file']['Path'] = fullpath($this->_fileName); $this->_info['file']['Size'] = filesize($this->_fileName); if ($this->_info['file']['Size'] < 1024) { $this->_info['file']['NiceSize'] = $this->_info['file']['Size'] . 'B'; } elseif ($this->_info['file']['Size'] < (1024 * 1024)) { $this->_info['file']['NiceSize'] = round($this->_info['file']['Size'] / 1024) . 'KB'; } elseif ($this->_info['file']['Size'] < (1024 * 1024 * 1024)) { $this->_info['file']['NiceSize'] = round($this->_info['file']['Size'] / (1024*1024)) . 'MB'; } else { $this->_info['file']['NiceSize'] = $this->_info['file']['Size'] . 'B'; } $this->_info['file']['UnixTime'] = filemtime($this->_fileName); // get image size directly from file $size = getimagesize($this->_fileName); $this->_info['file']['Width'] = $size[0]; $this->_info['file']['Height'] = $size[1]; // set mime types and formats // http://php.net/manual/en/function.getimagesize.php // http://php.net/manual/en/function.image-type-to-mime-type.php switch ($size[2]){ case 1: $this->_info['file']['Mime'] = 'image/gif'; $this->_info['file']['Format'] = 'GIF'; break; case 2: $this->_info['file']['Mime'] = 'image/jpeg'; $this->_info['file']['Format'] = 'JPEG'; break; case 3: $this->_info['file']['Mime'] = 'image/png'; $this->_info['file']['Format'] = 'PNG'; break; case 4: $this->_info['file']['Mime'] = 'application/x-shockwave-flash'; $this->_info['file']['Format'] = 'SWF'; break; case 5: $this->_info['file']['Mime'] = 'image/psd'; $this->_info['file']['Format'] = 'PSD'; break; case 6: $this->_info['file']['Mime'] = 'image/bmp'; $this->_info['file']['Format'] = 'BMP'; break; case 7: $this->_info['file']['Mime'] = 'image/tiff'; $this->_info['file']['Format'] = 'TIFF (Intel)'; break; case 8: $this->_info['file']['Mime'] = 'image/tiff'; $this->_info['file']['Format'] = 'TIFF (Motorola)'; break; case 9: $this->_info['file']['Mime'] = 'application/octet-stream'; $this->_info['file']['Format'] = 'JPC'; break; case 10: $this->_info['file']['Mime'] = 'image/jp2'; $this->_info['file']['Format'] = 'JP2'; break; case 11: $this->_info['file']['Mime'] = 'application/octet-stream'; $this->_info['file']['Format'] = 'JPX'; break; case 12: $this->_info['file']['Mime'] = 'application/octet-stream'; $this->_info['file']['Format'] = 'JB2'; break; case 13: $this->_info['file']['Mime'] = 'application/x-shockwave-flash'; $this->_info['file']['Format'] = 'SWC'; break; case 14: $this->_info['file']['Mime'] = 'image/iff'; $this->_info['file']['Format'] = 'IFF'; break; case 15: $this->_info['file']['Mime'] = 'image/vnd.wap.wbmp'; $this->_info['file']['Format'] = 'WBMP'; break; case 16: $this->_info['file']['Mime'] = 'image/xbm'; $this->_info['file']['Format'] = 'XBM'; break; default: $this->_info['file']['Mime'] = 'image/unknown'; } } else { $this->_info['file'] = array(); $this->_info['file']['Name'] = \dokuwiki\Utf8\PhpString::basename($this->_fileName); $this->_info['file']['Url'] = $this->_fileName; } return true; } /*************************************************************/ function _parseMarkerJFIF() { if (!isset($this->_markers)) { $this->_readJPEG(); } if ($this->_markers == null) { return false; } $data = null; $count = count($this->_markers); for ($i = 0; $i < $count; $i++) { if ($this->_markers[$i]['marker'] == 0xE0) { $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 4); if ($signature == 'JFIF') { $data =& $this->_markers[$i]['data']; break; } } } if ($data == null) { $this->_info['jfif'] = false; return false; } $this->_info['jfif'] = array(); $vmaj = $this->_getByte($data, 5); $vmin = $this->_getByte($data, 6); $this->_info['jfif']['Version'] = sprintf('%d.%02d', $vmaj, $vmin); $units = $this->_getByte($data, 7); switch ($units) { case 0: $this->_info['jfif']['Units'] = 'pixels'; break; case 1: $this->_info['jfif']['Units'] = 'dpi'; break; case 2: $this->_info['jfif']['Units'] = 'dpcm'; break; default: $this->_info['jfif']['Units'] = 'unknown'; break; } $xdens = $this->_getShort($data, 8); $ydens = $this->_getShort($data, 10); $this->_info['jfif']['XDensity'] = $xdens; $this->_info['jfif']['YDensity'] = $ydens; $thumbx = $this->_getByte($data, 12); $thumby = $this->_getByte($data, 13); $this->_info['jfif']['ThumbnailWidth'] = $thumbx; $this->_info['jfif']['ThumbnailHeight'] = $thumby; return true; } /*************************************************************/ function _parseMarkerSOF() { if (!isset($this->_markers)) { $this->_readJPEG(); } if ($this->_markers == null) { return false; } $data = null; $count = count($this->_markers); for ($i = 0; $i < $count; $i++) { switch ($this->_markers[$i]['marker']) { case 0xC0: // SOF0 case 0xC1: // SOF1 case 0xC2: // SOF2 case 0xC9: // SOF9 $data =& $this->_markers[$i]['data']; $marker = $this->_markers[$i]['marker']; break; } } if ($data == null) { $this->_info['sof'] = false; return false; } $pos = 0; $this->_info['sof'] = array(); switch ($marker) { case 0xC0: // SOF0 $format = 'Baseline'; break; case 0xC1: // SOF1 $format = 'Progessive'; break; case 0xC2: // SOF2 $format = 'Non-baseline'; break; case 0xC9: // SOF9 $format = 'Arithmetic'; break; default: return false; } $this->_info['sof']['Format'] = $format; $this->_info['sof']['SamplePrecision'] = $this->_getByte($data, $pos + 0); $this->_info['sof']['ImageHeight'] = $this->_getShort($data, $pos + 1); $this->_info['sof']['ImageWidth'] = $this->_getShort($data, $pos + 3); $this->_info['sof']['ColorChannels'] = $this->_getByte($data, $pos + 5); return true; } /** * Parses the XMP data * * @author Hakan Sandell <hakan.sandell@mydata.se> */ function _parseMarkerXmp() { if (!isset($this->_markers)) { $this->_readJPEG(); } if ($this->_markers == null) { return false; } $data = null; $count = count($this->_markers); for ($i = 0; $i < $count; $i++) { if ($this->_markers[$i]['marker'] == 0xE1) { $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 29); if ($signature == "http://ns.adobe.com/xap/1.0/\0") { $data = substr($this->_markers[$i]['data'], 29); break; } } } if ($data == null) { $this->_info['xmp'] = false; return false; } $parser = xml_parser_create(); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); $result = xml_parse_into_struct($parser, $data, $values, $tags); xml_parser_free($parser); if ($result == 0) { $this->_info['xmp'] = false; return false; } $this->_info['xmp'] = array(); $count = count($values); for ($i = 0; $i < $count; $i++) { if ($values[$i]['tag'] == 'rdf:Description' && $values[$i]['type'] == 'open') { while ((++$i < $count) && ($values[$i]['tag'] != 'rdf:Description')) { $this->_parseXmpNode($values, $i, $this->_info['xmp'][$values[$i]['tag']], $count); } } } return true; } /** * Parses XMP nodes by recursion * * @author Hakan Sandell <hakan.sandell@mydata.se> * * @param array $values * @param int $i * @param mixed $meta * @param integer $count */ function _parseXmpNode($values, &$i, &$meta, $count) { if ($values[$i]['type'] == 'close') return; if ($values[$i]['type'] == 'complete') { // Simple Type property $meta = $values[$i]['value']; return; } $i++; if ($i >= $count) return; if ($values[$i]['tag'] == 'rdf:Bag' || $values[$i]['tag'] == 'rdf:Seq') { // Array property $meta = array(); while ($values[++$i]['tag'] == 'rdf:li') { $this->_parseXmpNode($values, $i, $meta[], $count); } $i++; // skip closing Bag/Seq tag } elseif ($values[$i]['tag'] == 'rdf:Alt') { // Language Alternative property, only the first (default) value is used if ($values[$i]['type'] == 'open') { $i++; $this->_parseXmpNode($values, $i, $meta, $count); while ((++$i < $count) && ($values[$i]['tag'] != 'rdf:Alt')); $i++; // skip closing Alt tag } } else { // Structure property $meta = array(); $startTag = $values[$i-1]['tag']; do { $this->_parseXmpNode($values, $i, $meta[$values[$i]['tag']], $count); } while ((++$i < $count) && ($values[$i]['tag'] != $startTag)); } } /*************************************************************/ function _parseMarkerExif() { if (!isset($this->_markers)) { $this->_readJPEG(); } if ($this->_markers == null) { return false; } $data = null; $count = count($this->_markers); for ($i = 0; $i < $count; $i++) { if ($this->_markers[$i]['marker'] == 0xE1) { $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 6); if ($signature == "Exif\0\0") { $data =& $this->_markers[$i]['data']; break; } } } if ($data == null) { $this->_info['exif'] = false; return false; } $pos = 6; $this->_info['exif'] = array(); // We don't increment $pos after this because Exif uses offsets relative to this point $byteAlign = $this->_getShort($data, $pos + 0); if ($byteAlign == 0x4949) { // "II" $isBigEndian = false; } elseif ($byteAlign == 0x4D4D) { // "MM" $isBigEndian = true; } else { return false; // Unexpected data } $alignCheck = $this->_getShort($data, $pos + 2, $isBigEndian); if ($alignCheck != 0x002A) // That's the expected value return false; // Unexpected data if ($isBigEndian) { $this->_info['exif']['ByteAlign'] = "Big Endian"; } else { $this->_info['exif']['ByteAlign'] = "Little Endian"; } $offsetIFD0 = $this->_getLong($data, $pos + 4, $isBigEndian); if ($offsetIFD0 < 8) return false; // Unexpected data $offsetIFD1 = $this->_readIFD($data, $pos, $offsetIFD0, $isBigEndian, 'ifd0'); if ($offsetIFD1 != 0) $this->_readIFD($data, $pos, $offsetIFD1, $isBigEndian, 'ifd1'); return true; } /*************************************************************/ /** * @param mixed $data * @param integer $base * @param integer $offset * @param boolean $isBigEndian * @param string $mode * * @return int */ function _readIFD($data, $base, $offset, $isBigEndian, $mode) { $EXIFTags = $this->_exifTagNames($mode); $numEntries = $this->_getShort($data, $base + $offset, $isBigEndian); $offset += 2; $exifTIFFOffset = 0; $exifTIFFLength = 0; $exifThumbnailOffset = 0; $exifThumbnailLength = 0; for ($i = 0; $i < $numEntries; $i++) { $tag = $this->_getShort($data, $base + $offset, $isBigEndian); $offset += 2; $type = $this->_getShort($data, $base + $offset, $isBigEndian); $offset += 2; $count = $this->_getLong($data, $base + $offset, $isBigEndian); $offset += 4; if (($type < 1) || ($type > 12)) return false; // Unexpected Type $typeLengths = array( -1, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 ); $dataLength = $typeLengths[$type] * $count; if ($dataLength > 4) { $dataOffset = $this->_getLong($data, $base + $offset, $isBigEndian); $rawValue = $this->_getFixedString($data, $base + $dataOffset, $dataLength); } else { $rawValue = $this->_getFixedString($data, $base + $offset, $dataLength); } $offset += 4; switch ($type) { case 1: // UBYTE if ($count == 1) { $value = $this->_getByte($rawValue, 0); } else { $value = array(); for ($j = 0; $j < $count; $j++) $value[$j] = $this->_getByte($rawValue, $j); } break; case 2: // ASCII $value = $rawValue; break; case 3: // USHORT if ($count == 1) { $value = $this->_getShort($rawValue, 0, $isBigEndian); } else { $value = array(); for ($j = 0; $j < $count; $j++) $value[$j] = $this->_getShort($rawValue, $j * 2, $isBigEndian); } break; case 4: // ULONG if ($count == 1) { $value = $this->_getLong($rawValue, 0, $isBigEndian); } else { $value = array(); for ($j = 0; $j < $count; $j++) $value[$j] = $this->_getLong($rawValue, $j * 4, $isBigEndian); } break; case 5: // URATIONAL if ($count == 1) { $a = $this->_getLong($rawValue, 0, $isBigEndian); $b = $this->_getLong($rawValue, 4, $isBigEndian); $value = array(); $value['val'] = 0; $value['num'] = $a; $value['den'] = $b; if (($a != 0) && ($b != 0)) { $value['val'] = $a / $b; } } else { $value = array(); for ($j = 0; $j < $count; $j++) { $a = $this->_getLong($rawValue, $j * 8, $isBigEndian); $b = $this->_getLong($rawValue, ($j * 8) + 4, $isBigEndian); $value = array(); $value[$j]['val'] = 0; $value[$j]['num'] = $a; $value[$j]['den'] = $b; if (($a != 0) && ($b != 0)) $value[$j]['val'] = $a / $b; } } break; case 6: // SBYTE if ($count == 1) { $value = $this->_getByte($rawValue, 0); } else { $value = array(); for ($j = 0; $j < $count; $j++) $value[$j] = $this->_getByte($rawValue, $j); } break; case 7: // UNDEFINED $value = $rawValue; break; case 8: // SSHORT if ($count == 1) { $value = $this->_getShort($rawValue, 0, $isBigEndian); } else { $value = array(); for ($j = 0; $j < $count; $j++) $value[$j] = $this->_getShort($rawValue, $j * 2, $isBigEndian); } break; case 9: // SLONG if ($count == 1) { $value = $this->_getLong($rawValue, 0, $isBigEndian); } else { $value = array(); for ($j = 0; $j < $count; $j++) $value[$j] = $this->_getLong($rawValue, $j * 4, $isBigEndian); } break; case 10: // SRATIONAL if ($count == 1) { $a = $this->_getLong($rawValue, 0, $isBigEndian); $b = $this->_getLong($rawValue, 4, $isBigEndian); $value = array(); $value['val'] = 0; $value['num'] = $a; $value['den'] = $b; if (($a != 0) && ($b != 0)) $value['val'] = $a / $b; } else { $value = array(); for ($j = 0; $j < $count; $j++) { $a = $this->_getLong($rawValue, $j * 8, $isBigEndian); $b = $this->_getLong($rawValue, ($j * 8) + 4, $isBigEndian); $value = array(); $value[$j]['val'] = 0; $value[$j]['num'] = $a; $value[$j]['den'] = $b; if (($a != 0) && ($b != 0)) $value[$j]['val'] = $a / $b; } } break; case 11: // FLOAT $value = $rawValue; break; case 12: // DFLOAT $value = $rawValue; break; default: return false; // Unexpected Type } $tagName = ''; if (($mode == 'ifd0') && ($tag == 0x8769)) { // ExifIFDOffset $this->_readIFD($data, $base, $value, $isBigEndian, 'exif'); } elseif (($mode == 'ifd0') && ($tag == 0x8825)) { // GPSIFDOffset $this->_readIFD($data, $base, $value, $isBigEndian, 'gps'); } elseif (($mode == 'ifd1') && ($tag == 0x0111)) { // TIFFStripOffsets $exifTIFFOffset = $value; } elseif (($mode == 'ifd1') && ($tag == 0x0117)) { // TIFFStripByteCounts $exifTIFFLength = $value; } elseif (($mode == 'ifd1') && ($tag == 0x0201)) { // TIFFJFIFOffset $exifThumbnailOffset = $value; } elseif (($mode == 'ifd1') && ($tag == 0x0202)) { // TIFFJFIFLength $exifThumbnailLength = $value; } elseif (($mode == 'exif') && ($tag == 0xA005)) { // InteropIFDOffset $this->_readIFD($data, $base, $value, $isBigEndian, 'interop'); } // elseif (($mode == 'exif') && ($tag == 0x927C)) { // MakerNote // } else { if (isset($EXIFTags[$tag])) { $tagName = $EXIFTags[$tag]; if (isset($this->_info['exif'][$tagName])) { if (!is_array($this->_info['exif'][$tagName])) { $aux = array(); $aux[0] = $this->_info['exif'][$tagName]; $this->_info['exif'][$tagName] = $aux; } $this->_info['exif'][$tagName][count($this->_info['exif'][$tagName])] = $value; } else { $this->_info['exif'][$tagName] = $value; } } /* else { echo sprintf("<h1>Unknown tag %02x (t: %d l: %d) %s in %s</h1>", $tag, $type, $count, $mode, $this->_fileName); // Unknown Tags will be ignored!!! // That's because the tag might be a pointer (like the Exif tag) // and saving it without saving the data it points to might // create an invalid file. } */ } } if (($exifThumbnailOffset > 0) && ($exifThumbnailLength > 0)) { $this->_info['exif']['JFIFThumbnail'] = $this->_getFixedString($data, $base + $exifThumbnailOffset, $exifThumbnailLength); } if (($exifTIFFOffset > 0) && ($exifTIFFLength > 0)) { $this->_info['exif']['TIFFStrips'] = $this->_getFixedString($data, $base + $exifTIFFOffset, $exifTIFFLength); } $nextOffset = $this->_getLong($data, $base + $offset, $isBigEndian); return $nextOffset; } /*************************************************************/ function & _createMarkerExif() { $data = null; $count = count($this->_markers); for ($i = 0; $i < $count; $i++) { if ($this->_markers[$i]['marker'] == 0xE1) { $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 6); if ($signature == "Exif\0\0") { $data =& $this->_markers[$i]['data']; break; } } } if (!isset($this->_info['exif'])) { return false; } $data = "Exif\0\0"; $pos = 6; $offsetBase = 6; if (isset($this->_info['exif']['ByteAlign']) && ($this->_info['exif']['ByteAlign'] == "Big Endian")) { $isBigEndian = true; $aux = "MM"; $pos = $this->_putString($data, $pos, $aux); } else { $isBigEndian = false; $aux = "II"; $pos = $this->_putString($data, $pos, $aux); } $pos = $this->_putShort($data, $pos, 0x002A, $isBigEndian); $pos = $this->_putLong($data, $pos, 0x00000008, $isBigEndian); // IFD0 Offset is always 8 $ifd0 =& $this->_getIFDEntries($isBigEndian, 'ifd0'); $ifd1 =& $this->_getIFDEntries($isBigEndian, 'ifd1'); $pos = $this->_writeIFD($data, $pos, $offsetBase, $ifd0, $isBigEndian, true); $pos = $this->_writeIFD($data, $pos, $offsetBase, $ifd1, $isBigEndian, false); return $data; } /*************************************************************/ /** * @param mixed $data * @param integer $pos * @param integer $offsetBase * @param array $entries * @param boolean $isBigEndian * @param boolean $hasNext * * @return mixed */ function _writeIFD(&$data, $pos, $offsetBase, &$entries, $isBigEndian, $hasNext) { $tiffData = null; $tiffDataOffsetPos = -1; $entryCount = count($entries); $dataPos = $pos + 2 + ($entryCount * 12) + 4; $pos = $this->_putShort($data, $pos, $entryCount, $isBigEndian); for ($i = 0; $i < $entryCount; $i++) { $tag = $entries[$i]['tag']; $type = $entries[$i]['type']; if ($type == -99) { // SubIFD $pos = $this->_putShort($data, $pos, $tag, $isBigEndian); $pos = $this->_putShort($data, $pos, 0x04, $isBigEndian); // LONG $pos = $this->_putLong($data, $pos, 0x01, $isBigEndian); // Count = 1 $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian); $dataPos = $this->_writeIFD($data, $dataPos, $offsetBase, $entries[$i]['value'], $isBigEndian, false); } elseif ($type == -98) { // TIFF Data $pos = $this->_putShort($data, $pos, $tag, $isBigEndian); $pos = $this->_putShort($data, $pos, 0x04, $isBigEndian); // LONG $pos = $this->_putLong($data, $pos, 0x01, $isBigEndian); // Count = 1 $tiffDataOffsetPos = $pos; $pos = $this->_putLong($data, $pos, 0x00, $isBigEndian); // For Now $tiffData =& $entries[$i]['value'] ; } else { // Regular Entry $pos = $this->_putShort($data, $pos, $tag, $isBigEndian); $pos = $this->_putShort($data, $pos, $type, $isBigEndian); $pos = $this->_putLong($data, $pos, $entries[$i]['count'], $isBigEndian); if (strlen($entries[$i]['value']) > 4) { $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian); $dataPos = $this->_putString($data, $dataPos, $entries[$i]['value']); } else { $val = str_pad($entries[$i]['value'], 4, "\0"); $pos = $this->_putString($data, $pos, $val); } } } if ($tiffData != null) { $this->_putLong($data, $tiffDataOffsetPos, $dataPos - $offsetBase, $isBigEndian); $dataPos = $this->_putString($data, $dataPos, $tiffData); } if ($hasNext) { $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian); } else { $pos = $this->_putLong($data, $pos, 0, $isBigEndian); } return $dataPos; } /*************************************************************/ /** * @param boolean $isBigEndian * @param string $mode * * @return array */ function & _getIFDEntries($isBigEndian, $mode) { $EXIFNames = $this->_exifTagNames($mode); $EXIFTags = $this->_exifNameTags($mode); $EXIFTypeInfo = $this->_exifTagTypes($mode); $ifdEntries = array(); $entryCount = 0; foreach($EXIFNames as $tag => $name) { $type = $EXIFTypeInfo[$tag][0]; $count = $EXIFTypeInfo[$tag][1]; $value = null; if (($mode == 'ifd0') && ($tag == 0x8769)) { // ExifIFDOffset if (isset($this->_info['exif']['EXIFVersion'])) { $value =& $this->_getIFDEntries($isBigEndian, "exif"); $type = -99; } else { $value = null; } } elseif (($mode == 'ifd0') && ($tag == 0x8825)) { // GPSIFDOffset if (isset($this->_info['exif']['GPSVersionID'])) { $value =& $this->_getIFDEntries($isBigEndian, "gps"); $type = -99; } else { $value = null; } } elseif (($mode == 'ifd1') && ($tag == 0x0111)) { // TIFFStripOffsets if (isset($this->_info['exif']['TIFFStrips'])) { $value =& $this->_info['exif']['TIFFStrips']; $type = -98; } else { $value = null; } } elseif (($mode == 'ifd1') && ($tag == 0x0117)) { // TIFFStripByteCounts if (isset($this->_info['exif']['TIFFStrips'])) { $value = strlen($this->_info['exif']['TIFFStrips']); } else { $value = null; } } elseif (($mode == 'ifd1') && ($tag == 0x0201)) { // TIFFJFIFOffset if (isset($this->_info['exif']['JFIFThumbnail'])) { $value =& $this->_info['exif']['JFIFThumbnail']; $type = -98; } else { $value = null; } } elseif (($mode == 'ifd1') && ($tag == 0x0202)) { // TIFFJFIFLength if (isset($this->_info['exif']['JFIFThumbnail'])) { $value = strlen($this->_info['exif']['JFIFThumbnail']); } else { $value = null; } } elseif (($mode == 'exif') && ($tag == 0xA005)) { // InteropIFDOffset if (isset($this->_info['exif']['InteroperabilityIndex'])) { $value =& $this->_getIFDEntries($isBigEndian, "interop"); $type = -99; } else { $value = null; } } elseif (isset($this->_info['exif'][$name])) { $origValue =& $this->_info['exif'][$name]; // This makes it easier to process variable size elements if (!is_array($origValue) || isset($origValue['val'])) { unset($origValue); // Break the reference $origValue = array($this->_info['exif'][$name]); } $origCount = count($origValue); if ($origCount == 0 ) { $type = -1; // To ignore this field } $value = " "; switch ($type) { case 1: // UBYTE if ($count == 0) { $count = $origCount; } $j = 0; while (($j < $count) && ($j < $origCount)) { $this->_putByte($value, $j, $origValue[$j]); $j++; } while ($j < $count) { $this->_putByte($value, $j, 0); $j++; } break; case 2: // ASCII $v = strval($origValue[0]); if (($count != 0) && (strlen($v) > $count)) { $v = substr($v, 0, $count); } elseif (($count > 0) && (strlen($v) < $count)) { $v = str_pad($v, $count, "\0"); } $count = strlen($v); $this->_putString($value, 0, $v); break; case 3: // USHORT if ($count == 0) { $count = $origCount; } $j = 0; while (($j < $count) && ($j < $origCount)) { $this->_putShort($value, $j * 2, $origValue[$j], $isBigEndian); $j++; } while ($j < $count) { $this->_putShort($value, $j * 2, 0, $isBigEndian); $j++; } break; case 4: // ULONG if ($count == 0) { $count = $origCount; } $j = 0; while (($j < $count) && ($j < $origCount)) { $this->_putLong($value, $j * 4, $origValue[$j], $isBigEndian); $j++; } while ($j < $count) { $this->_putLong($value, $j * 4, 0, $isBigEndian); $j++; } break; case 5: // URATIONAL if ($count == 0) { $count = $origCount; } $j = 0; while (($j < $count) && ($j < $origCount)) { $v = $origValue[$j]; if (is_array($v)) { $a = $v['num']; $b = $v['den']; } else { $a = 0; $b = 0; // TODO: Allow other types and convert them } $this->_putLong($value, $j * 8, $a, $isBigEndian); $this->_putLong($value, ($j * 8) + 4, $b, $isBigEndian); $j++; } while ($j < $count) { $this->_putLong($value, $j * 8, 0, $isBigEndian); $this->_putLong($value, ($j * 8) + 4, 0, $isBigEndian); $j++; } break; case 6: // SBYTE if ($count == 0) { $count = $origCount; } $j = 0; while (($j < $count) && ($j < $origCount)) { $this->_putByte($value, $j, $origValue[$j]); $j++; } while ($j < $count) { $this->_putByte($value, $j, 0); $j++; } break; case 7: // UNDEFINED $v = strval($origValue[0]); if (($count != 0) && (strlen($v) > $count)) { $v = substr($v, 0, $count); } elseif (($count > 0) && (strlen($v) < $count)) { $v = str_pad($v, $count, "\0"); } $count = strlen($v); $this->_putString($value, 0, $v); break; case 8: // SSHORT if ($count == 0) { $count = $origCount; } $j = 0; while (($j < $count) && ($j < $origCount)) { $this->_putShort($value, $j * 2, $origValue[$j], $isBigEndian); $j++; } while ($j < $count) { $this->_putShort($value, $j * 2, 0, $isBigEndian); $j++; } break; case 9: // SLONG if ($count == 0) { $count = $origCount; } $j = 0; while (($j < $count) && ($j < $origCount)) { $this->_putLong($value, $j * 4, $origValue[$j], $isBigEndian); $j++; } while ($j < $count) { $this->_putLong($value, $j * 4, 0, $isBigEndian); $j++; } break; case 10: // SRATIONAL if ($count == 0) { $count = $origCount; } $j = 0; while (($j < $count) && ($j < $origCount)) { $v = $origValue[$j]; if (is_array($v)) { $a = $v['num']; $b = $v['den']; } else { $a = 0; $b = 0; // TODO: Allow other types and convert them } $this->_putLong($value, $j * 8, $a, $isBigEndian); $this->_putLong($value, ($j * 8) + 4, $b, $isBigEndian); $j++; } while ($j < $count) { $this->_putLong($value, $j * 8, 0, $isBigEndian); $this->_putLong($value, ($j * 8) + 4, 0, $isBigEndian); $j++; } break; case 11: // FLOAT if ($count == 0) { $count = $origCount; } $j = 0; while (($j < $count) && ($j < $origCount)) { $v = strval($origValue[$j]); if (strlen($v) > 4) { $v = substr($v, 0, 4); } elseif (strlen($v) < 4) { $v = str_pad($v, 4, "\0"); } $this->_putString($value, $j * 4, $v); $j++; } while ($j < $count) { $v = "\0\0\0\0"; $this->_putString($value, $j * 4, $v); $j++; } break; case 12: // DFLOAT if ($count == 0) { $count = $origCount; } $j = 0; while (($j < $count) && ($j < $origCount)) { $v = strval($origValue[$j]); if (strlen($v) > 8) { $v = substr($v, 0, 8); } elseif (strlen($v) < 8) { $v = str_pad($v, 8, "\0"); } $this->_putString($value, $j * 8, $v); $j++; } while ($j < $count) { $v = "\0\0\0\0\0\0\0\0"; $this->_putString($value, $j * 8, $v); $j++; } break; default: $value = null; break; } } if ($value != null) { $ifdEntries[$entryCount] = array(); $ifdEntries[$entryCount]['tag'] = $tag; $ifdEntries[$entryCount]['type'] = $type; $ifdEntries[$entryCount]['count'] = $count; $ifdEntries[$entryCount]['value'] = $value; $entryCount++; } } return $ifdEntries; } /*************************************************************/ function _parseMarkerAdobe() { if (!isset($this->_markers)) { $this->_readJPEG(); } if ($this->_markers == null) { return false; } $data = null; $count = count($this->_markers); for ($i = 0; $i < $count; $i++) { if ($this->_markers[$i]['marker'] == 0xED) { $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 14); if ($signature == "Photoshop 3.0\0") { $data =& $this->_markers[$i]['data']; break; } } } if ($data == null) { $this->_info['adobe'] = false; $this->_info['iptc'] = false; return false; } $pos = 14; $this->_info['adobe'] = array(); $this->_info['adobe']['raw'] = array(); $this->_info['iptc'] = array(); $datasize = strlen($data); while ($pos < $datasize) { $signature = $this->_getFixedString($data, $pos, 4); if ($signature != '8BIM') return false; $pos += 4; $type = $this->_getShort($data, $pos); $pos += 2; $strlen = $this->_getByte($data, $pos); $pos += 1; $header = ''; for ($i = 0; $i < $strlen; $i++) { $header .= $data[$pos + $i]; } $pos += $strlen + 1 - ($strlen % 2); // The string is padded to even length, counting the length byte itself $length = $this->_getLong($data, $pos); $pos += 4; $basePos = $pos; switch ($type) { case 0x0404: // Caption (IPTC Data) $pos = $this->_readIPTC($data, $pos); if ($pos == false) return false; break; case 0x040A: // CopyrightFlag $this->_info['adobe']['CopyrightFlag'] = $this->_getByte($data, $pos); $pos += $length; break; case 0x040B: // ImageURL $this->_info['adobe']['ImageURL'] = $this->_getFixedString($data, $pos, $length); $pos += $length; break; case 0x040C: // Thumbnail $aux = $this->_getLong($data, $pos); $pos += 4; if ($aux == 1) { $this->_info['adobe']['ThumbnailWidth'] = $this->_getLong($data, $pos); $pos += 4; $this->_info['adobe']['ThumbnailHeight'] = $this->_getLong($data, $pos); $pos += 4; $pos += 16; // Skip some data $this->_info['adobe']['ThumbnailData'] = $this->_getFixedString($data, $pos, $length - 28); $pos += $length - 28; } break; default: break; } // We save all blocks, even those we recognized $label = sprintf('8BIM_0x%04x', $type); $this->_info['adobe']['raw'][$label] = array(); $this->_info['adobe']['raw'][$label]['type'] = $type; $this->_info['adobe']['raw'][$label]['header'] = $header; $this->_info['adobe']['raw'][$label]['data'] =& $this->_getFixedString($data, $basePos, $length); $pos = $basePos + $length + ($length % 2); // Even padding } } /*************************************************************/ function _readIPTC(&$data, $pos = 0) { $totalLength = strlen($data); $IPTCTags = $this->_iptcTagNames(); while ($pos < ($totalLength - 5)) { $signature = $this->_getShort($data, $pos); if ($signature != 0x1C02) return $pos; $pos += 2; $type = $this->_getByte($data, $pos); $pos += 1; $length = $this->_getShort($data, $pos); $pos += 2; $basePos = $pos; $label = ''; if (isset($IPTCTags[$type])) { $label = $IPTCTags[$type]; } else { $label = sprintf('IPTC_0x%02x', $type); } if ($label != '') { if (isset($this->_info['iptc'][$label])) { if (!is_array($this->_info['iptc'][$label])) { $aux = array(); $aux[0] = $this->_info['iptc'][$label]; $this->_info['iptc'][$label] = $aux; } $this->_info['iptc'][$label][ count($this->_info['iptc'][$label]) ] = $this->_getFixedString($data, $pos, $length); } else { $this->_info['iptc'][$label] = $this->_getFixedString($data, $pos, $length); } } $pos = $basePos + $length; // No padding } return $pos; } /*************************************************************/ function & _createMarkerAdobe() { if (isset($this->_info['iptc'])) { if (!isset($this->_info['adobe'])) { $this->_info['adobe'] = array(); } if (!isset($this->_info['adobe']['raw'])) { $this->_info['adobe']['raw'] = array(); } if (!isset($this->_info['adobe']['raw']['8BIM_0x0404'])) { $this->_info['adobe']['raw']['8BIM_0x0404'] = array(); } $this->_info['adobe']['raw']['8BIM_0x0404']['type'] = 0x0404; $this->_info['adobe']['raw']['8BIM_0x0404']['header'] = "Caption"; $this->_info['adobe']['raw']['8BIM_0x0404']['data'] =& $this->_writeIPTC(); } if (isset($this->_info['adobe']['raw']) && (count($this->_info['adobe']['raw']) > 0)) { $data = "Photoshop 3.0\0"; $pos = 14; reset($this->_info['adobe']['raw']); foreach ($this->_info['adobe']['raw'] as $value){ $pos = $this->_write8BIM( $data, $pos, $value['type'], $value['header'], $value['data'] ); } } return $data; } /*************************************************************/ /** * @param mixed $data * @param integer $pos * * @param string $type * @param string $header * @param mixed $value * * @return int|mixed */ function _write8BIM(&$data, $pos, $type, $header, &$value) { $signature = "8BIM"; $pos = $this->_putString($data, $pos, $signature); $pos = $this->_putShort($data, $pos, $type); $len = strlen($header); $pos = $this->_putByte($data, $pos, $len); $pos = $this->_putString($data, $pos, $header); if (($len % 2) == 0) { // Even padding, including the length byte $pos = $this->_putByte($data, $pos, 0); } $len = strlen($value); $pos = $this->_putLong($data, $pos, $len); $pos = $this->_putString($data, $pos, $value); if (($len % 2) != 0) { // Even padding $pos = $this->_putByte($data, $pos, 0); } return $pos; } /*************************************************************/ function & _writeIPTC() { $data = " "; $pos = 0; $IPTCNames =& $this->_iptcNameTags(); foreach($this->_info['iptc'] as $label => $value) { $value =& $this->_info['iptc'][$label]; $type = -1; if (isset($IPTCNames[$label])) { $type = $IPTCNames[$label]; } elseif (substr($label, 0, 7) == "IPTC_0x") { $type = hexdec(substr($label, 7, 2)); } if ($type != -1) { if (is_array($value)) { $vcnt = count($value); for ($i = 0; $i < $vcnt; $i++) { $pos = $this->_writeIPTCEntry($data, $pos, $type, $value[$i]); } } else { $pos = $this->_writeIPTCEntry($data, $pos, $type, $value); } } } return $data; } /*************************************************************/ /** * @param mixed $data * @param integer $pos * * @param string $type * @param mixed $value * * @return int|mixed */ function _writeIPTCEntry(&$data, $pos, $type, &$value) { $pos = $this->_putShort($data, $pos, 0x1C02); $pos = $this->_putByte($data, $pos, $type); $pos = $this->_putShort($data, $pos, strlen($value)); $pos = $this->_putString($data, $pos, $value); return $pos; } /*************************************************************/ function _exifTagNames($mode) { $tags = array(); if ($mode == 'ifd0') { $tags[0x010E] = 'ImageDescription'; $tags[0x010F] = 'Make'; $tags[0x0110] = 'Model'; $tags[0x0112] = 'Orientation'; $tags[0x011A] = 'XResolution'; $tags[0x011B] = 'YResolution'; $tags[0x0128] = 'ResolutionUnit'; $tags[0x0131] = 'Software'; $tags[0x0132] = 'DateTime'; $tags[0x013B] = 'Artist'; $tags[0x013E] = 'WhitePoint'; $tags[0x013F] = 'PrimaryChromaticities'; $tags[0x0211] = 'YCbCrCoefficients'; $tags[0x0212] = 'YCbCrSubSampling'; $tags[0x0213] = 'YCbCrPositioning'; $tags[0x0214] = 'ReferenceBlackWhite'; $tags[0x8298] = 'Copyright'; $tags[0x8769] = 'ExifIFDOffset'; $tags[0x8825] = 'GPSIFDOffset'; } if ($mode == 'ifd1') { $tags[0x00FE] = 'TIFFNewSubfileType'; $tags[0x00FF] = 'TIFFSubfileType'; $tags[0x0100] = 'TIFFImageWidth'; $tags[0x0101] = 'TIFFImageHeight'; $tags[0x0102] = 'TIFFBitsPerSample'; $tags[0x0103] = 'TIFFCompression'; $tags[0x0106] = 'TIFFPhotometricInterpretation'; $tags[0x0107] = 'TIFFThreshholding'; $tags[0x0108] = 'TIFFCellWidth'; $tags[0x0109] = 'TIFFCellLength'; $tags[0x010A] = 'TIFFFillOrder'; $tags[0x010E] = 'TIFFImageDescription'; $tags[0x010F] = 'TIFFMake'; $tags[0x0110] = 'TIFFModel'; $tags[0x0111] = 'TIFFStripOffsets'; $tags[0x0112] = 'TIFFOrientation'; $tags[0x0115] = 'TIFFSamplesPerPixel'; $tags[0x0116] = 'TIFFRowsPerStrip'; $tags[0x0117] = 'TIFFStripByteCounts'; $tags[0x0118] = 'TIFFMinSampleValue'; $tags[0x0119] = 'TIFFMaxSampleValue'; $tags[0x011A] = 'TIFFXResolution'; $tags[0x011B] = 'TIFFYResolution'; $tags[0x011C] = 'TIFFPlanarConfiguration'; $tags[0x0122] = 'TIFFGrayResponseUnit'; $tags[0x0123] = 'TIFFGrayResponseCurve'; $tags[0x0128] = 'TIFFResolutionUnit'; $tags[0x0131] = 'TIFFSoftware'; $tags[0x0132] = 'TIFFDateTime'; $tags[0x013B] = 'TIFFArtist'; $tags[0x013C] = 'TIFFHostComputer'; $tags[0x0140] = 'TIFFColorMap'; $tags[0x0152] = 'TIFFExtraSamples'; $tags[0x0201] = 'TIFFJFIFOffset'; $tags[0x0202] = 'TIFFJFIFLength'; $tags[0x0211] = 'TIFFYCbCrCoefficients'; $tags[0x0212] = 'TIFFYCbCrSubSampling'; $tags[0x0213] = 'TIFFYCbCrPositioning'; $tags[0x0214] = 'TIFFReferenceBlackWhite'; $tags[0x8298] = 'TIFFCopyright'; $tags[0x9286] = 'TIFFUserComment'; } elseif ($mode == 'exif') { $tags[0x829A] = 'ExposureTime'; $tags[0x829D] = 'FNumber'; $tags[0x8822] = 'ExposureProgram'; $tags[0x8824] = 'SpectralSensitivity'; $tags[0x8827] = 'ISOSpeedRatings'; $tags[0x8828] = 'OECF'; $tags[0x9000] = 'EXIFVersion'; $tags[0x9003] = 'DateTimeOriginal'; $tags[0x9004] = 'DateTimeDigitized'; $tags[0x9101] = 'ComponentsConfiguration'; $tags[0x9102] = 'CompressedBitsPerPixel'; $tags[0x9201] = 'ShutterSpeedValue'; $tags[0x9202] = 'ApertureValue'; $tags[0x9203] = 'BrightnessValue'; $tags[0x9204] = 'ExposureBiasValue'; $tags[0x9205] = 'MaxApertureValue'; $tags[0x9206] = 'SubjectDistance'; $tags[0x9207] = 'MeteringMode'; $tags[0x9208] = 'LightSource'; $tags[0x9209] = 'Flash'; $tags[0x920A] = 'FocalLength'; $tags[0x927C] = 'MakerNote'; $tags[0x9286] = 'UserComment'; $tags[0x9290] = 'SubSecTime'; $tags[0x9291] = 'SubSecTimeOriginal'; $tags[0x9292] = 'SubSecTimeDigitized'; $tags[0xA000] = 'FlashPixVersion'; $tags[0xA001] = 'ColorSpace'; $tags[0xA002] = 'PixelXDimension'; $tags[0xA003] = 'PixelYDimension'; $tags[0xA004] = 'RelatedSoundFile'; $tags[0xA005] = 'InteropIFDOffset'; $tags[0xA20B] = 'FlashEnergy'; $tags[0xA20C] = 'SpatialFrequencyResponse'; $tags[0xA20E] = 'FocalPlaneXResolution'; $tags[0xA20F] = 'FocalPlaneYResolution'; $tags[0xA210] = 'FocalPlaneResolutionUnit'; $tags[0xA214] = 'SubjectLocation'; $tags[0xA215] = 'ExposureIndex'; $tags[0xA217] = 'SensingMethod'; $tags[0xA300] = 'FileSource'; $tags[0xA301] = 'SceneType'; $tags[0xA302] = 'CFAPattern'; } elseif ($mode == 'interop') { $tags[0x0001] = 'InteroperabilityIndex'; $tags[0x0002] = 'InteroperabilityVersion'; $tags[0x1000] = 'RelatedImageFileFormat'; $tags[0x1001] = 'RelatedImageWidth'; $tags[0x1002] = 'RelatedImageLength'; } elseif ($mode == 'gps') { $tags[0x0000] = 'GPSVersionID'; $tags[0x0001] = 'GPSLatitudeRef'; $tags[0x0002] = 'GPSLatitude'; $tags[0x0003] = 'GPSLongitudeRef'; $tags[0x0004] = 'GPSLongitude'; $tags[0x0005] = 'GPSAltitudeRef'; $tags[0x0006] = 'GPSAltitude'; $tags[0x0007] = 'GPSTimeStamp'; $tags[0x0008] = 'GPSSatellites'; $tags[0x0009] = 'GPSStatus'; $tags[0x000A] = 'GPSMeasureMode'; $tags[0x000B] = 'GPSDOP'; $tags[0x000C] = 'GPSSpeedRef'; $tags[0x000D] = 'GPSSpeed'; $tags[0x000E] = 'GPSTrackRef'; $tags[0x000F] = 'GPSTrack'; $tags[0x0010] = 'GPSImgDirectionRef'; $tags[0x0011] = 'GPSImgDirection'; $tags[0x0012] = 'GPSMapDatum'; $tags[0x0013] = 'GPSDestLatitudeRef'; $tags[0x0014] = 'GPSDestLatitude'; $tags[0x0015] = 'GPSDestLongitudeRef'; $tags[0x0016] = 'GPSDestLongitude'; $tags[0x0017] = 'GPSDestBearingRef'; $tags[0x0018] = 'GPSDestBearing'; $tags[0x0019] = 'GPSDestDistanceRef'; $tags[0x001A] = 'GPSDestDistance'; } return $tags; } /*************************************************************/ function _exifTagTypes($mode) { $tags = array(); if ($mode == 'ifd0') { $tags[0x010E] = array(2, 0); // ImageDescription -> ASCII, Any $tags[0x010F] = array(2, 0); // Make -> ASCII, Any $tags[0x0110] = array(2, 0); // Model -> ASCII, Any $tags[0x0112] = array(3, 1); // Orientation -> SHORT, 1 $tags[0x011A] = array(5, 1); // XResolution -> RATIONAL, 1 $tags[0x011B] = array(5, 1); // YResolution -> RATIONAL, 1 $tags[0x0128] = array(3, 1); // ResolutionUnit -> SHORT $tags[0x0131] = array(2, 0); // Software -> ASCII, Any $tags[0x0132] = array(2, 20); // DateTime -> ASCII, 20 $tags[0x013B] = array(2, 0); // Artist -> ASCII, Any $tags[0x013E] = array(5, 2); // WhitePoint -> RATIONAL, 2 $tags[0x013F] = array(5, 6); // PrimaryChromaticities -> RATIONAL, 6 $tags[0x0211] = array(5, 3); // YCbCrCoefficients -> RATIONAL, 3 $tags[0x0212] = array(3, 2); // YCbCrSubSampling -> SHORT, 2 $tags[0x0213] = array(3, 1); // YCbCrPositioning -> SHORT, 1 $tags[0x0214] = array(5, 6); // ReferenceBlackWhite -> RATIONAL, 6 $tags[0x8298] = array(2, 0); // Copyright -> ASCII, Any $tags[0x8769] = array(4, 1); // ExifIFDOffset -> LONG, 1 $tags[0x8825] = array(4, 1); // GPSIFDOffset -> LONG, 1 } if ($mode == 'ifd1') { $tags[0x00FE] = array(4, 1); // TIFFNewSubfileType -> LONG, 1 $tags[0x00FF] = array(3, 1); // TIFFSubfileType -> SHORT, 1 $tags[0x0100] = array(4, 1); // TIFFImageWidth -> LONG (or SHORT), 1 $tags[0x0101] = array(4, 1); // TIFFImageHeight -> LONG (or SHORT), 1 $tags[0x0102] = array(3, 3); // TIFFBitsPerSample -> SHORT, 3 $tags[0x0103] = array(3, 1); // TIFFCompression -> SHORT, 1 $tags[0x0106] = array(3, 1); // TIFFPhotometricInterpretation -> SHORT, 1 $tags[0x0107] = array(3, 1); // TIFFThreshholding -> SHORT, 1 $tags[0x0108] = array(3, 1); // TIFFCellWidth -> SHORT, 1 $tags[0x0109] = array(3, 1); // TIFFCellLength -> SHORT, 1 $tags[0x010A] = array(3, 1); // TIFFFillOrder -> SHORT, 1 $tags[0x010E] = array(2, 0); // TIFFImageDescription -> ASCII, Any $tags[0x010F] = array(2, 0); // TIFFMake -> ASCII, Any $tags[0x0110] = array(2, 0); // TIFFModel -> ASCII, Any $tags[0x0111] = array(4, 0); // TIFFStripOffsets -> LONG (or SHORT), Any (one per strip) $tags[0x0112] = array(3, 1); // TIFFOrientation -> SHORT, 1 $tags[0x0115] = array(3, 1); // TIFFSamplesPerPixel -> SHORT, 1 $tags[0x0116] = array(4, 1); // TIFFRowsPerStrip -> LONG (or SHORT), 1 $tags[0x0117] = array(4, 0); // TIFFStripByteCounts -> LONG (or SHORT), Any (one per strip) $tags[0x0118] = array(3, 0); // TIFFMinSampleValue -> SHORT, Any (SamplesPerPixel) $tags[0x0119] = array(3, 0); // TIFFMaxSampleValue -> SHORT, Any (SamplesPerPixel) $tags[0x011A] = array(5, 1); // TIFFXResolution -> RATIONAL, 1 $tags[0x011B] = array(5, 1); // TIFFYResolution -> RATIONAL, 1 $tags[0x011C] = array(3, 1); // TIFFPlanarConfiguration -> SHORT, 1 $tags[0x0122] = array(3, 1); // TIFFGrayResponseUnit -> SHORT, 1 $tags[0x0123] = array(3, 0); // TIFFGrayResponseCurve -> SHORT, Any (2^BitsPerSample) $tags[0x0128] = array(3, 1); // TIFFResolutionUnit -> SHORT, 1 $tags[0x0131] = array(2, 0); // TIFFSoftware -> ASCII, Any $tags[0x0132] = array(2, 20); // TIFFDateTime -> ASCII, 20 $tags[0x013B] = array(2, 0); // TIFFArtist -> ASCII, Any $tags[0x013C] = array(2, 0); // TIFFHostComputer -> ASCII, Any $tags[0x0140] = array(3, 0); // TIFFColorMap -> SHORT, Any (3 * 2^BitsPerSample) $tags[0x0152] = array(3, 0); // TIFFExtraSamples -> SHORT, Any (SamplesPerPixel - 3) $tags[0x0201] = array(4, 1); // TIFFJFIFOffset -> LONG, 1 $tags[0x0202] = array(4, 1); // TIFFJFIFLength -> LONG, 1 $tags[0x0211] = array(5, 3); // TIFFYCbCrCoefficients -> RATIONAL, 3 $tags[0x0212] = array(3, 2); // TIFFYCbCrSubSampling -> SHORT, 2 $tags[0x0213] = array(3, 1); // TIFFYCbCrPositioning -> SHORT, 1 $tags[0x0214] = array(5, 6); // TIFFReferenceBlackWhite -> RATIONAL, 6 $tags[0x8298] = array(2, 0); // TIFFCopyright -> ASCII, Any $tags[0x9286] = array(2, 0); // TIFFUserComment -> ASCII, Any } elseif ($mode == 'exif') { $tags[0x829A] = array(5, 1); // ExposureTime -> RATIONAL, 1 $tags[0x829D] = array(5, 1); // FNumber -> RATIONAL, 1 $tags[0x8822] = array(3, 1); // ExposureProgram -> SHORT, 1 $tags[0x8824] = array(2, 0); // SpectralSensitivity -> ASCII, Any $tags[0x8827] = array(3, 0); // ISOSpeedRatings -> SHORT, Any $tags[0x8828] = array(7, 0); // OECF -> UNDEFINED, Any $tags[0x9000] = array(7, 4); // EXIFVersion -> UNDEFINED, 4 $tags[0x9003] = array(2, 20); // DateTimeOriginal -> ASCII, 20 $tags[0x9004] = array(2, 20); // DateTimeDigitized -> ASCII, 20 $tags[0x9101] = array(7, 4); // ComponentsConfiguration -> UNDEFINED, 4 $tags[0x9102] = array(5, 1); // CompressedBitsPerPixel -> RATIONAL, 1 $tags[0x9201] = array(10, 1); // ShutterSpeedValue -> SRATIONAL, 1 $tags[0x9202] = array(5, 1); // ApertureValue -> RATIONAL, 1 $tags[0x9203] = array(10, 1); // BrightnessValue -> SRATIONAL, 1 $tags[0x9204] = array(10, 1); // ExposureBiasValue -> SRATIONAL, 1 $tags[0x9205] = array(5, 1); // MaxApertureValue -> RATIONAL, 1 $tags[0x9206] = array(5, 1); // SubjectDistance -> RATIONAL, 1 $tags[0x9207] = array(3, 1); // MeteringMode -> SHORT, 1 $tags[0x9208] = array(3, 1); // LightSource -> SHORT, 1 $tags[0x9209] = array(3, 1); // Flash -> SHORT, 1 $tags[0x920A] = array(5, 1); // FocalLength -> RATIONAL, 1 $tags[0x927C] = array(7, 0); // MakerNote -> UNDEFINED, Any $tags[0x9286] = array(7, 0); // UserComment -> UNDEFINED, Any $tags[0x9290] = array(2, 0); // SubSecTime -> ASCII, Any $tags[0x9291] = array(2, 0); // SubSecTimeOriginal -> ASCII, Any $tags[0x9292] = array(2, 0); // SubSecTimeDigitized -> ASCII, Any $tags[0xA000] = array(7, 4); // FlashPixVersion -> UNDEFINED, 4 $tags[0xA001] = array(3, 1); // ColorSpace -> SHORT, 1 $tags[0xA002] = array(4, 1); // PixelXDimension -> LONG (or SHORT), 1 $tags[0xA003] = array(4, 1); // PixelYDimension -> LONG (or SHORT), 1 $tags[0xA004] = array(2, 13); // RelatedSoundFile -> ASCII, 13 $tags[0xA005] = array(4, 1); // InteropIFDOffset -> LONG, 1 $tags[0xA20B] = array(5, 1); // FlashEnergy -> RATIONAL, 1 $tags[0xA20C] = array(7, 0); // SpatialFrequencyResponse -> UNDEFINED, Any $tags[0xA20E] = array(5, 1); // FocalPlaneXResolution -> RATIONAL, 1 $tags[0xA20F] = array(5, 1); // FocalPlaneYResolution -> RATIONAL, 1 $tags[0xA210] = array(3, 1); // FocalPlaneResolutionUnit -> SHORT, 1 $tags[0xA214] = array(3, 2); // SubjectLocation -> SHORT, 2 $tags[0xA215] = array(5, 1); // ExposureIndex -> RATIONAL, 1 $tags[0xA217] = array(3, 1); // SensingMethod -> SHORT, 1 $tags[0xA300] = array(7, 1); // FileSource -> UNDEFINED, 1 $tags[0xA301] = array(7, 1); // SceneType -> UNDEFINED, 1 $tags[0xA302] = array(7, 0); // CFAPattern -> UNDEFINED, Any } elseif ($mode == 'interop') { $tags[0x0001] = array(2, 0); // InteroperabilityIndex -> ASCII, Any $tags[0x0002] = array(7, 4); // InteroperabilityVersion -> UNKNOWN, 4 $tags[0x1000] = array(2, 0); // RelatedImageFileFormat -> ASCII, Any $tags[0x1001] = array(4, 1); // RelatedImageWidth -> LONG (or SHORT), 1 $tags[0x1002] = array(4, 1); // RelatedImageLength -> LONG (or SHORT), 1 } elseif ($mode == 'gps') { $tags[0x0000] = array(1, 4); // GPSVersionID -> BYTE, 4 $tags[0x0001] = array(2, 2); // GPSLatitudeRef -> ASCII, 2 $tags[0x0002] = array(5, 3); // GPSLatitude -> RATIONAL, 3 $tags[0x0003] = array(2, 2); // GPSLongitudeRef -> ASCII, 2 $tags[0x0004] = array(5, 3); // GPSLongitude -> RATIONAL, 3 $tags[0x0005] = array(2, 2); // GPSAltitudeRef -> ASCII, 2 $tags[0x0006] = array(5, 1); // GPSAltitude -> RATIONAL, 1 $tags[0x0007] = array(5, 3); // GPSTimeStamp -> RATIONAL, 3 $tags[0x0008] = array(2, 0); // GPSSatellites -> ASCII, Any $tags[0x0009] = array(2, 2); // GPSStatus -> ASCII, 2 $tags[0x000A] = array(2, 2); // GPSMeasureMode -> ASCII, 2 $tags[0x000B] = array(5, 1); // GPSDOP -> RATIONAL, 1 $tags[0x000C] = array(2, 2); // GPSSpeedRef -> ASCII, 2 $tags[0x000D] = array(5, 1); // GPSSpeed -> RATIONAL, 1 $tags[0x000E] = array(2, 2); // GPSTrackRef -> ASCII, 2 $tags[0x000F] = array(5, 1); // GPSTrack -> RATIONAL, 1 $tags[0x0010] = array(2, 2); // GPSImgDirectionRef -> ASCII, 2 $tags[0x0011] = array(5, 1); // GPSImgDirection -> RATIONAL, 1 $tags[0x0012] = array(2, 0); // GPSMapDatum -> ASCII, Any $tags[0x0013] = array(2, 2); // GPSDestLatitudeRef -> ASCII, 2 $tags[0x0014] = array(5, 3); // GPSDestLatitude -> RATIONAL, 3 $tags[0x0015] = array(2, 2); // GPSDestLongitudeRef -> ASCII, 2 $tags[0x0016] = array(5, 3); // GPSDestLongitude -> RATIONAL, 3 $tags[0x0017] = array(2, 2); // GPSDestBearingRef -> ASCII, 2 $tags[0x0018] = array(5, 1); // GPSDestBearing -> RATIONAL, 1 $tags[0x0019] = array(2, 2); // GPSDestDistanceRef -> ASCII, 2 $tags[0x001A] = array(5, 1); // GPSDestDistance -> RATIONAL, 1 } return $tags; } /*************************************************************/ function _exifNameTags($mode) { $tags = $this->_exifTagNames($mode); return $this->_names2Tags($tags); } /*************************************************************/ function _iptcTagNames() { $tags = array(); $tags[0x14] = 'SuplementalCategories'; $tags[0x19] = 'Keywords'; $tags[0x78] = 'Caption'; $tags[0x7A] = 'CaptionWriter'; $tags[0x69] = 'Headline'; $tags[0x28] = 'SpecialInstructions'; $tags[0x0F] = 'Category'; $tags[0x50] = 'Byline'; $tags[0x55] = 'BylineTitle'; $tags[0x6E] = 'Credit'; $tags[0x73] = 'Source'; $tags[0x74] = 'CopyrightNotice'; $tags[0x05] = 'ObjectName'; $tags[0x5A] = 'City'; $tags[0x5C] = 'Sublocation'; $tags[0x5F] = 'ProvinceState'; $tags[0x65] = 'CountryName'; $tags[0x67] = 'OriginalTransmissionReference'; $tags[0x37] = 'DateCreated'; $tags[0x0A] = 'CopyrightFlag'; return $tags; } /*************************************************************/ function & _iptcNameTags() { $tags = $this->_iptcTagNames(); return $this->_names2Tags($tags); } /*************************************************************/ function _names2Tags($tags2Names) { $names2Tags = array(); foreach($tags2Names as $tag => $name) { $names2Tags[$name] = $tag; } return $names2Tags; } /*************************************************************/ /** * @param $data * @param integer $pos * * @return int */ function _getByte(&$data, $pos) { return ord($data[$pos]); } /*************************************************************/ /** * @param mixed $data * @param integer $pos * * @param mixed $val * * @return int */ function _putByte(&$data, $pos, $val) { $val = intval($val); $data[$pos] = chr($val); return $pos + 1; } /*************************************************************/ function _getShort(&$data, $pos, $bigEndian = true) { if ($bigEndian) { return (ord($data[$pos]) << 8) + ord($data[$pos + 1]); } else { return ord($data[$pos]) + (ord($data[$pos + 1]) << 8); } } /*************************************************************/ function _putShort(&$data, $pos = 0, $val = 0, $bigEndian = true) { $val = intval($val); if ($bigEndian) { $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); } return $pos + 2; } /*************************************************************/ /** * @param mixed $data * @param integer $pos * * @param bool $bigEndian * * @return int */ 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]); } else { return ord($data[$pos]) + (ord($data[$pos + 1]) << 8) + (ord($data[$pos + 2]) << 16) + (ord($data[$pos + 3]) << 24); } } /*************************************************************/ /** * @param mixed $data * @param integer $pos * * @param mixed $val * @param bool $bigEndian * * @return int */ function _putLong(&$data, $pos, $val, $bigEndian = true) { $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); } 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); } return $pos + 4; } /*************************************************************/ function & _getNullString(&$data, $pos) { $str = ''; $max = strlen($data); while ($pos < $max) { if (ord($data[$pos]) == 0) { return $str; } else { $str .= $data[$pos]; } $pos++; } return $str; } /*************************************************************/ function & _getFixedString(&$data, $pos, $length = -1) { if ($length == -1) { $length = strlen($data) - $pos; } $rv = substr($data, $pos, $length); return $rv; } /*************************************************************/ function _putString(&$data, $pos, &$str) { $len = strlen($str); for ($i = 0; $i < $len; $i++) { $data[$pos + $i] = $str[$i]; } return $pos + $len; } /*************************************************************/ function _hexDump(&$data, $start = 0, $length = -1) { if (($length == -1) || (($length + $start) > strlen($data))) { $end = strlen($data); } else { $end = $start + $length; } $ascii = ''; $count = 0; echo "<tt>\n"; while ($start < $end) { if (($count % 16) == 0) { echo sprintf('%04d', $count) . ': '; } $c = ord($data[$start]); $count++; $start++; $aux = dechex($c); if (strlen($aux) == 1) echo '0'; echo $aux . ' '; if ($c == 60) $ascii .= '<'; elseif ($c == 62) $ascii .= '>'; elseif ($c == 32) $ascii .= ' '; elseif ($c > 32) $ascii .= chr($c); else $ascii .= '.'; if (($count % 4) == 0) { echo ' - '; } if (($count % 16) == 0) { echo ': ' . $ascii . "<br>\n"; $ascii = ''; } } if ($ascii != '') { while (($count % 16) != 0) { echo '-- '; $count++; if (($count % 4) == 0) { echo ' - '; } } echo ': ' . $ascii . "<br>\n"; } echo "</tt>\n"; } /*****************************************************************/ } /* vim: set expandtab tabstop=4 shiftwidth=4: */ lang/lt/index.txt 0000644 00000000224 15233462216 0007756 0 ustar 00 ====== Indeksas ====== Čia matote visų šiuo metu egzistuojančių puslapių sąrašą. Jie išrūšiuoti pagal [[doku>namespaces|pavadinimą]]. lang/lt/register.txt 0000644 00000000553 15233462216 0010500 0 ustar 00 ====== Naujo vartotojo registracija ====== Norėdami tapti nauju registruotu šio tinklalapio vartotoju, užpildykite žemiau esančią formą. Būtinai turite nurodyti **veikiantį el. pašto adresą**, nes jūsų slaptažodis bus išsiųstas pastaruoju adresu. Prisijungimo vardas turėtų būti sukurtas pagal [[doku>pagename|puslapio pavadinimo]] taisykles. lang/lt/denied.txt 0000644 00000000140 15233462216 0010074 0 ustar 00 ====== Priėjimas uždraustas ====== Jūs neturite reikiamų teisių, kad galėtumėte tęsti. lang/lt/revisions.txt 0000644 00000000327 15233462216 0010674 0 ustar 00 ====== Senos versijos ====== Čia matote senas šio dokumento versijas. Jei norite atstatyti dokumentą į jo senesniąją versiją, paspauskite "Redaguoti šį puslapį" prie norimos versijos ir išsaugokite ją. lang/lt/norev.txt 0000644 00000000231 15233462216 0007776 0 ustar 00 ====== Tokios versijos nėra ====== Nurodyta versija neegzistuoja. Norėdami pamatyti visas dokumento versijas, paspauskite ''Senos versijos'' mygtuką lang/lt/admin.txt 0000644 00000000151 15233462216 0007736 0 ustar 00 ====== Administracija ====== Žemiau matote veiksmų, kuriuos gali atlikti administratorius, sąrašą. lang/lt/locked.txt 0000644 00000000370 15233462216 0010112 0 ustar 00 ====== Puslapis užrakintas ====== Šis puslapis yra apsaugotas (užrakintas) nuo kitų vartotojų pakeitimų. Norėdami redaguoti puslapį, turėsite palaukti, kol kitas vartotojas baigs tai daryti arba „užrakto“ galiojimo laikas pasibaigs. lang/lt/recent.txt 0000644 00000000110 15233462216 0010121 0 ustar 00 ====== Naujausi keitimai ====== Šie puslapiai buvo neseniai pakeisti: lang/lt/draft.txt 0000644 00000000720 15233462216 0007750 0 ustar 00 ====== Rastas juodraščio failas ====== Jūsų paskutinė redagavimo sesija šiame puslapyje nebuvo tinkamai baigta. DokuWiki automatiškai išsaugojo juodraštį, kurį galite naudoti norėdami tęsti redagavimą. Žemiau galite pamatyti duomenis, kurie buvo išsaugoti iš jūsų paskutinės sesijos. Nuspręskite, ar norite //atkurti// prarastą redagavimo sesiją, //ištrinti// automatiškai išsaugotą juodraštį ar //atšaukti// redagavimo procesą. lang/lt/password.txt 0000644 00000000245 15233462216 0010514 0 ustar 00 Labas, @FULLNAME@! Čia yra jūsų prisijungimo duomenys prie tinklalapio @TITLE@ (@DOKUWIKIURL@): Prisijungimo vardas: @LOGIN@ Slaptažodis: @PASSWORD@ lang/lt/adminplugins.txt 0000644 00000000040 15233462216 0011335 0 ustar 00 ===== Papildomi įskiepiai ===== lang/lt/searchpage.txt 0000644 00000000136 15233462216 0010753 0 ustar 00 ====== Paieška ====== Žemiau matote Jūsų atliktos paieškos rezultatus. @CREATEPAGEINFO@ lang/lt/showrev.txt 0000644 00000000052 15233462216 0010343 0 ustar 00 **Čia yra sena dokumento versija!** ---- lang/lt/jquery.ui.datepicker.js 0000644 00000002337 15233462216 0012520 0 ustar 00 /* Lithuanian (UTF-8) initialisation for the jQuery UI date picker plugin. */ /* @author Arturas Paleicikas <arturas@avalon.lt> */ ( function( factory ) { if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define( [ "../widgets/datepicker" ], factory ); } else { // Browser globals factory( jQuery.datepicker ); } }( function( datepicker ) { datepicker.regional.lt = { closeText: "Uždaryti", prevText: "<Atgal", nextText: "Pirmyn>", currentText: "Šiandien", monthNames: [ "Sausis","Vasaris","Kovas","Balandis","Gegužė","Birželis", "Liepa","Rugpjūtis","Rugsėjis","Spalis","Lapkritis","Gruodis" ], monthNamesShort: [ "Sau","Vas","Kov","Bal","Geg","Bir", "Lie","Rugp","Rugs","Spa","Lap","Gru" ], dayNames: [ "sekmadienis", "pirmadienis", "antradienis", "trečiadienis", "ketvirtadienis", "penktadienis", "šeštadienis" ], dayNamesShort: [ "sek","pir","ant","tre","ket","pen","šeš" ], dayNamesMin: [ "Se","Pr","An","Tr","Ke","Pe","Še" ], weekHeader: "SAV", dateFormat: "yy-mm-dd", firstDay: 1, isRTL: false, showMonthAfterYear: true, yearSuffix: "" }; datepicker.setDefaults( datepicker.regional.lt ); return datepicker.regional.lt; } ) ); lang/lt/conflict.txt 0000644 00000000633 15233462216 0010454 0 ustar 00 ====== Egzistuoja naujesnė versija ====== Rasta naujesnė dokumento, kurį redagavote, versija. Tai atsitinka tada, kai kitas vartotojas modifikuoja dokumentą tuo metu, kai jūs jį redaguojate. Atidžiai peržvelkite žemiau esančius skirtumus ir nuspręskite, kurią versiją išsaugoti. Paspausdami ''Išsaugoti'' išsaugosite saviškę versiją. Paspausdami ''Atšaukti'' išsaugosite esamą versiją. lang/lt/lang.php 0000644 00000027243 15233462216 0007552 0 ustar 00 <?php /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * * @author Michael Harrison <michharri120@gmail.com> * @author Linas Valiukas <shirshegsm@gmail.com> * @author Edmondas Girkantas <eg@zemaitija.net> * @author Arūnas Vaitekūnas <aras@fan.lt> * @author audrius.klevas <audrius.klevas@gmail.com> * @author Tomas Darius Davainis <tomasdd@gmail.com> */ $lang['encoding'] = 'utf-8'; $lang['direction'] = 'ltr'; $lang['doublequoteopening'] = '„'; $lang['doublequoteclosing'] = '“'; $lang['singlequoteopening'] = '‚'; $lang['singlequoteclosing'] = '‘'; $lang['apostrophe'] = '’'; $lang['btn_edit'] = 'Redaguoti šį puslapį'; $lang['btn_source'] = 'Parodyti puslapio kodą'; $lang['btn_show'] = 'Parodyti puslapį'; $lang['btn_create'] = 'Sukurti šį puslapį'; $lang['btn_search'] = 'Paieška'; $lang['btn_save'] = 'Išsaugoti'; $lang['btn_preview'] = 'Peržiūra'; $lang['btn_top'] = 'Į viršų'; $lang['btn_newer'] = '<< naujesnė'; $lang['btn_older'] = 'senesnė >>'; $lang['btn_revs'] = 'Senos versijos'; $lang['btn_recent'] = 'Naujausi keitimai'; $lang['btn_upload'] = 'Atsiųsti bylą'; $lang['btn_cancel'] = 'Atšaukti'; $lang['btn_index'] = 'Indeksas'; $lang['btn_secedit'] = 'Redaguoti'; $lang['btn_login'] = 'Prisijungti'; $lang['btn_logout'] = 'Atsijungti'; $lang['btn_admin'] = 'Administracija'; $lang['btn_update'] = 'Atnaujinti'; $lang['btn_delete'] = 'Ištrinti'; $lang['btn_back'] = 'Atgal'; $lang['btn_backlink'] = 'Atgalinės nuorodos'; $lang['btn_subscribe'] = 'Užsisakyti keitimų prenumeratą'; $lang['btn_profile'] = 'Atnaujinti profilį'; $lang['btn_reset'] = 'Atstata'; $lang['btn_resendpwd'] = 'Nustatykite naują slaptažodį'; $lang['btn_draft'] = 'Redaguoti juodraštį'; $lang['btn_recover'] = 'Atkurti juodraštį'; $lang['btn_draftdel'] = 'Šalinti juodraštį'; $lang['btn_revert'] = 'Atkurti'; $lang['btn_register'] = 'Registruotis'; $lang['btn_apply'] = 'Taikyti'; $lang['btn_media'] = 'Žiniasklaidos vadybininkas'; $lang['btn_deleteuser'] = 'Pašalinti mano paskyrą'; $lang['btn_img_backto'] = 'Atgal į %s'; $lang['loggedinas'] = 'Prisijungęs kaip:'; $lang['user'] = 'Vartotojo vardas'; $lang['pass'] = 'Slaptažodis'; $lang['newpass'] = 'Naujas slaptažodis'; $lang['oldpass'] = 'Patvirtinti esamą slaptažodį'; $lang['passchk'] = 'dar kartą'; $lang['remember'] = 'Prisiminti mane'; $lang['fullname'] = 'Visas vardas'; $lang['email'] = 'El. pašto adresas'; $lang['profile'] = 'Vartotojo profilis'; $lang['badlogin'] = 'Nurodėte blogą vartotojo vardą arba slaptažodį.'; $lang['minoredit'] = 'Nedidelis pataisymas'; $lang['draftdate'] = 'Juodraštis automatiškai išsaugotas'; $lang['nosecedit'] = 'Puslapis buvo kažkieno pataisytas, teksto dalies informacija tapo pasenusi, todėl pakrautas visas puslapis.'; $lang['js']['willexpire'] = 'Šio puslapio redagavimo užrakto galiojimo laikas baigsis po minutės.\nNorėdami išvengti nesklandumų naudokite peržiūros mygtuką ir užraktas atsinaujins.'; $lang['js']['notsavedyet'] = 'Pakeitimai nebus išsaugoti.\nTikrai tęsti?'; $lang['js']['keepopen'] = 'Pažymėjus palikti langą atvertą'; $lang['js']['hidedetails'] = 'Paslėpti Detales'; $lang['js']['nosmblinks'] = 'Nurodos į "Windows shares" veikia tik su Microsoft Internet Explorer naršykle. Vis dėlto, jūs galite nukopijuoti šią nuorodą.'; $lang['js']['del_confirm'] = 'Ar tikrai ištrinti pažymėtą(us) įrašą(us)?'; $lang['regmissing'] = 'Turite užpildyti visus laukus.'; $lang['reguexists'] = 'Vartotojas su pasirinktu prisijungimo vardu jau egzistuoja.'; $lang['regsuccess'] = 'Vartotojas sukurtas, slaptažodis išsiųstas el. paštu.'; $lang['regsuccess2'] = 'Vartotojas sukurtas.'; $lang['regmailfail'] = 'Siunčiant slaptažodį el. paštu įvyko klaida - susisiekite su administracija!'; $lang['regbadmail'] = 'Nurodytas el. pašto adresas yra neteisingas - jei manote, kad tai klaida, susisiekite su administracija'; $lang['regbadpass'] = 'Įvesti slaptažodžiai nesutampa, bandykite dar kartą.'; $lang['regpwmail'] = 'Jūsų DokuWiki slaptažodis'; $lang['reghere'] = 'Dar neužsiregistravote? Padarykite tai dabar'; $lang['profna'] = 'Ši vikisvetainė neleidžia pakeisti profilio'; $lang['profnochange'] = 'Nėra pakeitimų, todėl nėra ką atlikti.'; $lang['profnoempty'] = 'Tuščias vardo arba el. pašto adreso laukas nėra leidžiamas.'; $lang['profchanged'] = 'Vartotojo profilis sėkmingai atnaujintas.'; $lang['pwdforget'] = 'Pamiršote slaptažodį? Gaukite naują'; $lang['resendna'] = 'Ši vikisvetainė neleidžia persiųsti slaptažodžių.'; $lang['resendpwdmissing'] = 'Jūs turite užpildyti visus laukus.'; $lang['resendpwdnouser'] = 'Tokio vartotojo nėra duomenų bazėje.'; $lang['resendpwdbadauth'] = 'Atsiprašome, bet šis tapatybės nustatymo kodas netinkamas. Įsitikinkite, kad panaudojote pilną patvirtinimo nuorodą.'; $lang['resendpwdconfirm'] = 'Patvirtinimo nuoroda išsiųsta el. paštu.'; $lang['resendpwdsuccess'] = 'Jūsų naujas slaptažodis buvo išsiųstas el. paštu.'; $lang['license'] = 'Jei nenurodyta kitaip, šio wiki turinys ginamas tokia licencija:'; $lang['licenseok'] = 'Pastaba: Redaguodami šį puslapį jūs sutinkate jog jūsų turinys atitinka licencijavima pagal šią licenciją'; $lang['txt_upload'] = 'Išsirinkite atsiunčiamą bylą:'; $lang['txt_filename'] = 'Įveskite wikivardą (nebūtina):'; $lang['txt_overwrt'] = 'Perrašyti egzistuojančią bylą'; $lang['lockedby'] = 'Užrakintas vartotojo:'; $lang['lockexpire'] = 'Užraktas bus nuimtas:'; $lang['rssfailed'] = 'Siunčiant šį feed\'ą įvyko klaida: '; $lang['nothingfound'] = 'Paieškos rezultatų nėra.'; $lang['mediaselect'] = 'Mediabylos išsirinkimas'; $lang['uploadsucc'] = 'Atsiuntimas pavyko'; $lang['uploadfail'] = 'Atsiuntimas nepavyko. Blogi priėjimo leidimai??'; $lang['uploadwrong'] = 'Atsiuntimas atmestas. Bylos tipas neleistinas'; $lang['uploadexist'] = 'Tokia byla jau egzistuoja. Veiksmai atšaukti.'; $lang['uploadbadcontent'] = 'Įkeltas turinys neatitinka %s failo išplėtimo.'; $lang['uploadspam'] = 'Įkėlimas blokuotas pagal šiukšlintojų juodajį šąrašą.'; $lang['uploadxss'] = 'Įkėlimas blokuotas greičiausiai dėl netinkamo teksto.'; $lang['uploadsize'] = 'Įkeltas failas per didelis (maks. %s)'; $lang['deletesucc'] = 'Byla "%s" ištrinta.'; $lang['deletefail'] = 'Byla "%s" negali būti ištrinta - patikrinkite leidimus.'; $lang['mediainuse'] = 'Byla "%s" nebuvo ištrinta - ji vis dar naudojama.'; $lang['namespaces'] = 'Pavadinimai'; $lang['mediafiles'] = 'Prieinamos bylos'; $lang['mediausage'] = 'Failo nuorodai užrašyti naudokite tokią sintaksę:'; $lang['mediaview'] = 'Žiūrėti pirminį failą'; $lang['mediaroot'] = 'pradžia (root)'; $lang['mediaextchange'] = 'Failo galūnė pasikeitė iš .%s į .%s!'; $lang['reference'] = 'Paminėjimai'; $lang['ref_inuse'] = 'Byla negali būti ištrinta, nes ji vis dar yra naudojama šiuose puslapiuose:'; $lang['ref_hidden'] = 'Kai kurie paminėjimai yra puslapiuose, kurių jums neleista skaityti.'; $lang['hits'] = 'Atidarymai'; $lang['quickhits'] = 'Sutampantys pavadinimai'; $lang['toc'] = 'Turinys'; $lang['current'] = 'esamas'; $lang['yours'] = 'Jūsų versija'; $lang['diff'] = 'rodyti skirtumus tarp šios ir esamos versijos'; $lang['diff2'] = 'Parodyti skirtumus tarp pasirinktų versijų'; $lang['line'] = 'Linija'; $lang['breadcrumb'] = 'Kelias:'; $lang['youarehere'] = 'Jūs esate čia:'; $lang['lastmod'] = 'Keista:'; $lang['by'] = 'vartotojo'; $lang['deleted'] = 'ištrintas'; $lang['created'] = 'sukurtas'; $lang['restored'] = 'atstatyta sena versija (%s)'; $lang['external_edit'] = 'redaguoti papildomomis priemonėmis'; $lang['summary'] = 'Redaguoti santrauką'; $lang['noflash'] = '<a href="http://www.adobe.com/products/flashplayer/">Adobe Flash Plugin</a> reikalingas šios medžiagos peržiūrai.'; $lang['mail_newpage'] = '[DokuWiki] puslapis pridėtas:'; $lang['mail_changed'] = '[DokuWiki] puslapis pakeistas:'; $lang['mail_new_user'] = 'naujas vartotojas:'; $lang['mail_upload'] = 'failas įkeltas:'; $lang['qb_bold'] = 'Pusjuodis'; $lang['qb_italic'] = 'Kursyvas'; $lang['qb_underl'] = 'Pabrauktas'; $lang['qb_code'] = 'Kodas'; $lang['qb_strike'] = 'Perbraukta'; $lang['qb_h1'] = 'Pirmo lygio antraštė'; $lang['qb_h2'] = 'Antro lygio antraštė'; $lang['qb_h3'] = 'Trečio lygio antraštė'; $lang['qb_h4'] = 'Ketvirto lygio antraštė'; $lang['qb_h5'] = 'Penkto lygio antraštė'; $lang['qb_link'] = 'Vidinė nuoroda'; $lang['qb_extlink'] = 'Išorinė nuoroda'; $lang['qb_hr'] = 'Horizontali linija'; $lang['qb_ol'] = 'Numeruotas sąrašas'; $lang['qb_ul'] = 'Nenumetuotas sąrašas'; $lang['qb_media'] = 'Paveikslėliai ir kitos bylos'; $lang['qb_sig'] = 'Įterpti parašą'; $lang['qb_smileys'] = 'Šypsenėlės'; $lang['qb_chars'] = 'Specialūs simboliai'; $lang['metaedit'] = 'Redaguoti metaduomenis'; $lang['metasaveerr'] = 'Nepavyko išsaugoti metaduomenų'; $lang['metasaveok'] = 'Metaduomenys išsaugoti'; $lang['img_title'] = 'Pavadinimas:'; $lang['img_caption'] = 'Antraštė:'; $lang['img_date'] = 'Data:'; $lang['img_fname'] = 'Bylos pavadinimas:'; $lang['img_fsize'] = 'Dydis:'; $lang['img_artist'] = 'Fotografas:'; $lang['img_copyr'] = 'Autorinės teisės:'; $lang['img_format'] = 'Formatas:'; $lang['img_camera'] = 'Kamera:'; $lang['img_keywords'] = 'Raktiniai žodžiai:'; $lang['authtempfail'] = 'Vartotojo tapatumo nustatymas laikinai nepasiekiamas. Jei ši situacija kartojasi, tai praneškite savo administratoriui.'; $lang['i_chooselang'] = 'Pasirinkite kalbą'; $lang['i_installer'] = 'DokuWiki Instaliatorius'; $lang['i_wikiname'] = 'Wiki vardas'; $lang['i_enableacl'] = 'Įjungti ACL (rekomenduojama)'; $lang['i_superuser'] = 'Supervartotojas'; $lang['i_problems'] = 'Instaliavimo metu buvo klaidų, kurios pateiktos žemiau. Tęsti negalima, kol nebus pašalintos priežastys.'; $lang['email_signature_text'] = 'Šis laiškas buvo sugeneruotas DokuWiki @DOKUWIKIURL@'; lang/lt/read.txt 0000644 00000000245 15233462216 0007565 0 ustar 00 Šį puslapį galima tik skaityti. Jūs galite peržvelgti jo kodą (source), bet negalite jo keisti. Jei manote, kad tai klaida - susisiekite su administratoriumi. lang/lt/preview.txt 0000644 00000000153 15233462216 0010331 0 ustar 00 ====== Peržiūra ====== Čia matote, kaip atrodo jūsų pakeitimai. **Pakeitimai dar nėra išsaugoti**! lang/lt/updateprofile.txt 0000644 00000000200 15233462216 0011504 0 ustar 00 ====== Redaguoti savo profilį ====== Užpildykite tik tuos laukus, kuriuos norite pakeisti. Vartotojo vardo keisti nebūtina. lang/lt/newpage.txt 0000644 00000000302 15233462216 0010272 0 ustar 00 ====== Šis puslapis dar neegzistuoja ====== Nuoroda, kurią jūs paspaudėte, atvedė į dar neegzistuojantį puslapį. Jūs galite jį sukurti paspausdami **Sukurti šį puslapį** mygtuką. lang/lt/resendpwd.txt 0000644 00000000404 15233462216 0010642 0 ustar 00 ====== Siųsti naują slaptažodį ====== Naujo slaptažodžio gavimui, užpildykite visus žemiau esančius laukus. Naujas slaptažodis bus atsiųstas į jūsų užregistruotą el. pašto adresą. Vartotojo vardas turi būti toks pat kaip ir wiki sistemoje. lang/lt/mailtext.txt 0000644 00000000453 15233462216 0010502 0 ustar 00 Jūsų DokuWiki buvo sukurtas arba pakeistas puslapis. Detalės: Data : @DATE@ Naršyklė : @BROWSER@ IP adresas : @IPADDRESS@ Host'as : @HOSTNAME@ Sena versija: @OLDPAGE@ Nauja versija: @NEWPAGE@ Redagavimo aprašas: @SUMMARY@ Vartotojas : @USER@ Pakeitimo diff'as: @DIFF@ lang/lt/backlinks.txt 0000644 00000000156 15233462216 0010614 0 ustar 00 ====== Atgalinės nuorodos ====== Čia matote sąrašą puslapių, kuriuose yra nuorodos į esamą puslapį. lang/lt/login.txt 0000644 00000000263 15233462216 0007762 0 ustar 00 ====== Prisijungimas ====== Šiuo metu jūs nesate prisijungęs. Įveskite savo prisijungimo duomenis žemiau. „Cookies“ palaikymas jūsų naršyklėje turi būti įjungtas. lang/lt/edit.txt 0000644 00000000442 15233462216 0007576 0 ustar 00 Modifikuokite šį puslapį ir paspauskite ''Išsaugoti''. Apie wiki sintaksę galite paskaityti [[wiki:syntax|čia]]. Prašome redaguoti šį puslapį tik tada, kai galite jį **patobulinti**. Jei tik norite išbandyti wiki galimybes, prašytume tai daryti [[playground:playground|čia]]. lang/lt/diff.txt 0000644 00000000135 15233462216 0007560 0 ustar 00 ====== Skirtumai ====== Čia matote skirtumus tarp pasirinktos versijos ir esamo dokumento. lang/lt/editrev.txt 0000644 00000000175 15233462216 0010316 0 ustar 00 **Jūs naudojate seną šio dokumento versiją!** jei ją išsaugosite, su šiais duomenimis sukursite naują versiją. ---- lang/ro/index.txt 0000644 00000000157 15233462216 0007764 0 ustar 00 ====== Index ====== Acesta e un index al tuturor paginilor ordonat după [[doku>namespaces|spații de nume]]. lang/ro/subscr_form.txt 0000644 00000000237 15233462216 0011200 0 ustar 00 ====== Administrarea abonărilor ====== Această pagină îți permite să îți administrăzi abonările pentru pagina curentă și pentru spațiul de nume. lang/ro/pwconfirm.txt 0000644 00000000427 15233462216 0010661 0 ustar 00 Salutare, @FULLNAME@! Cineva a cerut o parolă nouă pentru @TITLE@ pentru conectarea la @DOKUWIKIURL@. Dacă nu ai solicitat o parolă nouă, ignoră acest e-mail. Pentru a confirma că cererea a fost într-adevăr trimisă de tine, folosește link-ul de mai jos. @CONFIRM@ lang/ro/register.txt 0000644 00000000451 15233462216 0010476 0 ustar 00 ====== Înregistrează-te ca utilizator nou ====== Pentru a crea un wiki nou completează mai jos toate informațiile. Asigură-te că ai introdus o adresă de e-mail **validă** unde va fi trimisă noua parolă. Numele de utilizator trebuie de asemenea să fie valid [[doku>pagename|pagename]]. lang/ro/denied.txt 0000644 00000000124 15233462216 0010077 0 ustar 00 ====== Acces nepermis ====== Din păcate nu ai destule drepturi pentru a continua. lang/ro/revisions.txt 0000644 00000000351 15233462216 0010672 0 ustar 00 ====== Versiune anterioară ====== Acestea sunt versiunile anterioare ale paginii curente. Pentru revenirea la o versiune anteroară, selectează versiunea de mai jos, clic pe ''Editează această pagină'' și salvează versiunea. lang/ro/norev.txt 0000644 00000000245 15233462216 0010004 0 ustar 00 ====== Nu există versiunea paginii ====== Versiunea indicată nu există. Folosește butonul ''Versiuni anterioare'' pentru o listă a versiunilor acestei pagini. lang/ro/admin.txt 0000644 00000000160 15233462216 0007737 0 ustar 00 ====== Administrare ====== Poți vedea mai jos o listă cu acțiunile administrative disponibile în DokuWiki. lang/ro/uploadmail.txt 0000644 00000000363 15233462216 0011003 0 ustar 00 Salutare, @FULLNAME@! Un fișier a fost încărcat în DokuWiki. Iată detaliile: Fișier : @MEDIA@ Dată : @DATE@ Browser : @BROWSER@ Adresă IP : @IPADDRESS@ Hostname : @HOSTNAME@ Dimensiune : @SIZE@ MIME Type : @MIME@ Utilizator : @USER@ lang/ro/subscr_list.txt 0000644 00000000662 15233462216 0011212 0 ustar 00 Salutare, @FULLNAME@! Paginile din spațiul de nume @PAGE@ al @TITLE@ wiki au fost modificate. Modificările sunt următoarele: -------------------------------------------------------- @DIFF@ -------------------------------------------------------- Pentru a anula notificarea paginii, autentificcă-te pe wiki la @DOKUWIKIURL@ apoi accesează @SUBSCRIBE@ și dezabonează-te de la pagină și/sau modificările spațiului de nume. lang/ro/registermail.txt 0000644 00000000353 15233462216 0011342 0 ustar 00 Salutare, @FULLNAME@! Un nou utilizator s-a înregistrat. Iată detaliile: Nume de utilizator : @NEWUSER@ Nume complet : @NEWNAME@ E-mail : @NEWEMAIL@ Dată : @DATE@ Browser : @BROWSER@ Adresă IP : @IPADDRESS@ Hostname : @HOSTNAME@ lang/ro/locked.txt 0000644 00000000272 15233462216 0010114 0 ustar 00 ====== Pagină blocată ====== Pagina este momentan blocată de alt utilizator. Trebuie să aștepți pînă când acest utilizator termină editarea sau până când expiră blocarea. lang/ro/recent.txt 0000644 00000000122 15233462216 0010125 0 ustar 00 ====== Modificări recente ====== Următoarele pagini au fost modificate recent: lang/ro/resetpwd.txt 0000644 00000000167 15233462216 0010513 0 ustar 00 ====== Configurează o parolă nouă ====== Te rog să introduci o parolă nouă pentru contul tău de pe acest wiki. lang/ro/draft.txt 0000644 00000000675 15233462216 0007762 0 ustar 00 ====== Fișierul schiță nu a fost găsit ====== Ultima ta sesiune de editare nu s-a finalizat corect. În vreme ce lucrai, DokuWiki a salvat automat o schiță, pe care o poți utiliza acum pentru a continua editarea. Mai jos poți vedea informațiile care s-au salvat de la ultima sesiune. Decide dacă vrei să //recuperezi// sesiunea de editare pierdută, să //ștergi// schița salvată automat sau să //anulezi// procesul de editare. lang/ro/password.txt 0000644 00000000216 15233462216 0010513 0 ustar 00 Salutare, @FULLNAME@! Aici se găsesc credențialele de utilizator pentru @TITLE@ la @DOKUWIKIURL@ Login : @LOGIN@ Parola : @PASSWORD@ lang/ro/adminplugins.txt 0000644 00000000044 15233462216 0011342 0 ustar 00 ===== Plugin-uri suplimentare ===== lang/ro/searchpage.txt 0000644 00000000127 15233462216 0010754 0 ustar 00 ====== Căutare ====== Rezultatele căutării sunt afișate mai jos. @CREATEPAGEINFO@ lang/ro/showrev.txt 0000644 00000000065 15233462216 0010350 0 ustar 00 **Aceasta e o versiune anterioară a paginii.** ---- lang/ro/stopwords.txt 0000644 00000000762 15233462216 0010723 0 ustar 00 # Aceasta este o listă de cuvinte ignorate la indexare, câte un cuvânt pe linie # Când editezi acest fișier, asigură-te că folosești sfârșituri de linie UNIX # (o singură linie nouă). # Nu e nevoie să incluzi cuvinte mai scurte de 3 caractere - acestea sunt, # oricum, ignorate. # Această listă se bazează pe cele ce pot fi găsite la http://www.ranks.nl/stopwords/ about are and you your them their com for from into how that the this was what when where who will with und the www lang/ro/subscr_single.txt 0000644 00000001030 15233462216 0011506 0 ustar 00 Salutare, @FULLNAME@! Pagina @PAGE@ în @TITLE@ wiki a fost modificată. Modificările sunt următoarele: -------------------------------------------------------- @DIFF@ -------------------------------------------------------- Dată: @DATE@ Utilizator: @USER@ Sumarul editării: @SUMMARY@ Versiune anterioară: @OLDPAGE@ Versiune curentă: @NEWPAGE@ Pentru a anula notificarea paginii, autentificcă-te pe wiki la @DOKUWIKIURL@ apoi accesează @SUBSCRIBE@ și dezabonează-te de la pagină și/sau modificările spațiului de nume. lang/ro/jquery.ui.datepicker.js 0000644 00000002346 15233462216 0012521 0 ustar 00 /* Romanian initialisation for the jQuery UI date picker plugin. * * Written by Edmond L. (ll_edmond@walla.com) * and Ionut G. Stan (ionut.g.stan@gmail.com) */ ( function( factory ) { if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define( [ "../widgets/datepicker" ], factory ); } else { // Browser globals factory( jQuery.datepicker ); } }( function( datepicker ) { datepicker.regional.ro = { closeText: "Închide", prevText: "« Luna precedentă", nextText: "Luna următoare »", currentText: "Azi", monthNames: [ "Ianuarie","Februarie","Martie","Aprilie","Mai","Iunie", "Iulie","August","Septembrie","Octombrie","Noiembrie","Decembrie" ], monthNamesShort: [ "Ian", "Feb", "Mar", "Apr", "Mai", "Iun", "Iul", "Aug", "Sep", "Oct", "Nov", "Dec" ], dayNames: [ "Duminică", "Luni", "Marţi", "Miercuri", "Joi", "Vineri", "Sâmbătă" ], dayNamesShort: [ "Dum", "Lun", "Mar", "Mie", "Joi", "Vin", "Sâm" ], dayNamesMin: [ "Du","Lu","Ma","Mi","Jo","Vi","Sâ" ], weekHeader: "Săpt", dateFormat: "dd.mm.yy", firstDay: 1, isRTL: false, showMonthAfterYear: false, yearSuffix: "" }; datepicker.setDefaults( datepicker.regional.ro ); return datepicker.regional.ro; } ) ); lang/ro/conflict.txt 0000644 00000000603 15233462216 0010452 0 ustar 00 ====== Există o nouă versiune ====== Există o versiune nouă a paginii editate. Aceasta se întâmplă atunci când un alt utilizator a modificat pagina în timp ce editai. Examinează diferențele indicate mai jos, apoi ia decizia care versiune o vei reține. Dacă alegi ''Salvează'', versiunea paginii va fi salvată. Apasă ''Renunțare'' pentru a menține versiunea curentă. lang/ro/lang.php 0000644 00000060644 15233462216 0007555 0 ustar 00 <?php /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * * @author Aleksandr Selivanov <alexgearbox@yandex.ru> * @author Vitalie Ciubotaru <vitalie@ciubotaru.tokyo> * @author Victor <kvp@live.com> * @author Marian Banica <open@banica.eu.org> * @author Tiberiu Micu <tibimicu@gmx.net> * @author Sergiu Baltariu <s_baltariu@yahoo.com> * @author Emanuel-Emeric Andrași <n30@mandrivausers.ro> * @author Marius OLAR <olarmariusalex@gmail.com> * @author Adrian Vesa <adrianvesa@dotwikis.com> * @author valentina_prof <sadoveanu.inform@gmail.com> */ $lang['encoding'] = 'utf-8'; $lang['direction'] = 'ltr'; $lang['doublequoteopening'] = '„'; $lang['doublequoteclosing'] = '“'; $lang['singlequoteopening'] = '‚'; $lang['singlequoteclosing'] = '‘'; $lang['apostrophe'] = '’'; $lang['btn_edit'] = 'Editează această pagină'; $lang['btn_source'] = 'Arată sursa paginii'; $lang['btn_show'] = 'Arată pagina'; $lang['btn_create'] = 'Creează această pagină'; $lang['btn_search'] = 'Caută'; $lang['btn_save'] = 'Salvează'; $lang['btn_preview'] = 'Previzualizează'; $lang['btn_top'] = 'La început'; $lang['btn_newer'] = '<< mai recent'; $lang['btn_older'] = 'mai vechi>>'; $lang['btn_revs'] = 'Versiuni anterioare'; $lang['btn_recent'] = 'Modificări recente'; $lang['btn_upload'] = 'Upload'; $lang['btn_cancel'] = 'Renunțare'; $lang['btn_index'] = 'Index'; $lang['btn_secedit'] = 'Editează'; $lang['btn_login'] = 'Autentificare'; $lang['btn_logout'] = 'Deconectare'; $lang['btn_admin'] = 'Administrativ'; $lang['btn_update'] = 'Actualizează'; $lang['btn_delete'] = 'Șterge'; $lang['btn_back'] = 'Înapoi'; $lang['btn_backlink'] = 'Legătură anterioară'; $lang['btn_subscribe'] = 'Subscrie modificarea paginii'; $lang['btn_profile'] = 'Actualizează profil'; $lang['btn_reset'] = 'Resetează'; $lang['btn_resendpwd'] = 'Configurează o parolă nouă'; $lang['btn_draft'] = 'Editează schiță'; $lang['btn_recover'] = 'Recuperează schiță'; $lang['btn_draftdel'] = 'Șterge schiță'; $lang['btn_revert'] = 'Revenire'; $lang['btn_register'] = 'Înregistrează'; $lang['btn_apply'] = 'Aplică'; $lang['btn_media'] = 'Administrare media'; $lang['btn_deleteuser'] = 'Sterge-mi contul'; $lang['btn_img_backto'] = 'Înapoi la %s'; $lang['btn_mediaManager'] = 'Vizualizează în administratorul media'; $lang['loggedinas'] = 'Autentificat ca:'; $lang['user'] = 'Utilizator'; $lang['pass'] = 'Parola'; $lang['newpass'] = 'Parola nouă'; $lang['oldpass'] = 'Confirmă parola curentă'; $lang['passchk'] = 'Încă o dată'; $lang['remember'] = 'Ține-mă minte'; $lang['fullname'] = 'Nume complet'; $lang['email'] = 'E-mail'; $lang['profile'] = 'Profil utilizator'; $lang['badlogin'] = 'Ne pare rău, utilizatorul și/sau parola au fost greșite.'; $lang['badpassconfirm'] = 'Ne pare rau, parola este gresita'; $lang['minoredit'] = 'Modificare minoră'; $lang['draftdate'] = 'Schiță salvată automat la'; $lang['nosecedit'] = 'Pagina s-a modificat între timp, secțiunea info a expirat, s-a încărcat pagina întreagă în loc.'; $lang['searchcreatepage'] = 'Dacă nu găsești ceea ce căuți, poți crea sau modifica pagina %s numită după căutarea ta.'; $lang['search_fullresults'] = 'Rezultatele textului-complet'; $lang['js']['search_toggle_tools'] = 'Folosiți instrumentele de căutare'; $lang['js']['willexpire'] = 'Blocarea pentru editarea paginii expiră intr-un minut.\nPentru a preveni conflictele folosește butonul de previzualizare pentru resetarea blocării.'; $lang['js']['notsavedyet'] = 'Există modificări nesalvate care se vor pierde. Dorești să continui?'; $lang['js']['searchmedia'] = 'Caută fișiere'; $lang['js']['keepopen'] = 'Menține fereastra deschisă la selecție'; $lang['js']['hidedetails'] = 'Ascunde detalii'; $lang['js']['mediatitle'] = 'Configurare link'; $lang['js']['mediadisplay'] = 'Tip de link'; $lang['js']['mediaalign'] = 'Aliniere'; $lang['js']['mediasize'] = 'Mărime imagine'; $lang['js']['mediatarget'] = 'Țintă link'; $lang['js']['mediaclose'] = 'Închide'; $lang['js']['mediainsert'] = 'Inserează'; $lang['js']['mediadisplayimg'] = 'Afișează imaginea'; $lang['js']['mediadisplaylnk'] = 'Afișează doar link-ul'; $lang['js']['mediasmall'] = 'Versiune mică'; $lang['js']['mediamedium'] = 'Versiune medie'; $lang['js']['medialarge'] = 'Versiune mare'; $lang['js']['mediaoriginal'] = 'Versiune inițială'; $lang['js']['medialnk'] = 'Link către pagina detaliilor'; $lang['js']['mediadirect'] = 'Link direct către versiunea inițială'; $lang['js']['medianolnk'] = 'Fără link'; $lang['js']['medianolink'] = 'Nu crea link către imagine'; $lang['js']['medialeft'] = 'Aliniază imaginea la stânga'; $lang['js']['mediaright'] = 'Aliniază imaginea la dreapta'; $lang['js']['mediacenter'] = 'Aliniază imaginea la centru'; $lang['js']['medianoalign'] = 'Nu utiliza aliniere'; $lang['js']['nosmblinks'] = 'Link-urile către sharing-uri Windows funcționeaza numai în Microsoft Internet Explorer. Poți însă copia și insera link-ul.'; $lang['js']['linkwiz'] = 'Asistent legătură'; $lang['js']['linkto'] = 'Legătură la:'; $lang['js']['del_confirm'] = 'Ești sigur de ștergerea elementele selectate?'; $lang['js']['restore_confirm'] = 'Ești sigur de restaurarea acestei versiuni?'; $lang['js']['media_diff'] = 'Arată diferențele:'; $lang['js']['media_diff_both'] = 'Unul lângă altul'; $lang['js']['media_diff_opacity'] = 'Străveziu'; $lang['js']['media_diff_portions'] = 'Glisează'; $lang['js']['media_select'] = 'Selectează fișierele...'; $lang['js']['media_upload_btn'] = 'Încarcă'; $lang['js']['media_done_btn'] = 'Gata'; $lang['js']['media_drop'] = 'Lasă fișierele aici pentru încărcarea lor'; $lang['js']['media_cancel'] = 'Înlătură'; $lang['js']['media_overwrt'] = 'Suprascrie fișierele deja existente'; $lang['search_exact_match'] = 'Potrivire perfecta'; $lang['search_starts_with'] = 'Începe cu'; $lang['search_ends_with'] = 'Termină cu'; $lang['search_contains'] = 'Conţine'; $lang['search_custom_match'] = 'Personalizat'; $lang['search_any_ns'] = 'Orice spațiu de nume'; $lang['search_any_time'] = 'Oricând'; $lang['search_past_7_days'] = 'Săptămâna trecută'; $lang['search_past_month'] = 'Luna trecută'; $lang['search_past_year'] = 'Anul trecut'; $lang['search_sort_by_hits'] = 'Sortează după popularitate'; $lang['search_sort_by_mtime'] = 'Sortează după ultima modificare'; $lang['regmissing'] = 'Ne pare rău, trebuie să completezi toate cîmpurile.'; $lang['reguexists'] = 'Ne pare rău, un utilizator cu acest nume este deja autentificat.'; $lang['regsuccess'] = 'Utilizatorul a fost creat. Parola a fost trimisă prin e-mail.'; $lang['regsuccess2'] = 'Utilizatorul a fost creat.'; $lang['regfail'] = 'Utilizatorul nu a putut fi creat.'; $lang['regmailfail'] = 'Se pare că a fost o eroare la trimiterea parolei prin e-mail. Contactează administratorul!'; $lang['regbadmail'] = 'Adresa de e-mail este nevalidă - dacă ești de părere că este o eroare contactează administratorul.'; $lang['regbadpass'] = 'Cele două parole furnizate nu sunt identice; încearcă din nou.'; $lang['regpwmail'] = 'Parola ta DokuWiki'; $lang['reghere'] = 'Încă nu ai un cont? Creează unul!'; $lang['profna'] = 'Acest wiki nu permite modificarea profilului'; $lang['profnochange'] = 'Nici o modificare; nimic de făcut.'; $lang['profnoempty'] = 'Nu sunt permise numele sau adresa de e-mail necompletate.'; $lang['profchanged'] = 'Profilul de utilizator a fost actualizat cu succes.'; $lang['profnodelete'] = 'Acest wiki nu accepta stergerea conturilor utilizatorilor'; $lang['profdeleteuser'] = 'Sterge cont'; $lang['profdeleted'] = 'Contul tau a fost sters de pe acest wiki'; $lang['profconfdelete'] = 'As dori sa sterf contul meu de pe acest Wiki. <br/> Aceasta actiune nu poate fi anulata.'; $lang['profconfdeletemissing'] = 'Căsuța de confirmare nu este bifată'; $lang['proffail'] = 'Profilul utilizatorului nu a fost actualizat.'; $lang['pwdforget'] = 'Parolă uitată? Obține una nouă!'; $lang['resendna'] = 'Acest wiki nu permite retrimiterea parolei.'; $lang['resendpwd'] = 'Configurează o parolă nouă pentru'; $lang['resendpwdmissing'] = 'Ne pare rău, trebuie completate toate câmpurile.'; $lang['resendpwdnouser'] = 'Ne pare rău, acest utilizator nu poate fi găsit în baza de date.'; $lang['resendpwdbadauth'] = 'Ne pare rău, acest cod de autorizare nu este corect. Verifică dacă ai folosit întreg link-ul de confirmare.'; $lang['resendpwdconfirm'] = 'Un link de confirmare a fost trimis prin e-mail.'; $lang['resendpwdsuccess'] = 'Noua parolă a fost trimisă prin e-mail.'; $lang['license'] = 'Exceptând locurile unde este altfel specificat, conținutul acestui wiki este licențiat sub următoarea licență:'; $lang['licenseok'] = 'Notă: Prin editarea acestei pagini ești de acord să publici conțintul sub următoarea licență:'; $lang['searchmedia'] = 'Caută numele fișierului:'; $lang['searchmedia_in'] = 'Caută în %s'; $lang['txt_upload'] = 'Selectează fișierul de încărcat:'; $lang['txt_filename'] = 'Încarcă fișierul ca (opțional):'; $lang['txt_overwrt'] = 'Suprascrie fișierul existent'; $lang['maxuploadsize'] = 'Incarcare maxima %s per fisier.'; $lang['lockedby'] = 'Momentan blocat de:'; $lang['lockexpire'] = 'Blocarea expiră la:'; $lang['rssfailed'] = 'A apărut o eroare in timpul descărcării acestui câmp: '; $lang['nothingfound'] = 'Nu am găsit nimic.'; $lang['mediaselect'] = 'Fișiere media'; $lang['uploadsucc'] = 'Încărcare reușită'; $lang['uploadfail'] = 'Încărcare eșuată. Poate din cauza permisiunilor?'; $lang['uploadwrong'] = 'Încărcare nepermisă. Extensia fișierului e nepermisă'; $lang['uploadexist'] = 'Fișierul există deja. Nimic nu a fost făcut.'; $lang['uploadbadcontent'] = 'Conținutul încărcat nu corespunde extensiei fișierului %s.'; $lang['uploadspam'] = 'Încărcarea a fost blocată din cauza listei negre de spam.'; $lang['uploadxss'] = 'Încărcarea a fost blocată din cauza unui posibil conținut dăunător.'; $lang['uploadsize'] = 'Fișierul uploadat a fost prea mare. (max %s)'; $lang['deletesucc'] = 'Fișierul "%s" a fost șters.'; $lang['deletefail'] = '"%s" nu a putut fi șters - verifică permisiunile.'; $lang['mediainuse'] = 'Fișierul "%s" nu a fost șters - este încă în uz.'; $lang['namespaces'] = 'Spații de nume'; $lang['mediafiles'] = 'Fișiere disponibile în'; $lang['accessdenied'] = 'Nu îți este permis să vizualizezi această pagină.'; $lang['mediausage'] = 'Folosește următoarea sintaxă pentru a face referință la acest fișier:'; $lang['mediaview'] = 'Vizualizează fișierul inițial'; $lang['mediaroot'] = 'root'; $lang['mediaupload'] = 'Încarcă un fișier in acest spațiu de nume. Pentru a crea sub-spații de nume, adaugă-le la fișierul de încărcat, separate de doua puncte (:).'; $lang['mediaextchange'] = 'Extensia fișierului a fost modificată din .%s în .%s.'; $lang['reference'] = 'Referință pentru'; $lang['ref_inuse'] = 'Fișierul nu a putut fi șters întrucât este folosit de următoarele pagini:'; $lang['ref_hidden'] = 'Nu ai permisiunea să citești o parte din referințele din pagină.'; $lang['hits'] = 'Accese'; $lang['quickhits'] = 'Nume de pagini potrivite'; $lang['toc'] = 'Cuprins'; $lang['current'] = 'curent'; $lang['yours'] = 'Versiunea ta'; $lang['diff'] = 'Arată diferențele față de versiunea curentă'; $lang['diff2'] = 'Arată diferențele dintre versiunile selectate'; $lang['difflink'] = 'Link către această vizualizare comparativă'; $lang['diff_type'] = 'Vezi diferențe:'; $lang['diff_inline'] = 'Succesiv'; $lang['diff_side'] = 'Alăturate'; $lang['diffprevrev'] = 'Versiuni anterioare'; $lang['diffnextrev'] = 'Urmatoarea versiune'; $lang['difflastrev'] = 'Ultima versiune'; $lang['diffbothprevrev'] = 'Ambele părți revizuirea anterioară'; $lang['diffbothnextrev'] = 'Ambele părți următoarea reviziune'; $lang['line'] = 'Linia'; $lang['breadcrumb'] = 'Traseu:'; $lang['youarehere'] = 'Ești aici:'; $lang['lastmod'] = 'Ultima modificare:'; $lang['by'] = 'de către'; $lang['deleted'] = 'șters'; $lang['created'] = 'creat'; $lang['restored'] = 'versiune veche restaurată (%s)'; $lang['external_edit'] = 'editare externă'; $lang['summary'] = 'Editează sumarul'; $lang['noflash'] = 'Plugin-ul <a href="http://www.adobe.com/products/flashplayer/">Adobe Flash Plugin</a> este necesar pentru afișarea corectă a conținutului.'; $lang['download'] = 'Bloc descărcări'; $lang['tools'] = 'Unelte'; $lang['user_tools'] = 'Unelte utilizator'; $lang['site_tools'] = 'Unelte site'; $lang['page_tools'] = 'Unelte pagină'; $lang['skip_to_content'] = 'mergi la conținut'; $lang['sidebar'] = 'Bara de navigare'; $lang['mail_newpage'] = 'pagină adăugată:'; $lang['mail_changed'] = 'pagină schimbată:'; $lang['mail_subscribe_list'] = 'pagini modificate în spațiul de nume:'; $lang['mail_new_user'] = 'utilizator nou'; $lang['mail_upload'] = 'fișier încărcat:'; $lang['changes_type'] = 'Vizualizare modificări'; $lang['pages_changes'] = 'Pagini'; $lang['media_changes'] = 'Fișiere media'; $lang['both_changes'] = 'Ambele pagini și fișiere media'; $lang['qb_bold'] = 'Text aldin'; $lang['qb_italic'] = 'Text cursiv'; $lang['qb_underl'] = 'Text subliniat'; $lang['qb_code'] = 'Text cod'; $lang['qb_strike'] = 'Text tăiat'; $lang['qb_h1'] = 'Titlu de nivel 1'; $lang['qb_h2'] = 'Titlu de nivel 2'; $lang['qb_h3'] = 'Titlu de nivel 3'; $lang['qb_h4'] = 'Titlu de nivel 4'; $lang['qb_h5'] = 'Titlu de nivel 5'; $lang['qb_h'] = 'Titlu'; $lang['qb_hs'] = 'Selectează titlul'; $lang['qb_hplus'] = 'Titlu mai mare'; $lang['qb_hminus'] = 'Titlu mai mic'; $lang['qb_hequal'] = 'Titlu de același nivel'; $lang['qb_link'] = 'Link intern'; $lang['qb_extlink'] = 'Link extern'; $lang['qb_hr'] = 'Linie orizontală'; $lang['qb_ol'] = 'Listă ordonată'; $lang['qb_ul'] = 'Listă neordoată'; $lang['qb_media'] = 'Adaugă imagini și alte fișiere'; $lang['qb_sig'] = 'Inserează semnătură'; $lang['qb_smileys'] = 'Smiley-uri'; $lang['qb_chars'] = 'Caractere speciale'; $lang['upperns'] = 'Accesează spațiul de nume părinte'; $lang['metaedit'] = 'Editează metadata'; $lang['metasaveerr'] = 'Scrierea metadatelor a eșuat'; $lang['metasaveok'] = 'Metadatele au fost salvate'; $lang['img_title'] = 'Titlu:'; $lang['img_caption'] = 'Legendă:'; $lang['img_date'] = 'Dată:'; $lang['img_fname'] = 'Nume fișier:'; $lang['img_fsize'] = 'Dimensiune:'; $lang['img_artist'] = 'Fotograf:'; $lang['img_copyr'] = 'Drept de autor:'; $lang['img_format'] = 'Format:'; $lang['img_camera'] = 'Camera:'; $lang['img_keywords'] = 'Cuvinte cheie:'; $lang['img_width'] = 'Lățime:'; $lang['img_height'] = 'Înălțime:'; $lang['subscr_subscribe_success'] = 'Adăugat %s la lista de abonare pentru %s'; $lang['subscr_subscribe_error'] = 'Eroare la adăugarea %s la lista de abonare pentru %s'; $lang['subscr_subscribe_noaddress'] = 'Nu există adresă de e-mail asociată autentificării curente, nu poți fi adăugat la lista de abonare'; $lang['subscr_unsubscribe_success'] = 'Șters %s din lista de abonare pentru %s'; $lang['subscr_unsubscribe_error'] = 'Eroare la ștergerea %s din lista de abonare pentru %s'; $lang['subscr_already_subscribed'] = '%s este deja abonat la %s'; $lang['subscr_not_subscribed'] = '%s nu este abonat la %s'; $lang['subscr_m_not_subscribed'] = 'Momentan nu ești abonat la pagina curentă sau la spațiul de nume.'; $lang['subscr_m_new_header'] = 'Adaugă abonare'; $lang['subscr_m_current_header'] = 'Abonări curente'; $lang['subscr_m_unsubscribe'] = 'Dezabonează-te'; $lang['subscr_m_subscribe'] = 'Abonează-te'; $lang['subscr_m_receive'] = 'Primește'; $lang['subscr_style_every'] = 'e-mail la ficare schimbare'; $lang['subscr_style_digest'] = 'e-mail cu sumar al modificărilor pentru fiecare pagină (la fiecare %.2f zile)'; $lang['subscr_style_list'] = 'lista paginilor modificate de la ultimul e-mail (la fiecare %.2f zile)'; $lang['authtempfail'] = 'Autentificarea utilizatorului este temporar indisponibilă. Contactează administratorul.'; $lang['i_chooselang'] = 'Alege limba'; $lang['i_installer'] = 'Installer DokuWiki'; $lang['i_wikiname'] = 'Numele acestui wiki'; $lang['i_enableacl'] = 'Activează ACL (liste de control a accesului) (recomandat)'; $lang['i_superuser'] = 'Utilizator privilegiat'; $lang['i_problems'] = 'Programul de instalare a găsit câteva probleme, indicate mai jos. Nu poți continua până nu le rezolvi.'; $lang['i_modified'] = 'Din motive de securitate, acest script va funcționa doar cu o instalare nouă și nemodificată a DokuWiki. Poți fie să extragi din nou fișierele din arhiva descărcată fie să consulți instrucțiunile de instalare DokuWiki la <a href="http://dokuwiki.org/install">'; $lang['i_funcna'] = 'Funcția PHP <code>%s</code> nu este disponibilă. Probabil provider-ul tău a dezactivat-o pentru un motiv anume.'; $lang['i_disabled'] = 'a fost dezactivat de furnizorul tău'; $lang['i_funcnmail'] = '<b>Notă:</b> Funcția PHP de email nu este disponibilă %s Daca tot rămâne nedisponibilă, trebuie sa instalezi <a href="https://www.dokuwiki.org/plugin:smtp">modulul stmp</a>. '; $lang['i_phpver'] = 'Versiunea ta de PHP <code>%s</code> este mai veche decât cea necesară (<code>%s</code>). Trebuie să îți actualizezi instalarea PHP.'; $lang['i_mbfuncoverload'] = 'mbstring.func_overload trebuie să fie dezactivată în php.ini pentru a rula DokuWiki.'; $lang['i_urandom'] = 'DokuWiki nu poate sa creeze numere sigure criptografice pentru cookie-uri. Poate doriți să verificați setările open_basedir din php.ini pentru acces <code>/dev/urandom</code> adecvat.'; $lang['i_permfail'] = '<code>%s</code> nu poate fi scris de către DokuWiki. Trebuie să modifici permisiunile pe acest director.'; $lang['i_confexists'] = '<code>%s</code> există deja'; $lang['i_writeerr'] = 'Nu s-a putut crea <code>%s</code>. Trebuie să verifici permisiunile directorului/fișierului și să creezi fișierul manual.'; $lang['i_badhash'] = 'dokuwiki.php nu a fost recunoscut sau a fost modificat (hash=<code>%s</code>)'; $lang['i_badval'] = '<code>%s</code> - valoare nepemisă sau neintrodusă'; $lang['i_success'] = 'Configurarea a fost finalizată cu succes. Acum poți sterge fișierul install.php. Poți accesa <a href="doku.php?id=wiki:welcome">noua ta instanță DokuWiki</a>.'; $lang['i_failure'] = 'Au apărut erori la scrierea fișierelor de configurare. Va trebui să le corectezi manual înainte de a putea folosi <a href="doku.php?id=wiki:welcome">noua ta instanță DokuWiki</a>.'; $lang['i_policy'] = 'Politica ACL (liste de control a accesului) inițială'; $lang['i_pol0'] = 'Wiki deschis (oricine poate citi, scrie și încărca fișiere)'; $lang['i_pol1'] = 'Wiki public (oricine poate citi, utilizatorii înregistrați pot scrie și încărca fișiere)'; $lang['i_pol2'] = 'Wiki închis (doar utilizatorii înregistrați pot citi, scrie și încărca fișiere)'; $lang['i_allowreg'] = 'Permite utilizatorilor sa se inregistreze singuri.'; $lang['i_retry'] = 'Încearcă din nou'; $lang['i_license'] = 'Te rugăm să alegi licența sub care dorești să publici conținutul:'; $lang['i_license_none'] = 'Nu arata nici o informatie despre licenta.'; $lang['i_pop_field'] = 'Te rog, ajuta-ne sa imbunatatim experienta DokuWiki.'; $lang['i_pop_label'] = 'Odata pe luna, trimite date catre dezvoltatorii DokuWiki in mod anonim.'; $lang['recent_global'] = 'În acest moment vizualizezi modificările în interiorul spațiului de nume <b>%s</b>. De asemenea poți <a href="%s">vizualiza modificările recente în întregului wiki-ul</a>.'; $lang['years'] = 'acum %d ani'; $lang['months'] = 'acum %d luni'; $lang['weeks'] = 'acum %d săptămâni'; $lang['days'] = 'acum %d zile'; $lang['hours'] = 'acum %d ore'; $lang['minutes'] = 'acum %d minute'; $lang['seconds'] = 'acum %d secunde'; $lang['wordblock'] = 'Modificarea ta nu a fost salvată deoarece conține text blocat (spam).'; $lang['media_uploadtab'] = 'Încărcare fișier'; $lang['media_searchtab'] = 'Căutare'; $lang['media_file'] = 'Fișier'; $lang['media_viewtab'] = 'Vizualizare'; $lang['media_edittab'] = 'Editare'; $lang['media_historytab'] = 'Istoric'; $lang['media_list_thumbs'] = 'Miniaturi'; $lang['media_list_rows'] = 'Linii'; $lang['media_sort_name'] = 'Nume'; $lang['media_sort_date'] = 'Dată'; $lang['media_namespaces'] = 'Alege spațiul de nume'; $lang['media_files'] = 'Fișiere în %s'; $lang['media_upload'] = 'Încărcare în %s'; $lang['media_search'] = 'Cautare în %s'; $lang['media_view'] = '%s'; $lang['media_viewold'] = '%s în %s'; $lang['media_edit'] = 'Editare %s'; $lang['media_history'] = 'Istoricul pentru %s'; $lang['media_meta_edited'] = 'metadate editate'; $lang['media_perm_read'] = 'Ne pare rău, dar nu ai suficiente permisiuni pentru a putea citi fișiere.'; $lang['media_perm_upload'] = 'Ne pare rău, dar nu ai suficiente permisiuni pentru a putea încărca fișiere.'; $lang['media_update'] = 'Încarcă noua versiune'; $lang['media_restore'] = 'Restaurează această versiune'; $lang['media_acl_warning'] = 'Este posibil ca această listă să nu fie completă din cauza restricțiilor ACL și a paginilor ascunse.'; $lang['email_fail'] = 'PHP mail() lipsește sau este dezactivat. Următorul email nu a fost trimis:'; $lang['currentns'] = 'Spațiul de nume curent'; $lang['searchresult'] = 'Rezultatul cautarii'; $lang['plainhtml'] = 'HTML simplu'; $lang['page_nonexist_rev'] = 'Pagina nu a existat la %s. Ulterior a fost creat la <a href="%s">%s </a>.'; $lang['unable_to_parse_date'] = 'Imposibil de analizat la parametrul "%s".'; $lang['email_signature_text'] = 'Acest e-mail a fost generat de DokuWiki la @DOKUWIKIURL@'; lang/ro/install.html 0000644 00000002711 15233462216 0010446 0 ustar 00 <p>Această pagină oferă asistență la instalarea pentru prima dată a <a href="http://dokuwiki.org">Dokuwiki</a>. Mai multe informații privind această instalare găsești în <a href="http://dokuwiki.org/installer">pagina de documentație</a>.</p> <p>DokuWiki folosește fișiere obișnuite pentru stocarea paginilor wiki și a informaților asociate acestor pagini (de ex. imagini, indecși de căutare, versiuni vechi etc.). Pentru a putea fi folosit, DokuWiki <strong>trebuie</strong> să aibă drepturi de scriere în directoarele ce conțin aceste fișiere. Acest script de instalare nu poate configura drepturile directoarelor. De regulă, aceasta se face direct, în linie de comandă, sau în cazul unoi soluții de hosting, prin FTP sau prin panoul de control al gazdei (de ex. cPanel).</p> <p>Acest script de instalare va configura DokuWiki pentru <abbr title="access control list">ACL</abbr>, care permite autentificarea administratorului și accesul la meniul de administrare pentru instalarea plugin-urilor, gestiunea utilizatorilor, accesului la paginile wiki și modificarea configurației. Acest script nu este necesar pentru funcționarea DokuWiki, însă ușurează administrarea. <p>Utilizatorii experimentați sau utilizatorii care au nevoie de o configurație specială pot accesa paginile cu <a href="http://dokuwiki.org/install">instrucțiunile de instalare</a> și <a href="http://dokuwiki.org/config">opțiunile de configurare</a> a DokuWiki.</p> lang/ro/read.txt 0000644 00000000247 15233462216 0007570 0 ustar 00 Această pagină poate fi doar citită. Poți vedea sursa, dar nu poți modifica pagina. Consultă administratorul dacă ești de părere că ceva este în neregulă. lang/ro/preview.txt 0000644 00000000162 15233462216 0010332 0 ustar 00 ====== Previzualizare ====== Acesta este modul în care va arăta textul. **Ai în vedere: Nu e încă salvat**! lang/ro/updateprofile.txt 0000644 00000000236 15233462216 0011516 0 ustar 00 ====== Actualizare profil utilizator ====== Trebuie să completezi doar câmpurile pe care dorești să le modifici. Nu poți modifica numele de utilizator. lang/ro/onceexisted.txt 0000644 00000000406 15233462216 0011164 0 ustar 00 ======= Această pagină nu mai există ====== Ați urmărit un link către o pagină care nu mai există. Puteți verifica lista de [[?do=revisions|reviziuni vechi]] pentru a vedea când și de ce a fost ștearsă, accesați reviziile vechi sau restaurați-o. lang/ro/subscr_digest.txt 0000644 00000000724 15233462216 0011515 0 ustar 00 Salutare, @FULLNAME@! Pagina @PAGE@ în @TITLE@ wiki a fost modificată. Acestea sunt modificările: -------------------------------------------------------- @DIFF@ -------------------------------------------------------- Versiune anterioară: @OLDPAGE@ Versiune curentă: @NEWPAGE@ Pentru a anula notificarea paginii, autentifică-te pe wiki la @DOKUWIKIURL@ apoi accesează @SUBSCRIBE@ și dezabonează-te de la pagină și/sau modificările spațiului de nume. lang/ro/newpage.txt 0000644 00000000245 15233462216 0010301 0 ustar 00 ====== Pagina nu există încă ====== Ai urmat o legătură către o pagină care nu există. O poti crea prin apăsarea butonului **Editează această pagină**. lang/ro/resendpwd.txt 0000644 00000000332 15233462216 0010643 0 ustar 00 ====== Trimite parolă nouă ====== Introduc numele de utilizator în formularul de mai jos pentru a solicita o nouă parolă pentru aceast wiki. Un link de confirmare va fi trimis la adresa de e-mail înregistrată. lang/ro/mailtext.txt 0000644 00000000431 15233462216 0010477 0 ustar 00 Salutare, @FULLNAME@! A fost adăugată sau modificată o pagină. Aici sunt detaliile: Dată : @DATE@ Browser : @BROWSER@ Adresă IP : @IPADDRESS@ Hostname : @HOSTNAME@ Versiune anterioară : @OLDPAGE@ Versiune curentă : @NEWPAGE@ Sumar editare: @SUMMARY@ @DIFF@ lang/ro/backlinks.txt 0000644 00000000150 15233462216 0010607 0 ustar 00 ====== Legături înapoi ====== Aceasta e o listă de pagini care au legături către pagina curentă. lang/ro/login.txt 0000644 00000000261 15233462216 0007761 0 ustar 00 ====== Autentificare ====== Nu ești autentificat! Introdu datele de autentificare. Pentru ca autentificarea să funcționeze trebuie să fie permise cookie-urile în browser. lang/ro/edit.txt 0000644 00000000362 15233462216 0007600 0 ustar 00 Editează pagina și apasă ''Salvează''. Vezi [[wiki:syntax]] pentru sintaxă. Te rog editează pagina doar pentru a o **îmbunătați**. Dacă vrei să testezi câteva lucruri, învață sa faci primii pași în [[playground:playground]]. lang/ro/diff.txt 0000644 00000000172 15233462216 0007562 0 ustar 00 ====== Diferențe ====== Aici sunt prezentate diferențele dintre versiunile selectate și versiunea curentă a paginii. lang/ro/editrev.txt 0000644 00000000172 15233462216 0010314 0 ustar 00 **Ai încărcat o versiune anterioră a paginii.** Dacă ai salvat-o, vei crea o versiune nouă cu aceast conținut. ---- lang/vi/index.txt 0000644 00000000250 15233462216 0007754 0 ustar 00 ====== Sơ đồ trang web ====== Đây là sơ đồ trang web chứa tất cả các trang có sẵn được sắp xếp theo [[doku>namespaces|không gian tên]]. lang/vi/subscr_form.txt 0000644 00000000216 15233462216 0011173 0 ustar 00 ====== Quản lý đăng ký ====== Trang này cho phép bạn quản lý đăng ký của mình cho trang hiện tại và không gian tên. lang/vi/pwconfirm.txt 0000644 00000000526 15233462216 0010657 0 ustar 00 Chào @FULLNAME@! Ai đó đã yêu cầu mật khẩu mới cho bạn đăng nhập @TITLE@ tại @DOKUWIKIURL@ Nếu bạn không yêu cầu mật khẩu mới thì hãy bỏ qua thư điện tử này. Để xác nhận rằng yêu cầu đã thực sự được gửi bởi bạn, vui lòng truy cập vào liên kết sau. @CONFIRM@ lang/vi/register.txt 0000644 00000000720 15233462216 0010473 0 ustar 00 ====== Đăng ký làm thành viên mới ====== Điền vào tất cả các thông tin dưới đây để tạo một tài khoản mới trong wiki này. Hãy chắc chắn rằng bạn cung cấp một **địa chỉ thư điện tử hợp lệ** - nếu bạn không được yêu cầu nhập mật khẩu tại đây, một mật khẩu mới sẽ được gửi đến địa chỉ đó. Tên đăng nhập phải là [[doku>pagename|tên]] hợp lệ . lang/vi/denied.txt 0000644 00000000156 15233462216 0010102 0 ustar 00 ====== Không có quyền thực hiện ====== Xin lỗi, bạn không có đủ quyền để tiếp tục. lang/vi/revisions.txt 0000644 00000000350 15233462216 0010667 0 ustar 00 ====== Phiên bản cũ ====== Đây là những phiên bản cũ của tài liệu hiện tại. Để lùi về một phiên bản cũ, chọn phiên bản từ bên dưới, nhấn vào ''Sửa đổi trang này'' và lưu nó. lang/vi/norev.txt 0000644 00000000271 15233462216 0010001 0 ustar 00 ====== Không có phiên bản ====== Phiên bản bạn yêu cầu không tồn tại. Nhấn vào "Phiên bản cũ" để biết danh sách phiên bản cũ của tài liệu này. lang/vi/admin.txt 0000644 00000000211 15233462216 0007732 0 ustar 00 ====== Quản lý ====== Dưới đây bạn có thể tìm thấy một danh sách các tác vụ quản lý có sẵn trong DokuWiki. lang/vi/uploadmail.txt 0000644 00000000542 15233462216 0011000 0 ustar 00 Một tập tin đã được tải lên DokuWiki của bạn. Đây là những thông tin chi tiết: Tập tin : @MEDIA@ Phiên bản cũ: @OLD@ Ngày : @DATE@ Trình duyệt : @BROWSER@ Địa chỉ IP : @IPADDRESS@ Tên máy chủ : @HOSTNAME@ Kích thước : @SIZE@ Loại MIME : @MIME@ Thành viên : @USER@ lang/vi/subscr_list.txt 0000644 00000000623 15233462216 0011205 0 ustar 00 Chào! Trang @PAGE@ trong wiki @TITLE@ đã được thay đổi. Dưới đây là những thay đổi: -------------------------------------------------------- @DIFF@ -------------------------------------------------------- Để hủy thông báo trang, đăng nhập vào wiki tại @DOKUWIKIURL@ sau đó truy cập @SUBSCRIBE@ và hủy đăng ký trang và/hoặc thay đổi không gian tên. lang/vi/registermail.txt 0000644 00000000456 15233462216 0011344 0 ustar 00 Một người dùng mới đã đăng ký. Đây là những thông tin chi tiết: Tên thành viên : @NEWUSER@ Tên đầy đủ : @NEWNAME@ Thư điện tử : @NEWEMAIL@ Ngày : @DATE@ Trình duyệt : @BROWSER@ Địa chỉ IP : @IPADDRESS@ Tên máy chủ : @HOSTNAME@ lang/vi/locked.txt 0000644 00000000326 15233462216 0010112 0 ustar 00 ====== Trang bị khóa ====== Trang này hiện đang bị khóa do thành viên khác đang sửa đổi. Bạn phải đợi cho đến khi thành viên này hoàn thành sửa đổi hoặc khóa hết hạn. lang/vi/recent.txt 0000644 00000000126 15233462216 0010127 0 ustar 00 ====== Thay đổi gần đây ====== Những trang sau có thay đổi gần đây: lang/vi/resetpwd.txt 0000644 00000000172 15233462216 0010505 0 ustar 00 ====== Đặt mật khẩu mới ====== Vui lòng nhập mật khẩu mới cho tài khoản của bạn trong wiki này. lang/vi/draft.txt 0000644 00000001132 15233462216 0007745 0 ustar 00 ====== Tìm thấy tập tin nháp ====== Phiên sửa đổi cuối cùng của bạn trên trang này không được hoàn thành chính xác. DokuWiki tự động lưu một bản nháp trong khi bạn đang sửa đổi mà bây giờ bạn có thể sử dụng để tiếp tục sửa đổi. Bạn có thể thấy dữ liệu dưới đây được lưu từ phiên trước của bạn. Vui lòng quyết định nếu bạn muốn //khôi phục// phiên sửa đổi bị mất của bạn, //xóa// bản nháp được lưu tự động hoặc //hủy bỏ// quá trình sửa đổi. lang/vi/password.txt 0000644 00000000237 15233462216 0010514 0 ustar 00 Chào @FULLNAME@! Đây là thông tin thành viên chi tiết để bạn đăng nhập @TITLE@ tại @DOKUWIKIURL@: Tên: @LOGIN@ Mật khẩu: @PASSWORD@ lang/vi/adminplugins.txt 0000644 00000000034 15233462216 0011337 0 ustar 00 ===== Plugin bổ sung ===== lang/vi/searchpage.txt 0000644 00000000202 15233462216 0010744 0 ustar 00 ====== Tìm kiếm ====== Bạn có thể tìm thấy kết quả mà bạn tìm kiếm ở bên dưới đây. @CREATEPAGEINFO@ lang/vi/showrev.txt 0000644 00000000075 15233462216 0010347 0 ustar 00 **Đây là một phiên bản cũ của tài liệu!** ---- lang/vi/stopwords.txt 0000644 00000001056 15233462216 0010716 0 ustar 00 # Đây là danh sách các từ mà người lập chỉ mục bỏ qua, mỗi từ một dòng # Khi bạn sửa đổi tập tin này, hãy chắc chắn sử dụng các kết thúc dòng UNIX (dòng đơn mới) # Không cần bao gồm các từ ngắn hơn 3 ký tự - dù sao chúng cũng bị bỏ qua # Danh sách này dựa trên những cái được tìm thấy tại http://www.ranks.nl/stopwords/ about are as an and you your them their com for from into if in is it how of on or that the this to was what when where who will with und the www lang/vi/subscr_single.txt 0000644 00000001127 15233462216 0011513 0 ustar 00 Chào! Trang @PAGE@ trong wiki @TITLE@ đã được thay đổi. Dưới đây là những thay đổi: -------------------------------------------------------- @DIFF@ -------------------------------------------------------- Thành viên : @USER@ Tóm lược sửa đổi : @SUMMARY@ Phiên bản cũ : @OLDPAGE@ Phiên bản mới : @NEWPAGE@ Ngày của phiên bản mới: @DATE@ Để hủy thông báo trang, đăng nhập vào wiki tại @DOKUWIKIURL@ sau đó truy cập @SUBSCRIBE@ và hủy đăng ký trang và/hoặc thay đổi không gian tên. lang/vi/jquery.ui.datepicker.js 0000644 00000002517 15233462216 0012517 0 ustar 00 /* Vietnamese initialisation for the jQuery UI date picker plugin. */ /* Translated by Le Thanh Huy (lthanhhuy@cit.ctu.edu.vn). */ ( function( factory ) { if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define( [ "../widgets/datepicker" ], factory ); } else { // Browser globals factory( jQuery.datepicker ); } }( function( datepicker ) { datepicker.regional.vi = { closeText: "Đóng", prevText: "<Trước", nextText: "Tiếp>", currentText: "Hôm nay", monthNames: [ "Tháng Một", "Tháng Hai", "Tháng Ba", "Tháng Tư", "Tháng Năm", "Tháng Sáu", "Tháng Bảy", "Tháng Tám", "Tháng Chín", "Tháng Mười", "Tháng Mười Một", "Tháng Mười Hai" ], monthNamesShort: [ "Tháng 1", "Tháng 2", "Tháng 3", "Tháng 4", "Tháng 5", "Tháng 6", "Tháng 7", "Tháng 8", "Tháng 9", "Tháng 10", "Tháng 11", "Tháng 12" ], dayNames: [ "Chủ Nhật", "Thứ Hai", "Thứ Ba", "Thứ Tư", "Thứ Năm", "Thứ Sáu", "Thứ Bảy" ], dayNamesShort: [ "CN", "T2", "T3", "T4", "T5", "T6", "T7" ], dayNamesMin: [ "CN", "T2", "T3", "T4", "T5", "T6", "T7" ], weekHeader: "Tu", dateFormat: "dd/mm/yy", firstDay: 0, isRTL: false, showMonthAfterYear: false, yearSuffix: "" }; datepicker.setDefaults( datepicker.regional.vi ); return datepicker.regional.vi; } ) ); lang/vi/conflict.txt 0000644 00000000766 15233462216 0010462 0 ustar 00 ====== Có phiên bản mới hơn tồn tại ====== Một phiên bản mới hơn của tài liệu bạn sửa đổi tồn tại. Điều này xảy ra khi một thành viên khác thay đổi tài liệu trong khi bạn đang sửa đổi nó. Kiểm tra khác biệt được hiển thị dưới đây, sau đó hãy quyết định giữ phiên bản nào. Nếu bạn chọn ''Lưu'', phiên bản của bạn sẽ được lưu. Nhấn ''Hủy bỏ'' để giữ phiên bản hiện tại. lang/vi/lang.php 0000644 00000064663 15233462216 0007560 0 ustar 00 <?php /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * * @author Thien Hau <thienhau.9a14@gmail.com> * @author James Do <jdo@myrealbox.com> */ $lang['encoding'] = 'utf-8'; $lang['direction'] = 'ltr'; $lang['doublequoteopening'] = '“'; $lang['doublequoteclosing'] = '”'; $lang['singlequoteopening'] = '‘'; $lang['singlequoteclosing'] = '’'; $lang['apostrophe'] = '’'; $lang['btn_edit'] = 'Sửa đổi trang này'; $lang['btn_source'] = 'Xem mã nguồn trang'; $lang['btn_show'] = 'Xem trang'; $lang['btn_create'] = 'Tạo trang này'; $lang['btn_search'] = 'Tìm kiếm'; $lang['btn_save'] = 'Lưu'; $lang['btn_preview'] = 'Xem trước'; $lang['btn_top'] = 'Quay lên trên'; $lang['btn_newer'] = '<< mới hơn'; $lang['btn_older'] = 'cũ hơn >>'; $lang['btn_revs'] = 'Phiên bản cũ'; $lang['btn_recent'] = 'Thay đổi gần đây'; $lang['btn_upload'] = 'Tải lên'; $lang['btn_cancel'] = 'Hủy bỏ'; $lang['btn_index'] = 'Sơ đồ trang web'; $lang['btn_secedit'] = 'Sửa đổi'; $lang['btn_login'] = 'Đăng nhập'; $lang['btn_logout'] = 'Đăng xuất'; $lang['btn_admin'] = 'Quản lý'; $lang['btn_update'] = 'Cập nhật'; $lang['btn_delete'] = 'Xóa'; $lang['btn_back'] = 'Quay lại'; $lang['btn_backlink'] = 'Liên kết đến đây'; $lang['btn_subscribe'] = 'Quản lý đăng ký'; $lang['btn_profile'] = 'Cập nhật hồ sơ'; $lang['btn_reset'] = 'Đặt lại'; $lang['btn_resendpwd'] = 'Đặt mật khẩu mới'; $lang['btn_draft'] = 'Sửa đổi bản nháp'; $lang['btn_recover'] = 'Phục hồi bản nháp'; $lang['btn_draftdel'] = 'Xóa bản nháp'; $lang['btn_revert'] = 'Phục hồi'; $lang['btn_register'] = 'Đăng ký'; $lang['btn_apply'] = 'Áp dụng'; $lang['btn_media'] = 'Quản lý phương tiện'; $lang['btn_deleteuser'] = 'Xóa tài khoản của tôi'; $lang['btn_img_backto'] = 'Quay lại %s'; $lang['btn_mediaManager'] = 'Xem trong Quản lý phương tiện'; $lang['loggedinas'] = 'Đã đăng nhập vói tên:'; $lang['user'] = 'Tên thành viên'; $lang['pass'] = 'Mật khẩu'; $lang['newpass'] = 'Mật khẩu mới'; $lang['oldpass'] = 'Xác nhận mật khẩu hiện tại'; $lang['passchk'] = 'lần nữa'; $lang['remember'] = 'Nhớ tôi'; $lang['fullname'] = 'Tên thật'; $lang['email'] = 'Thư điện tử'; $lang['profile'] = 'Hồ sơ thành viên'; $lang['badlogin'] = 'Xin lỗi, tên thành viên hoặc mật khẩu không đúng.'; $lang['badpassconfirm'] = 'Xin lỗi, mật khẩu không đúng'; $lang['minoredit'] = 'Thay đổi nhỏ'; $lang['draftdate'] = 'Bản nháp được lưu tự động lúc'; $lang['nosecedit'] = 'Trang đã được thay đổi trong một thời gian ngắn, thay vào đó thông tin phần đã hết hạn được tải toàn bộ trang.'; $lang['searchcreatepage'] = 'Nếu bạn không tìm thấy những gì bạn đang tìm kiếm, bạn có thể tạo hoặc sửa đổi trang %s, được đặt tên theo truy vấn của bạn.'; $lang['search_fullresults'] = 'Toàn văn kết quả'; $lang['js']['search_toggle_tools'] = 'Chuyển đổi công cụ tìm kiếm'; $lang['js']['willexpire'] = 'Khóa của bạn dùng cho việc sửa đổi trang này sắp hết hạn sau một phút nữa.\nĐể tránh xung đột, hãy sử dụng nút xem trước để đặt lại bộ hẹn giờ khóa.'; $lang['js']['notsavedyet'] = 'Những thay đổi chưa được lưu sẽ bị mất.'; $lang['js']['searchmedia'] = 'Tìm kiếm tập tin'; $lang['js']['keepopen'] = 'Giữ cửa sổ mở trên lựa chọn'; $lang['js']['hidedetails'] = 'Ẩn thông tin chi tiết'; $lang['js']['mediatitle'] = 'Cài đặt liên kết'; $lang['js']['mediadisplay'] = 'Kiểu liên kết'; $lang['js']['mediaalign'] = 'Căn chỉnh'; $lang['js']['mediasize'] = 'Kích thước hình ảnh'; $lang['js']['mediatarget'] = 'Liên kết đến'; $lang['js']['mediaclose'] = 'Đóng'; $lang['js']['mediainsert'] = 'Chèn'; $lang['js']['mediadisplayimg'] = 'Hiển thị hình ảnh.'; $lang['js']['mediadisplaylnk'] = 'Chỉ hiển thị liên kết.'; $lang['js']['mediasmall'] = 'Phiên bản nhỏ'; $lang['js']['mediamedium'] = 'Phiên bản vừa'; $lang['js']['medialarge'] = 'Phiên bản lớn'; $lang['js']['mediaoriginal'] = 'Phiên bản gốc'; $lang['js']['medialnk'] = 'Liên kết tới trang chi tiết'; $lang['js']['mediadirect'] = 'Liên kết trực tiếp tới bản gốc'; $lang['js']['medianolnk'] = 'Không liên kết'; $lang['js']['medianolink'] = 'Không liên kết đến hình ảnh'; $lang['js']['medialeft'] = 'Căn chỉnh hình ảnh sang trái.'; $lang['js']['mediaright'] = 'Căn chỉnh hình ảnh sang phải.'; $lang['js']['mediacenter'] = 'Căn chỉnh hình ảnh ra giữa.'; $lang['js']['medianoalign'] = 'Không căn chỉnh.'; $lang['js']['nosmblinks'] = 'Liên kết đến Windows shares chỉ hoạt động trong Microsoft Internet Explorer.\nBạn vẫn có thể sao chép và dán liên kết.'; $lang['js']['linkwiz'] = 'Thuật sĩ liên kết'; $lang['js']['linkto'] = 'Liên kết đến:'; $lang['js']['del_confirm'] = 'Muốn xóa (các) mục đã chọn?'; $lang['js']['restore_confirm'] = 'Sẵn sàng phục hồi phiên bản này?'; $lang['js']['media_diff'] = 'Xem khác biệt:'; $lang['js']['media_diff_both'] = 'Cạnh nhau'; $lang['js']['media_diff_opacity'] = 'Sáng qua'; $lang['js']['media_diff_portions'] = 'Vuốt'; $lang['js']['media_select'] = 'Chọn tập tin…'; $lang['js']['media_upload_btn'] = 'Tải lên'; $lang['js']['media_done_btn'] = 'Xong'; $lang['js']['media_drop'] = 'Kéo tập tin vào đây để tải lên'; $lang['js']['media_cancel'] = 'xóa'; $lang['js']['media_overwrt'] = 'Ghi đè các tập tin hiện có'; $lang['search_exact_match'] = 'Khớp chính xác'; $lang['search_starts_with'] = 'Bắt đầu với'; $lang['search_ends_with'] = 'Kết thúc bằng'; $lang['search_contains'] = 'Chứa'; $lang['search_custom_match'] = 'Tùy chỉnh'; $lang['search_any_ns'] = 'Mọi không gian tên'; $lang['search_any_time'] = 'Mọi lúc'; $lang['search_past_7_days'] = 'Tuần trước'; $lang['search_past_month'] = 'Tháng trước'; $lang['search_past_year'] = 'Năm trước'; $lang['search_sort_by_hits'] = 'Sắp xếp theo lượt truy cập'; $lang['search_sort_by_mtime'] = 'Sắp xếp theo sửa đổi cuối'; $lang['regmissing'] = 'Xin lỗi, bạn cần điền vào tất cả các trường'; $lang['reguexists'] = 'Xin lỗi, thành viên có thông tin đăng nhập này đã tồn tại.'; $lang['regsuccess'] = 'Thành viên đã được tạo và mật khẩu đã được gửi qua thư điện tử.'; $lang['regsuccess2'] = 'Thành viên đã được tạo.'; $lang['regfail'] = 'Không thể tạo thành viên.'; $lang['regmailfail'] = 'Có vẻ như đã xảy ra lỗi khi gửi thư mật khẩu. Vui lòng liên hệ với quản trị viên!'; $lang['regbadmail'] = 'Địa chỉ thư điện tử được cung cấp có vẻ không hợp lệ - nếu bạn nghĩ đây là lỗi, hãy liên hệ với quản trị viên'; $lang['regbadpass'] = 'Hai mật khẩu đã nhập không giống nhau, vui lòng thử lại.'; $lang['regpwmail'] = 'Mật khẩu DokuWiki của bạn'; $lang['reghere'] = 'Bạn chưa có tài khoản? Hãy lấy một cái'; $lang['profna'] = 'Wiki này không hỗ trợ sửa đổi hồ sơ'; $lang['profnochange'] = 'Không có thay đổi, không có gì để làm.'; $lang['profnoempty'] = 'Không được bỏ trống tên hoặc địa chỉ thư điện tử.'; $lang['profchanged'] = 'Cập nhật hồ sơ thành viên thành công.'; $lang['profnodelete'] = 'Wiki này không hỗ trợ xóa thành viên'; $lang['profdeleteuser'] = 'Xóa tài khoản'; $lang['profdeleted'] = 'Tài khoản thành viên của bạn đã bị xóa khỏi wiki này'; $lang['profconfdelete'] = 'Tôi muốn xóa tài khoản của tôi khỏi wiki này. <br/> Hành động này không thể hoàn tác.'; $lang['profconfdeletemissing'] = 'Hộp kiểm xác nhận chưa được đánh dấu'; $lang['proffail'] = 'Hồ sơ thành viên chưa được cập nhật.'; $lang['pwdforget'] = 'Bạn quên mật khẩu? Thử một cái mới'; $lang['resendna'] = 'Wiki này không hỗ trợ gửi lại mật khẩu.'; $lang['resendpwd'] = 'Đặt mật khẩu mới cho'; $lang['resendpwdmissing'] = 'Xin lỗi, bạn phải điền vào tất cả các trường.'; $lang['resendpwdnouser'] = 'Xin lỗi, chúng tôi không thể tìm thấy thành viên này trong cơ sở dữ liệu của chúng tôi.'; $lang['resendpwdbadauth'] = 'Xin lỗi, mã xác thực này không hợp lệ. Hãy chắc chắn rằng bạn đã sử dụng liên kết xác nhận đầy đủ.'; $lang['resendpwdconfirm'] = 'Một liên kết xác nhận đã được gửi qua thư điện tử.'; $lang['resendpwdsuccess'] = 'Mật khẩu mới của bạn đã được gửi qua thư điện tử.'; $lang['license'] = 'Trừ khi có ghi chú khác, nội dung trên wiki này được cấp phép theo giấy phép sau:'; $lang['licenseok'] = 'Lưu ý: Bằng cách sửa đổi trang này, bạn đồng ý cấp phép cho nội dung của mình theo giấy phép sau:'; $lang['searchmedia'] = 'Tìm kiếm tên tập tin:'; $lang['searchmedia_in'] = 'Tìm kiếm trong %s'; $lang['txt_upload'] = 'Chọn tập tin để tải lên:'; $lang['txt_filename'] = 'Tải lên dưới dạng (tùy chọn):'; $lang['txt_overwrt'] = 'Ghi đè tập tin hiện có'; $lang['maxuploadsize'] = 'Tải lên tối đa. %s mỗi tập tin.'; $lang['lockedby'] = 'Hiện đang bị khóa bởi:'; $lang['lockexpire'] = 'Khóa hết hạn vào lúc:'; $lang['rssfailed'] = 'Đã xảy ra lỗi khi tìm nạp nguồn cấp dữ liệu này:'; $lang['nothingfound'] = 'Không có gì được tìm thấy.'; $lang['mediaselect'] = 'Tập tin phương tiện'; $lang['uploadsucc'] = 'Tải lên thành công'; $lang['uploadfail'] = 'Tải lên không thành công. Có thể không đủ quyền?'; $lang['uploadwrong'] = 'Tải lên bị từ chối. Phần mở rộng tập tin này bị cấm!'; $lang['uploadexist'] = 'Tập tin đã tồn tại. Không có gì được thực hiện.'; $lang['uploadbadcontent'] = 'Nội dung được tải lên không khớp với phần tập tin mở rộng %s.'; $lang['uploadspam'] = 'Việc tải lên đã bị chặn bởi danh sách đen về spam.'; $lang['uploadxss'] = 'Việc tải lên đã bị chặn do nội dung độc hại.'; $lang['uploadsize'] = 'Tập tin đã tải lên quá lớn. (tối đa %s)'; $lang['deletesucc'] = 'Đã xóa tập tin "%s".'; $lang['deletefail'] = 'Không thể xóa "%s" - kiểm tra quyền.'; $lang['mediainuse'] = 'Không thể xóa tập tin "%s" - nó vẫn đang được sử dụng.'; $lang['namespaces'] = 'Không gian tên'; $lang['mediafiles'] = 'Tập tin có sẵn trong'; $lang['accessdenied'] = 'Bạn không được phép xem trang này.'; $lang['mediausage'] = 'Sử dụng cú pháp sau để tham chiếu tập tin này:'; $lang['mediaview'] = 'Xem tập tin gốc'; $lang['mediaroot'] = 'gốc'; $lang['mediaupload'] = 'Tải một tập tin vào không gian tên hiện tại ở đây. Để tạo không gian tên con, hãy thêm chúng vào tên tập tin của bạn được phân tách bằng dấu hai chấm sau khi bạn chọn tập tin. Tập tin cũng có thể được chọn bằng cách kéo và thả.'; $lang['mediaextchange'] = 'Đã thay đổi phần mở rộng tập tin từ .%s thành .%s!'; $lang['reference'] = 'Tài liệu tham khảo cho'; $lang['ref_inuse'] = 'Không thể xóa tập tin này vì nó vẫn được sử dụng ở các trang sau:'; $lang['ref_hidden'] = 'Một số tài liệu tham khảo nằm trên các trang bạn không có quyền đọc'; $lang['hits'] = 'Lượt truy cập'; $lang['quickhits'] = 'Tên trang trùng khớp'; $lang['toc'] = 'Mục lục'; $lang['current'] = 'hiện tại'; $lang['yours'] = 'Phiên bản của bạn'; $lang['diff'] = 'Xem khác biệt với phiên bản hiện tại'; $lang['diff2'] = 'Xem khác biệt giữa các phiên bản được chọn'; $lang['difflink'] = 'Liên kết đến bản xem so sánh này'; $lang['diff_type'] = 'Xem khác biệt:'; $lang['diff_inline'] = 'Nội tuyến'; $lang['diff_side'] = 'Cạnh nhau'; $lang['diffprevrev'] = 'Phiên bản trước'; $lang['diffnextrev'] = 'Phiên bản sau'; $lang['difflastrev'] = 'Phiên bản cuối'; $lang['diffbothprevrev'] = 'Phiên bản trước của cả hai bên'; $lang['diffbothnextrev'] = 'Phiên bản sau của cả hai bên'; $lang['line'] = 'Dòng'; $lang['breadcrumb'] = 'Trang đã xem:'; $lang['youarehere'] = 'Bạn đang ở đây:'; $lang['lastmod'] = 'Sửa đổi lần cuối:'; $lang['by'] = 'bởi'; $lang['deleted'] = 'bị xoá'; $lang['created'] = 'đã tạo'; $lang['restored'] = 'đã khôi phục phiên bản cũ (%s)'; $lang['external_edit'] = 'sửa đổi bên ngoài'; $lang['summary'] = 'Tóm lược sửa đổi'; $lang['noflash'] = 'Cần có <a href="http://www.adobe.com/products/flashplayer/">Adobe Flash Plugin</a> mới có thể xem được nội dung này.'; $lang['download'] = 'Tải xuống đoạn trích'; $lang['tools'] = 'Công cụ'; $lang['user_tools'] = 'Công cụ thành viên'; $lang['site_tools'] = 'Công cụ trang web'; $lang['page_tools'] = 'Công cụ trang'; $lang['skip_to_content'] = 'đi đến nội dung'; $lang['sidebar'] = 'Thanh bên'; $lang['mail_newpage'] = 'trang đã thêm:'; $lang['mail_changed'] = 'trang đã thay đổi:'; $lang['mail_subscribe_list'] = 'các trang đã thay đổi trong không gian tên:'; $lang['mail_new_user'] = 'thành viên mới:'; $lang['mail_upload'] = 'tập tin đã được tải lên:'; $lang['changes_type'] = 'Xem thay đổi của'; $lang['pages_changes'] = 'Trang'; $lang['media_changes'] = 'Tập tin phương tiện'; $lang['both_changes'] = 'Cả trang và tập tin phương tiện'; $lang['qb_bold'] = 'Chữ đậm'; $lang['qb_italic'] = 'Chữ nghiêng'; $lang['qb_underl'] = 'Chữ gạch chân'; $lang['qb_code'] = 'Chữ đơn cách'; $lang['qb_strike'] = 'Gạch ngang chữ'; $lang['qb_h1'] = 'Đề mục cấp 1'; $lang['qb_h2'] = 'Đề mục cấp 2'; $lang['qb_h3'] = 'Đề mục cấp 3'; $lang['qb_h4'] = 'Đề mục cấp 4'; $lang['qb_h5'] = 'Đề mục cấp 5'; $lang['qb_h'] = 'Đề mục'; $lang['qb_hs'] = 'Chọn đề mục'; $lang['qb_hplus'] = 'Đề mục cao hơn'; $lang['qb_hminus'] = 'Đề mục thấp hơn'; $lang['qb_hequal'] = 'Đề mục cùng cấp'; $lang['qb_link'] = 'Liên kết nội bộ'; $lang['qb_extlink'] = 'Liên kết ngoài'; $lang['qb_hr'] = 'Thanh ngang'; $lang['qb_ol'] = 'Mục danh sách đánh số'; $lang['qb_ul'] = 'Mục danh sách không đánh số'; $lang['qb_media'] = 'Thêm hình ảnh và các tập tin khác (mở trong một cửa sổ mới)'; $lang['qb_sig'] = 'Chèn chữ ký'; $lang['qb_smileys'] = 'Biểu tượng mặt cười'; $lang['qb_chars'] = 'Ký tự đặc biệt'; $lang['upperns'] = 'nhảy đến không gian tên mẹ'; $lang['metaedit'] = 'Sửa đổi siêu dữ liệu'; $lang['metasaveerr'] = 'Viết siêu dữ liệu không thành công'; $lang['metasaveok'] = 'Đã lưu siêu dữ liệu'; $lang['img_title'] = 'Tiêu đề:'; $lang['img_caption'] = 'Chú thích:'; $lang['img_date'] = 'Ngày:'; $lang['img_fname'] = 'Tên tập tin:'; $lang['img_fsize'] = 'Kích thước:'; $lang['img_artist'] = 'Người chụp:'; $lang['img_copyr'] = 'Bản quyền:'; $lang['img_format'] = 'Định dạng:'; $lang['img_camera'] = 'Máy ảnh:'; $lang['img_keywords'] = 'Từ khóa:'; $lang['img_width'] = 'Chiều rộng:'; $lang['img_height'] = 'Chiều cao:'; $lang['subscr_subscribe_success'] = 'Đã thêm %s vào danh sách đăng ký cho %s'; $lang['subscr_subscribe_error'] = 'Có lỗi khi thêm %s vào danh sách đăng ký cho %s'; $lang['subscr_subscribe_noaddress'] = 'Không có địa chỉ liên quan nào đến thông tin đăng nhập của bạn, bạn không thể thêm vào danh sách đăng ký'; $lang['subscr_unsubscribe_success'] = 'Đã xóa %s khỏi danh sách đăng ký cho %s'; $lang['subscr_unsubscribe_error'] = 'Có lỗi khi xóa %s khỏi danh sách đăng ký cho %s'; $lang['subscr_already_subscribed'] = '%s đã được đăng ký cho %s'; $lang['subscr_not_subscribed'] = '%s chưa được đăng ký cho %s'; $lang['subscr_m_not_subscribed'] = 'Bạn hiện chưa đăng ký vào trang hoặc không gian tên hiện tại.'; $lang['subscr_m_new_header'] = 'Thêm đăng ký'; $lang['subscr_m_current_header'] = 'Đăng ký hiện tại'; $lang['subscr_m_unsubscribe'] = 'Hủy đăng ký'; $lang['subscr_m_subscribe'] = 'Đăng ký'; $lang['subscr_m_receive'] = 'Nhận'; $lang['subscr_style_every'] = 'thư điện tử về mọi thay đổi'; $lang['subscr_style_digest'] = 'Thông báo thư điện tử về các thay đổi cho mỗi trang (mỗi %.2f ngày)'; $lang['subscr_style_list'] = 'danh sách các trang đã thay đổi kể từ thư điện tử cuối cùng (mỗi %.2f ngày)'; $lang['authtempfail'] = 'Xác thực thành viên tạm thời không có sẵn. Nếu tình trạng này vẫn còn, vui lòng thông báo cho Quản trị viên Wiki của bạn.'; $lang['i_chooselang'] = 'Chọn ngôn ngữ của bạn'; $lang['i_installer'] = 'Trình cài đặt DokuWiki'; $lang['i_wikiname'] = 'Tên Wiki'; $lang['i_enableacl'] = 'Kích hoạt Danh sách kiểm soát truy cập (ACL) (khuyến nghị)'; $lang['i_superuser'] = 'Siêu thành viên'; $lang['i_problems'] = 'Trình cài đặt tìm thấy một số vấn đề, được chỉ ra bên dưới. Bạn không thể tiếp tục cho đến khi bạn đã sửa chúng.'; $lang['i_modified'] = 'Vì lý do bảo mật, tập lệnh này sẽ chỉ hoạt động với bản cài đặt Dokuwiki mới và chưa được sửa đổi. Bạn nên trích xuất lại các tập tin từ gói đã tải xuống hoặc tham khảo <a href="http://dokuwiki.org/install">Hướng dẫn cài đặt Dokuwiki</a> đầy đủ'; $lang['i_funcna'] = 'Hàm PHP <code>%s</code> không có sẵn. Có lẽ nhà cung cấp dịch vụ lưu trữ của bạn đã vô hiệu hóa nó vì một số lý do?'; $lang['i_disabled'] = 'Nó đã bị vô hiệu hóa bởi nhà cung cấp của bạn.'; $lang['i_funcnmail'] = '<b>Lưu ý:</b> Không có sẵn hàm PHP mail. %s Nếu nó vẫn không có sẵn, bạn có thể cài đặt <a href="http://dokuwiki.org/plugins/smtp">smtp plugin</a>.'; $lang['i_phpver'] = 'Phiên bản PHP <code>%s</code> hiện taị thấp hơn mức <code>%s</code> cần thiết. Bạn cần nâng cấp cài đặt PHP của bạn.'; $lang['i_mbfuncoverload'] = 'mbopes.func_overload phải bị vô hiệu trong php.ini để chạy DokuWiki.'; $lang['i_urandom'] = 'DokuWiki không thể tạo số mật mã an toàn cho cookie. Bạn có thể muốn kiểm tra cài đặt open_basingir trong php.ini để truy cập <code>/dev/urandom</code> thích hợp.'; $lang['i_permfail'] = 'DokuWiki không thể ghi được <code>%s</code>. Bạn cần sửa đổi các thiết lập quyền của đường dẫn này!'; $lang['i_confexists'] = '<code>%s</code> đã tồn tại'; $lang['i_writeerr'] = 'Không thể tạo <code>%s</code>. Bạn sẽ cần kiểm tra quyền truy cập thư mục/tập tin và tạo tập tin thủ công.'; $lang['i_badhash'] = 'Không thể công nhận hoặc sửa đổi dokuwiki.php (hash=<code>%s</code>)'; $lang['i_badval'] = '<code>%s</code> - giá trị bất hợp pháp hoặc trống'; $lang['i_success'] = 'Đã hoàn thành việc cấu hình thành công. Bạn có thể xóa tập tin install.php ngay bây giờ. Tiếp tục với <a href="doku.php?id=wiki:welcome">DokuWiki mới của bạn</a>'; $lang['i_failure'] = 'Một số lỗi xảy ra trong khi viết tập tin cấu hình. Bạn có thể cần phải sửa chúng thủ công trước khi bạn có thể sử dụng <a href="doku.php?id=wiki:welcome">DokuWiki mới của bạn</a>.'; $lang['i_policy'] = 'Chính sách ACL (Danh sách kiểm soát truy cập) ban đầu'; $lang['i_pol0'] = 'Wiki mở (bất kỳ ai cũng có thể đọc, viết, tải lên)'; $lang['i_pol1'] = 'Wiki công cộng (bất kỳ ai cũng có thể xem, thành viên đã đăng ký có thể viết và tải lên)'; $lang['i_pol2'] = 'Wiki đóng (chỉ thành viên đã đăng ký mới có thể đọc, viết, tải lên)'; $lang['i_allowreg'] = 'Cho phép người dùng tự đăng ký'; $lang['i_retry'] = 'Thử lại'; $lang['i_license'] = 'Vui lòng chọn giấy phép bạn muốn đặt nội dung của bạn dưới:'; $lang['i_license_none'] = 'Không hiển thị bất kỳ thông tin giấy phép nào'; $lang['i_pop_field'] = 'Vui lòng giúp chúng tôi cải thiện trải nghiệm DokuWiki:'; $lang['i_pop_label'] = 'Mỗi tháng một lần, gửi dữ liệu sử dụng ẩn danh đến các nhà phát triển DokuWiki'; $lang['recent_global'] = 'Bạn hiện đang xem những thay đổi bên trong không gian tên <b>%s</b>. Bạn cũng có thể <a href="%s">xem những thay đổi gần đây trên toàn bộ wiki</a>.'; $lang['years'] = '%d năm trước'; $lang['months'] = '%d tháng trước'; $lang['weeks'] = '%d tuần trước'; $lang['days'] = '%d ngày trước'; $lang['hours'] = '%d giờ trước'; $lang['minutes'] = '%d phút trước'; $lang['seconds'] = '%d giây trước'; $lang['wordblock'] = 'Thay đổi của bạn không được lưu vì nó chứa văn bản bị chặn (spam).'; $lang['media_uploadtab'] = 'Tải lên'; $lang['media_searchtab'] = 'Tìm kiếm'; $lang['media_file'] = 'Tập tin'; $lang['media_viewtab'] = 'Xem'; $lang['media_edittab'] = 'Sửa đổi'; $lang['media_historytab'] = 'Lịch sử'; $lang['media_list_thumbs'] = 'Hình thu nhỏ'; $lang['media_list_rows'] = 'Dòng'; $lang['media_sort_name'] = 'Tên'; $lang['media_sort_date'] = 'Ngày'; $lang['media_namespaces'] = 'Chọn không gian tên'; $lang['media_files'] = 'Tập tin trong %s'; $lang['media_upload'] = 'Tải lên %s'; $lang['media_search'] = 'Tìm kiếm trong %s'; $lang['media_view'] = '%s'; $lang['media_viewold'] = '%s ở %s'; $lang['media_edit'] = 'Sửa đổi %s'; $lang['media_history'] = 'Lịch sử của %s'; $lang['media_meta_edited'] = 'đã sửa siêu dữ liệu'; $lang['media_perm_read'] = 'Xin lỗi, bạn không có đủ quyền để đọc tập tin.'; $lang['media_perm_upload'] = 'Xin lỗi, bạn không đủ quyền để tải tập tin lên.'; $lang['media_update'] = 'Tải lên phiên bản mới'; $lang['media_restore'] = 'Phục hồi phiên bản này'; $lang['media_acl_warning'] = 'Danh sách này có thể không đầy đủ do các hạn chế về Danh sách kiểm soát truy cập (ACL) và trang ẩn.'; $lang['email_fail'] = 'Thiếu hoặc đã vô hiệu hóa PHP mail(). Những thư điện tử sau không được gửi:'; $lang['currentns'] = 'Không gian tên hiện tại'; $lang['searchresult'] = 'Kết quả tìm kiếm'; $lang['plainhtml'] = 'HTML thuần túy'; $lang['wikimarkup'] = 'Đánh dấu Wiki'; $lang['page_nonexist_rev'] = 'Trang không tồn tại tại %s. Sau đó, nó đã được tạo ở <a href="%s">%s</a>.'; $lang['unable_to_parse_date'] = 'Không thể phân tích cú pháp tại tham số "%s".'; $lang['email_signature_text'] = 'Thư này được tạo bởi DokuWiki tại @DOKUWIKIURL@'; lang/vi/read.txt 0000644 00000000304 15233462216 0007560 0 ustar 00 Trang này hiện chỉ có thể đọc. Bạn có thể xem mã nguồn, nhưng không thể thay đổi nó. Hỏi quản trị viên của bạn nếu bạn nghĩ rằng điều này là sai. lang/vi/preview.txt 0000644 00000000250 15233462216 0010326 0 ustar 00 ====== Xem trước ====== Đây là một bản xem trước của văn bản của bạn sẽ trông như thế nào. **Hãy nhớ: Nó vẫn chưa được lưu**! lang/vi/updateprofile.txt 0000644 00000000306 15233462216 0011512 0 ustar 00 ====== Cập nhật hồ sơ tài khoản của bạn ====== Bạn chỉ cần hoàn thành những trường bạn muốn thay đổi. Bạn không thể thay đổi tên thành viên của bạn. lang/vi/onceexisted.txt 0000644 00000000463 15233462216 0011165 0 ustar 00 ======= Trang này không còn tồn tại ====== Bạn đã vào một liên kết đến một trang không còn tồn tại. Bạn có thể kiểm tra danh sách [[?do=revisions|phiên bản cũ]] để xem khi nào và tại sao nó bị xóa, truy cập các phiên bản cũ hoặc khôi phục nó. lang/vi/subscr_digest.txt 0000644 00000000716 15233462216 0011514 0 ustar 00 Chào! Trang @PAGE@ trong wiki @TITLE@ đã được thay đổi. Dưới đây là những thay đổi: -------------------------------------------------------- @DIFF@ -------------------------------------------------------- Phiên bản cũ: @OLDPAGE@ Phiên bản mới: @NEWPAGE@ Để hủy thông báo trang, đăng nhập vào wiki tại @DOKUWIKIURL@ sau đó truy cập @SUBSCRIBE@ và hủy đăng ký trang và/hoặc thay đổi không gian tên. lang/vi/newpage.txt 0000644 00000000343 15233462216 0010276 0 ustar 00 ====== Chưa có đề tài này ====== Bạn đã truy cập vào một liên kết đến một đề tài chưa tồn tại. Nếu quyền cho phép, bạn có thể tạo nó bằng cách nhấp vào **Tạo trang này**. lang/vi/resendpwd.txt 0000644 00000000474 15233462216 0010650 0 ustar 00 ====== Gửi mật khẩu mới ====== Vui lòng nhập tên thành viên của bạn vào mẫu dưới đây để yêu cầu một mật khẩu mới cho tài khoản của bạn trong wiki này. Một liên kết xác nhận sẽ được gửi đến địa chỉ thư điện tử đã đăng ký của bạn. lang/vi/mailtext.txt 0000644 00000001145 15233462216 0010500 0 ustar 00 Một trang trên DokuWiki của bạn vừa được thêm hoặc thay đổi. Đây là những thông tin chi tiết: Trình duyệt : @BROWSER@ Địa chỉ IP : @IPADDRESS@ Tên máy chủ : @HOSTNAME@ Phiên bản cũ : @OLDPAGE@ Phiên bản mới : @NEWPAGE@ Ngày của phiên bản mới: @DATE@ Tóm lược sửa đổi : @SUMMARY@ Thành viên : @USER@ Có thể có những thay đổi mới hơn sau khi phiên bản này. Nếu điều này xảy ra, một tin nhắn sẽ được hiển thị trên đầu trang rev. @DIFF@ lang/vi/backlinks.txt 0000644 00000000163 15233462216 0010611 0 ustar 00 ====== Liên kết đến đây ====== Đây là danh sách các trang có liên kết đến trang hiện tại. lang/vi/login.txt 0000644 00000000300 15233462216 0007751 0 ustar 00 ====== Đăng nhập ====== Bạn hiện chưa đăng nhập! Nhập thông tin xác thực của bạn bên dưới để đăng nhập. Bạn cần kích hoạt cookie để đăng nhập. lang/vi/edit.txt 0000644 00000000505 15233462216 0007575 0 ustar 00 Sửa đổi trang này và nhấn ''Lưu''. Xem [[wiki:syntax]] cho cú pháp Wiki. Vui lòng chỉ sửa đổi trang nếu bạn có thể **cải thiện** nó. Nếu bạn muốn thử một số thứ, hãy học cách thực hiện những bước đầu tiên của bạn tại [[playground:playground|chỗ thử]]. lang/vi/diff.txt 0000644 00000000140 15233462216 0007553 0 ustar 00 ====== Khác biệt ====== Đây là những khác biệt giữa hai phiên bản của trang. lang/vi/editrev.txt 0000644 00000000234 15233462216 0010311 0 ustar 00 **Bạn đã tải một phiên bản cũ của tài liệu!** Nếu bạn lưu nó, bạn sẽ tạo một phiên bản mới với dữ liệu này. ---- lang/uz/index.txt 0000644 00000000015 15233462216 0007773 0 ustar 00 Sayt xaritasi lang/uz/denied.txt 0000644 00000000020 15233462216 0010110 0 ustar 00 Ruxsat etilmagan lang/uz/admin.txt 0000644 00000000017 15233462216 0007756 0 ustar 00 Administratsiya lang/uz/adminplugins.txt 0000644 00000000024 15233462216 0011356 0 ustar 00 Qo'shimcha plaginlar lang/uz/conflict.txt 0000644 00000000024 15233462216 0010465 0 ustar 00 Yangi versiya mavjud lang/uz/lang.php 0000644 00000002031 15233462216 0007555 0 ustar 00 <?php /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * * @author Bakhromjon Soliev <bahromjonsoliev@gmail.com> */ $lang['btn_edit'] = 'Ushbu sahifani tahrirlash'; $lang['btn_show'] = 'Sahifani ko\'rsatish'; $lang['btn_create'] = 'Sahifa yaratish'; $lang['btn_search'] = 'Izlash'; $lang['btn_save'] = 'Saqlash'; $lang['btn_preview'] = 'Namoyish'; $lang['btn_top'] = 'Yuqoriga'; $lang['btn_revs'] = 'Avvalgi versiyalari'; $lang['btn_recent'] = 'So\'nggi o\'zgarishlar'; $lang['btn_upload'] = 'Yuklash'; $lang['btn_cancel'] = 'Bekor qilish'; $lang['btn_index'] = 'Sayt xaritasi'; $lang['btn_secedit'] = 'Tahrirlash'; $lang['btn_delete'] = 'O\'chirish'; $lang['btn_back'] = 'Ortga'; $lang['btn_profile'] = 'Profilni yangilash'; $lang['btn_resendpwd'] = 'Yangi parol o\'rnatish'; $lang['btn_draft'] = 'Qoralamani tahrirlash'; lang/uz/diff.txt 0000644 00000000010 15233462216 0007567 0 ustar 00 Farqlari lang/fo/index.txt 0000644 00000000172 15233462216 0007745 0 ustar 00 ====== Evnisyvirlit ====== Hetta er eitt yvirlit yvur øll atkomandi skjøl, flokka eftir [[doku>namespaces|navnarúm]]. lang/fo/register.txt 0000644 00000000425 15233462216 0010463 0 ustar 00 ====== Upprætta eina wiki-konti ====== Fylla út niðanfyrista skema fyri at upprætta eina konti í hesu wiki. Minst til at nýta eina **galdandi t-post-adressu** - títt loyniorð verður sent til tín. Títt brúkaranavn skal verða galdandi [[doku>pagename|skjalanavn]]. lang/fo/denied.txt 0000644 00000000114 15233462216 0010062 0 ustar 00 ====== Atgongd nokta! ====== Tú hevur ikki rættindi til at halda áfram. lang/fo/revisions.txt 0000644 00000000372 15233462216 0010661 0 ustar 00 ====== Gamlar útgávur ====== Her eru tær gomlu útgávurnar av hesum skalinum. Tú kanst venda aftur til eina eldri útgávu av skjalinum við at velja tað niðanfyri, trýst á **''[Rætta hetta skjal]''** knappin, og til síðst goyma skjali. lang/fo/norev.txt 0000644 00000000303 15233462216 0007763 0 ustar 00 ====== Valda útgávan er ikki til ====== Valda útgávan av skjalinum er ikki til! Trýst á knappin **''[Gamlar útgávur]''** fyri at síggja ein lista yvur gamlar útgávur av hesum skjali. lang/fo/admin.txt 0000644 00000000134 15233462216 0007724 0 ustar 00 ====== Fyrisiting ====== Niðanfyri kanst tú finna eina røð av amboðum til fyrisiting. lang/fo/locked.txt 0000644 00000000271 15233462216 0010077 0 ustar 00 ====== Læst skjal ====== Hetta skjal er fyribils læst av einum øðrum brúkara. Bíða vinarliga til brúkarin er liðugur við at rætta skjali, ella at lásið er fara úr gildi. lang/fo/recent.txt 0000644 00000000107 15233462216 0010114 0 ustar 00 ====== Nýggjar broytingar ====== Fylgjandi skjøl er broytt nýliga: lang/fo/password.txt 0000644 00000000200 15233462216 0010470 0 ustar 00 Hey @FULLNAME@! Her eru tínar brúkaraupplýsingar @TITLE@ at @DOKUWIKIURL@ Brúkaranavn : @LOGIN@ Loyniorð : @PASSWORD@ lang/fo/searchpage.txt 0000644 00000000142 15233462216 0010735 0 ustar 00 ====== Leiting ====== Tú kanst síggja úrslitini av tíni leiting niðanfyri. @CREATEPAGEINFO@ lang/fo/showrev.txt 0000644 00000000063 15233462216 0010332 0 ustar 00 **Hetta er ein gomul útgáva av skjalinum!** ---- lang/fo/stopwords.txt 0000644 00000001405 15233462216 0010702 0 ustar 00 # This is a list of words the indexer ignores, one word per line # When you edit this file be sure to use UNIX line endings (single newline) # No need to include words shorter than 3 chars - these are ignored anyway # This list is based upon the ones found at http://www.ranks.nl/stopwords/ annar báðir eg eingin einhvør eini eitt ella enn fim fleiri flestir frá fyri fyrr fýra góður hann hansara har hendan hennara her hetta hevur hon hvar hvat hvussi hví hvør ikki inn kan koma lítil man maður meira men miðan niður nær næstan næsti nógv nýtt okkurt ongin onki onkur seks sindur sjey smáur stórur større størst sum síggjast tann tað teir tey til tríggir trý tvey tykkara tær tí tín tó tú um undan var vera við yvur átta áðrenn øll www lang/fo/jquery.ui.datepicker.js 0000644 00000002264 15233462216 0012504 0 ustar 00 /* Faroese initialisation for the jQuery UI date picker plugin */ /* Written by Sverri Mohr Olsen, sverrimo@gmail.com */ ( function( factory ) { if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define( [ "../widgets/datepicker" ], factory ); } else { // Browser globals factory( jQuery.datepicker ); } }( function( datepicker ) { datepicker.regional.fo = { closeText: "Lat aftur", prevText: "<Fyrra", nextText: "Næsta>", currentText: "Í dag", monthNames: [ "Januar","Februar","Mars","Apríl","Mei","Juni", "Juli","August","September","Oktober","November","Desember" ], monthNamesShort: [ "Jan","Feb","Mar","Apr","Mei","Jun", "Jul","Aug","Sep","Okt","Nov","Des" ], dayNames: [ "Sunnudagur", "Mánadagur", "Týsdagur", "Mikudagur", "Hósdagur", "Fríggjadagur", "Leyardagur" ], dayNamesShort: [ "Sun","Mán","Týs","Mik","Hós","Frí","Ley" ], dayNamesMin: [ "Su","Má","Tý","Mi","Hó","Fr","Le" ], weekHeader: "Vk", dateFormat: "dd-mm-yy", firstDay: 1, isRTL: false, showMonthAfterYear: false, yearSuffix: "" }; datepicker.setDefaults( datepicker.regional.fo ); return datepicker.regional.fo; } ) ); lang/fo/conflict.txt 0000644 00000000620 15233462216 0010435 0 ustar 00 ====== Ein níggjari útgáva av skjalinum er til ====== Ein nýggjari útgáva av hesum skjalinum er til. Hetta hendur tá fleiri brúkarir rætta í skjalinum samstundis. Eftirkanna tær vístu broytingar nágreiniliga, og avgerð hvat fyri útgávu sum skal goymast. Um tú velur ''Goym'', verður tín útgáva av skalinum goymd. Velur tú ''Angra'' varðveittur tú tí núverandi útgávuna. lang/fo/lang.php 0000644 00000023726 15233462216 0007541 0 ustar 00 <?php /** * faroese language file * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Poul J. Clementsen <poul@diku.dk> * @author Einar Petersen <einar.petersen@gmail.com> */ $lang['encoding'] = 'utf-8'; $lang['direction'] = 'ltr'; $lang['doublequoteopening'] = '"'; $lang['doublequoteclosing'] = '"'; $lang['singlequoteopening'] = '\''; $lang['singlequoteclosing'] = '\''; $lang['apostrophe'] = '\''; $lang['btn_edit'] = 'Rætta hetta skjal'; $lang['btn_source'] = 'Vís keldu'; $lang['btn_show'] = 'Vís skjal'; $lang['btn_create'] = 'Býrja uppá hetta skjal'; $lang['btn_search'] = 'Leita'; $lang['btn_save'] = 'Goym'; $lang['btn_preview'] = 'Forskoðan'; $lang['btn_top'] = 'Aftur til toppin'; $lang['btn_newer'] = '<< undan síða'; $lang['btn_older'] = 'næsta síðe >>'; $lang['btn_revs'] = 'Gamlar útgávur'; $lang['btn_recent'] = 'Nýggj broyting'; $lang['btn_upload'] = 'Legg fílu upp'; $lang['btn_cancel'] = 'Angra'; $lang['btn_index'] = 'Evnisyvirlit'; $lang['btn_secedit'] = 'Rætta'; $lang['btn_login'] = 'Rita inn'; $lang['btn_logout'] = 'Rita út'; $lang['btn_admin'] = 'Admin'; $lang['btn_update'] = 'Dagfør'; $lang['btn_delete'] = 'Strika'; $lang['btn_back'] = 'Aftur'; $lang['btn_backlink'] = 'Ávísingar afturúr'; $lang['btn_subscribe'] = 'Tilmelda broytingar'; $lang['btn_profile'] = 'Dagføra vangamynd'; $lang['btn_reset'] = 'Nullstilla'; $lang['btn_draft'] = 'Broyt kladdu'; $lang['btn_recover'] = 'Endurbygg kladdu'; $lang['btn_draftdel'] = 'Sletta'; $lang['btn_revert'] = 'Endurbygg'; $lang['btn_register'] = 'Melda til'; $lang['loggedinas'] = 'Ritavur inn sum:'; $lang['user'] = 'Brúkaranavn'; $lang['pass'] = 'Loyniorð'; $lang['newpass'] = 'Nýtt loyniorð'; $lang['oldpass'] = 'Vátta núverandi loyniorð'; $lang['passchk'] = 'Endurtak nýtt loyniorð'; $lang['remember'] = 'Minst til loyniorðið hjá mær'; $lang['fullname'] = 'Navn'; $lang['email'] = 'T-postur'; $lang['profile'] = 'Brúkara vangamynd'; $lang['badlogin'] = 'Skeivt brúkaranavn ella loyniorð.'; $lang['minoredit'] = 'Smærri broytingar'; $lang['draftdate'] = 'Goym kladdu sett frá'; $lang['nosecedit'] = 'Hendan síðan var broytt undir tilevnan, brotið var ikki rætt dagfest, heintaði fulla síðu í staðin'; $lang['regmissing'] = 'Tú skalt fylla út øll øki.'; $lang['reguexists'] = 'Hetta brúkaranavn er upptiki.'; $lang['regsuccess'] = 'Tú ert nú stovnavur sum brúkari. Títt loyniorð verður sent til tín í einum T-posti.'; $lang['regsuccess2'] = 'Tú ert nú stovnavur sum brúkari.'; $lang['regmailfail'] = 'Títt loyniorð bleiv ikki sent. Fá vinarliga samband við administratorin.'; $lang['regbadmail'] = 'T-post adressan er ógildig. Fá vinarliga samband við administratorin, um tú heldur at hetta er eitt brek.'; $lang['regbadpass'] = 'Bæði loyniorðini eru ikki eins, royn vinarliga umaftur.'; $lang['regpwmail'] = 'Títt DokuWiki loyniorð'; $lang['reghere'] = 'Upprætta eina DokuWiki-konto her'; $lang['profna'] = 'Tað er ikki møguligt at broyta tína vangamynd í hesu wiki'; $lang['profnochange'] = 'Ongar broytingar, onki tillaga.'; $lang['profnoempty'] = 'Tómt navn ella t-post adressa er ikki loyvt.'; $lang['profchanged'] = 'Brúkara vangamynd dagført rætt.'; $lang['pwdforget'] = 'Gloymt títt loyniorð? Fá eitt nýtt'; $lang['resendna'] = 'Tað er ikki møguligt at fá sent nýtt loyniorð við hesu wiki.'; $lang['resendpwdmissing'] = 'Tú skal filla út øll økir.'; $lang['resendpwdnouser'] = 'Vit kunna ikki finna hendan brúkara í okkara dátagrunni.'; $lang['resendpwdbadauth'] = 'Hald til góðar, hendan góðkenningar kodan er ikki gildug. Kanna eftir at tú nýtti tað fulfíggjaðu góðkenningarleinkjuna'; $lang['resendpwdconfirm'] = 'Ein góðkenningarleinkja er send við e-posti'; $lang['resendpwdsuccess'] = 'Títt nýggja loyniorð er sent við t-posti.'; $lang['license'] = 'Um ikki annað er tilskilað, so er tilfar á hesari wiki loyvt margfaldað undir fylgjandi treytum:'; $lang['licenseok'] = 'Legg til merkis: Við at dagføra hesa síðu samtykkir tú at loyva margfalding av tilfarinum undir fylgjandi treytum:'; $lang['searchmedia'] = 'Leita eftir fíl navn:'; $lang['searchmedia_in'] = 'Leita í %s'; $lang['txt_upload'] = 'Vel tí fílu sum skal leggjast upp:'; $lang['txt_filename'] = 'Sláa inn wikinavn (valfrítt):'; $lang['txt_overwrt'] = 'Yvurskriva verandi fílu'; $lang['lockedby'] = 'Fyribils læst av:'; $lang['lockexpire'] = 'Lásið ferð úr gildi kl.:'; $lang['js']['willexpire'] = 'Títt lás á hetta skjalið ferð úr gildi um ein minnutt.\nTrýst á Forskoðan-knappin fyri at sleppa undan trupulleikum.'; $lang['js']['notsavedyet'] = 'Tað eru gjørdar broytingar í skjalinum, um tú haldur fram vilja broytingar fara fyri skeytið. Ynskir tú at halda fram?'; $lang['js']['searchmedia'] = 'Leita eftir dátufílum'; $lang['js']['mediasize'] = 'Mynda stødd'; $lang['js']['mediatarget'] = 'Leinkja til'; $lang['js']['mediaclose'] = 'Læt aftur'; $lang['js']['mediainsert'] = 'Set inn'; $lang['js']['mediadisplayimg'] = 'Vís myndina'; $lang['js']['mediadisplaylnk'] = 'Vís bert leinkjuna'; $lang['js']['nosmblinks'] = 'Ávísingar til Windows shares virka bert í Microsoft Internet Explorer. Tú kanst enn avrita og sata inn slóðina.'; $lang['js']['del_confirm'] = 'Strika post(ar)?'; $lang['rssfailed'] = 'Eitt brek koma fyri tá roynt var at fáa: '; $lang['nothingfound'] = 'Leiting gav onki úrslit.'; $lang['mediaselect'] = 'Vel miðlafílu'; $lang['uploadsucc'] = 'Upp legg av fílu var væl eydna'; $lang['uploadfail'] = 'Brek við upp legg av fílu. Tað er møguliga trupuleikar við rættindunum'; $lang['uploadwrong'] = 'Upp legg av fílu víst burtur. Fíluslag er ikki loyvt'; $lang['uploadexist'] = 'Fílan er longu til.'; $lang['deletesucc'] = 'Fílan "%s" er nú strika.'; $lang['deletefail'] = '"%s" kundi ikki strikast - kanna rættindini.'; $lang['mediainuse'] = 'Fíla "%s" er ikki strika - hen verður enn nýtt.'; $lang['namespaces'] = 'Navnarúm'; $lang['mediafiles'] = 'Atkomandi fílur í'; $lang['reference'] = 'Ávísing til'; $lang['ref_inuse'] = 'Fílan kan ikki strikast, síðan hon enn verður nýtt á fylgjandi síðum:'; $lang['ref_hidden'] = 'Nakrar ávísingar eru í skjølum sum tú ikki hevur lesi rættindi til'; $lang['hits'] = 'Hits'; $lang['quickhits'] = 'Samsvarandi skjøl'; $lang['toc'] = 'Innihaldsyvirlit'; $lang['current'] = 'núverandi'; $lang['yours'] = 'Tín útgáva'; $lang['diff'] = 'vís broytingar í mun til núverandi útgávu'; $lang['line'] = 'Linja'; $lang['breadcrumb'] = 'Leið:'; $lang['youarehere'] = 'Tú ert her:'; $lang['lastmod'] = 'Seinast broytt:'; $lang['by'] = 'av'; $lang['deleted'] = 'strika'; $lang['created'] = 'stovna'; $lang['restored'] = 'gomul útgáva endurstovna (%s)'; $lang['summary'] = 'Samandráttur'; $lang['mail_newpage'] = 'skjal skoyta uppí:'; $lang['mail_changed'] = 'skjal broytt:'; $lang['qb_bold'] = 'Feit'; $lang['qb_italic'] = 'Skák'; $lang['qb_underl'] = 'Undurstrika'; $lang['qb_code'] = 'Skrivimaskinu tekstur'; $lang['qb_strike'] = 'Gjøgnumstrika'; $lang['qb_h1'] = 'Stig 1 yvirskrift'; $lang['qb_h2'] = 'Stig 2 yvirskrift'; $lang['qb_h3'] = 'Stig 3 yvirskrift'; $lang['qb_h4'] = 'Stig 4 yvirskrift'; $lang['qb_h5'] = 'Stig 5 yvirskrift'; $lang['qb_link'] = 'Innanhýsis slóð'; $lang['qb_extlink'] = 'Útvortis slóð'; $lang['qb_hr'] = 'Vatnrætt linja'; $lang['qb_ol'] = 'Talmerktur listi'; $lang['qb_ul'] = 'Ótalmerktur listi'; $lang['qb_media'] = 'Leggja myndir og aðrar fílur afturat'; $lang['qb_sig'] = 'Set inn undirskrift'; $lang['qb_smileys'] = 'Smileys'; $lang['qb_chars'] = 'Sertekn'; $lang['metaedit'] = 'Rætta metadáta'; $lang['metasaveerr'] = 'Brek við skriving av metadáta'; $lang['metasaveok'] = 'Metadáta goymt'; $lang['btn_img_backto'] = 'Aftur til %s'; $lang['img_title'] = 'Heitið:'; $lang['img_caption'] = 'Myndatekstur:'; $lang['img_date'] = 'Dato:'; $lang['img_fname'] = 'Fílunavn:'; $lang['img_fsize'] = 'Stødd:'; $lang['img_artist'] = 'Myndafólk:'; $lang['img_copyr'] = 'Upphavsrættur:'; $lang['img_format'] = 'Snið:'; $lang['img_camera'] = 'Fototól:'; $lang['img_keywords'] = 'Evnisorð:'; $lang['authtempfail'] = 'Validering av brúkara virkar fyribils ikki. Um hetta er varandi, fá so samband við umboðsstjóran á hesi wiki.'; $lang['email_signature_text'] = 'Hesin t-postur var skaptur av DokuWiki á @DOKUWIKIURL@'; lang/fo/read.txt 0000644 00000000257 15233462216 0007555 0 ustar 00 Hetta skjal kan bert læsast. Tú kanst síggja kelduna, men ikki goyma broytingar í tí. Um tú heldur at hetta er eitt brek, skriva so vinarliga í [[wiki:brek-yvirlit]]. lang/fo/preview.txt 0000644 00000000317 15233462216 0010320 0 ustar 00 ====== Forskoðan ====== Hetta er ein forskoðan skjalinum, sum vísur hvussi tað fer at síggja út. Minst til: Tað er //**IKKI**// goymt enn! Um tað sær rætt út, trýst so á **''[Goym]''** knappin lang/fo/updateprofile.txt 0000644 00000000237 15233462216 0011503 0 ustar 00 ====== Dagføra vangamynd fyri tína konti ====== Tú nýtist bert at fylla út tey øki sum tú ynskjur at broyta. Tú kanst ikki broyta títt brúkaranavn. lang/fo/subscr_digest.txt 0000644 00000000623 15233462216 0011477 0 ustar 00 Halló! Síðan @PAGE@ í @TITLE@ wiki er broytt. Her eru broytinganar: -------------------------------------------------------- @DIFF@ -------------------------------------------------------- Gamla skjalið: @OLDPAGE@ Nýggja skjalið: @NEWPAGE@ Fyri at avmelda síðu kunngerðir, logga inn í wikiina á @DOKUWIKIURL@ vitja so @SUBSCRIBE@ og avmelda hald á síðu og/ella navnaøkis broytingar. lang/fo/newpage.txt 0000644 00000000267 15233462216 0010271 0 ustar 00 ====== Hetta skjal er ikki til (enn) ====== Tú fylgdi ein ávísing til eitt skjal sum ikki er til (enn). Tú kanst stovna skjali við at trýsta á **Stovna hetta skjal** knappin. lang/fo/resendpwd.txt 0000644 00000000374 15233462216 0010635 0 ustar 00 ====== Send nýtt loyniorð ====== Fyll út øll niðanfyristandandi øki fyri at fáa sent eitt nýtt loyniorð til hesa wiki. Títt nýggja loyniorð verður sent til tí uppgivnu t-postadressu. Brúkaranavn eigur at verða títt wiki brúkaranavn. lang/fo/mailtext.txt 0000644 00000000527 15233462216 0010471 0 ustar 00 Eitt skjal í tíni DokuWiki bleiv broytt ella skoytt uppí. Her er ein lýsing: Dato : @DATE@ Browser : @BROWSER@ IP-adressa : @IPADDRESS@ Hostnavn : @HOSTNAME@ Gomul útgáva : @OLDPAGE@ Nýggj útgáva : @NEWPAGE@ Rætti samandráttur : @SUMMARY@ Brúkari : @USER@ @DIFF@ lang/fo/backlinks.txt 0000644 00000000165 15233462216 0010601 0 ustar 00 ====== Ávísing afturúr ====== Hetta er ein listi yvur øll tey skjøl sum vísa aftur á tað núverandi skjali. lang/fo/login.txt 0000644 00000000240 15233462216 0007742 0 ustar 00 ====== Rita inn ====== Tú hevur ikki rita inn! Slá inn brúkaranavn og loyniorð. Tín kagi skal loyva at cookies verða goymdar fyri at tú kanst rita inn. lang/fo/edit.txt 0000644 00000000574 15233462216 0007571 0 ustar 00 Rætta hetta skjal og trýst so á **''[Goym]''** knappin. Sí [[wiki:syntax|snið ábending]] fyri Wiki setningsbygnað. Rætta vinarliga bert hetta skjali um tú kanst **fyrireika** tað. Nýt vinarliga [[playground:playground|sandkassan]] til at testa áðrenn tú rættar í einum røttum skjali. Minst eisini til at brúkar **''[Forskoðan]''** áðrenn tú goymur skjalið. lang/fo/diff.txt 0000644 00000000325 15233462216 0007546 0 ustar 00 ====== Munir ====== Hetta vísur munir millum tí valdu og núverandu útgávu av skjalinum. Gular eru linjur sum er at finna í gomlu útgávuni, og grønar eru linjur sum eru at finna í núvarandi útgávuni. lang/fo/editrev.txt 0000644 00000000221 15233462216 0010273 0 ustar 00 **Tú hevur heinta eina gamla útgávu av hesum skjalinum!** Um tú goymur skjali vilt tú skriva útyvir núverandi við gomlu útgávuni. ---- lang/is/recent.txt 0000644 00000000123 15233462216 0010121 0 ustar 00 ===== Nýlegar Breytingar ===== Eftirfarandi síðum hefur nýlega verið breytt: lang/is/adminplugins.txt 0000644 00000000035 15233462216 0011335 0 ustar 00 ===== Aðrar viðbætur ===== lang/is/jquery.ui.datepicker.js 0000644 00000002320 15233462216 0012504 0 ustar 00 /* Icelandic initialisation for the jQuery UI date picker plugin. */ /* Written by Haukur H. Thorsson (haukur@eskill.is). */ ( function( factory ) { if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define( [ "../widgets/datepicker" ], factory ); } else { // Browser globals factory( jQuery.datepicker ); } }( function( datepicker ) { datepicker.regional.is = { closeText: "Loka", prevText: "< Fyrri", nextText: "Næsti >", currentText: "Í dag", monthNames: [ "Janúar","Febrúar","Mars","Apríl","Maí","Júní", "Júlí","Ágúst","September","Október","Nóvember","Desember" ], monthNamesShort: [ "Jan","Feb","Mar","Apr","Maí","Jún", "Júl","Ágú","Sep","Okt","Nóv","Des" ], dayNames: [ "Sunnudagur", "Mánudagur", "Þriðjudagur", "Miðvikudagur", "Fimmtudagur", "Föstudagur", "Laugardagur" ], dayNamesShort: [ "Sun","Mán","Þri","Mið","Fim","Fös","Lau" ], dayNamesMin: [ "Su","Má","Þr","Mi","Fi","Fö","La" ], weekHeader: "Vika", dateFormat: "dd.mm.yy", firstDay: 0, isRTL: false, showMonthAfterYear: false, yearSuffix: "" }; datepicker.setDefaults( datepicker.regional.is ); return datepicker.regional.is; } ) ); lang/is/lang.php 0000644 00000025532 15233462216 0007545 0 ustar 00 <?php /** * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * * is language file * * This file was initially built by fetching translations from other * Wiki projects. See the @url lines below. Additional translations * and fixes where done for DokuWiki by the people mentioned in the * lines starting with @author * * @url http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/languages/messages/MessagesIs.php?view=co * * @author Hrannar Baldursson <hrannar.baldursson@gmail.com> * @author Ólafur Gunnlaugsson <oli@audiotools.com> * @author Erik Bjørn Pedersen <erik.pedersen@shaw.ca> */ $lang['encoding'] = 'utf-8'; $lang['direction'] = 'ltr'; $lang['doublequoteopening'] = '„'; $lang['doublequoteclosing'] = '“'; $lang['singlequoteopening'] = '‚'; $lang['singlequoteclosing'] = '‘'; $lang['apostrophe'] = '\''; $lang['btn_edit'] = 'Breyta þessari síðu'; $lang['btn_source'] = 'Skoða wikikóða'; $lang['btn_show'] = 'Sýna síðu'; $lang['btn_create'] = 'Búa til þessa síðu'; $lang['btn_search'] = 'Leit'; $lang['btn_save'] = 'Vista'; $lang['btn_preview'] = 'Forskoða'; $lang['btn_top'] = 'Efst á síðu'; $lang['btn_newer'] = '<< nýrra'; $lang['btn_older'] = 'eldra >>'; $lang['btn_revs'] = 'breytingaskrá'; $lang['btn_recent'] = 'Nýlegar breytingar'; $lang['btn_upload'] = 'Hlaða upp'; $lang['btn_cancel'] = 'Hætta við'; $lang['btn_index'] = 'Atriðaskrá'; $lang['btn_secedit'] = 'Breyta'; $lang['btn_login'] = 'Innskrá'; $lang['btn_logout'] = 'Útskrá'; $lang['btn_admin'] = 'Stjórnandi'; $lang['btn_update'] = 'Uppfæra'; $lang['btn_delete'] = 'Eyða'; $lang['btn_back'] = 'Til baka'; $lang['btn_backlink'] = 'Hvað tengist hingað'; $lang['btn_subscribe'] = 'Vakta'; $lang['btn_profile'] = 'Uppfæra notanda'; $lang['btn_reset'] = 'Endurstilla'; $lang['btn_draft'] = 'Breyta uppkasti'; $lang['btn_recover'] = 'Endurheimta uppkast'; $lang['btn_draftdel'] = 'Eyða uppkasti'; $lang['btn_revert'] = 'Endurheimta'; $lang['btn_register'] = 'Skráning'; $lang['btn_img_backto'] = 'Aftur til %s'; $lang['loggedinas'] = 'Innskráning sem:'; $lang['user'] = 'Notendanafn'; $lang['pass'] = 'Aðgangsorð'; $lang['newpass'] = 'Nýtt aðgangsorð'; $lang['oldpass'] = 'Staðfesta núverandi (gamla) aðgangsorðið'; $lang['passchk'] = 'Aðgangsorð (aftur)'; $lang['remember'] = 'Muna.'; $lang['fullname'] = 'Fullt nafn þitt*'; $lang['email'] = 'Tölvupóstfangið þitt*'; $lang['profile'] = 'Notendastillingar'; $lang['badlogin'] = 'Því miður, notandanafn eða aðgangsorð var rangur.'; $lang['minoredit'] = 'Minniháttar breyting'; $lang['draftdate'] = 'Uppkast vistað sjálfkrafa'; $lang['nosecedit'] = 'Síðunni var breytt á meðan, upplýsingar um svæðið voru úreltar og öll síðan því endurhlaðin.'; $lang['js']['searchmedia'] = 'Leita að skrám'; $lang['js']['hidedetails'] = 'Fela upplýsingar'; $lang['js']['linkwiz'] = 'Tengill-leiðsagnarforrit'; $lang['js']['linkto'] = 'Tengja'; $lang['js']['del_confirm'] = 'Á örugglega að eyða valdar skrár?'; $lang['regmissing'] = 'Afsakið, en þú verður að fylla út í allar eyður.'; $lang['reguexists'] = 'Afsakið, notandi með þessu nafni er þegar skráður inn.'; $lang['regsuccess'] = 'Notandi hefur verið búinn til og aðgangsorð sent í tölvupósti.'; $lang['regsuccess2'] = 'Notandi hefur verið búinn til.'; $lang['regmailfail'] = 'Það lítur út fyrir villu við sendingu aðgangsorðs. Vinsamlegast hafðu samband við stjórnanda.'; $lang['regbadmail'] = 'Uppgefinn tölvupóstur virðist ógildur - teljir þú þetta vera villu, hafðu þá samband við stjórnanda.'; $lang['regbadpass'] = 'Aðgangsorðin tvö eru ekki eins, vinsamlegast reyndu aftur.'; $lang['regpwmail'] = 'DokuWiki aðgangsorðið þitt'; $lang['reghere'] = 'Ertu ekki með reikning? Skráðu þig'; $lang['profna'] = 'Þessi wiki leyfir ekki breytingar á notendaupplýsingum'; $lang['profnochange'] = 'Enga breytingar vistaðar'; $lang['profnoempty'] = 'Það er ekki leyfilegt að skilja nafn og póstfang eftir óútfyllt'; $lang['profchanged'] = 'Notendaupplýsingum breytt'; $lang['pwdforget'] = 'Gleymt aðgangsorð? Fáðu nýtt'; $lang['resendna'] = 'Þessi wiki styður ekki endursendingar aðgangsorðs'; $lang['resendpwdmissing'] = 'Afsakið, þú verður að út eyðublaðið allt'; $lang['resendpwdnouser'] = 'Afsakið, notandi finnst ekki.'; $lang['resendpwdbadauth'] = 'Afsakið, þessi sannvottunorð er ekki gild. Gakktu úr skugga um að þú notaðir að ljúka staðfesting hlekkur.'; $lang['resendpwdconfirm'] = 'Staðfesting hlekkur hefur verið send með tölvupósti.'; $lang['resendpwdsuccess'] = 'Nýja aðgangsorðið hefur verið sent með tölvupósti.'; $lang['license'] = 'Nema annað sé tekið fram, efni á þessari wiki er leyfð undir eftirfarandi leyfi:'; $lang['licenseok'] = 'Athugið: Með því að breyta þessari síðu samþykkir þú að leyfisveitandi efni undir eftirfarandi leyfi:'; $lang['searchmedia'] = 'Leit skrárheiti:'; $lang['searchmedia_in'] = 'Leit í %s'; $lang['txt_upload'] = 'Veldu skrá til innhleðslu:'; $lang['txt_filename'] = 'Innhlaða sem (valfrjálst):'; $lang['txt_overwrt'] = 'Skrifa yfir skrá sem þegar er til'; $lang['lockedby'] = 'Læstur af:'; $lang['lockexpire'] = 'Læsing rennur út eftir:'; $lang['nothingfound'] = 'Ekkert fannst'; $lang['mediaselect'] = 'Miðlaskrá'; $lang['uploadsucc'] = 'Innhlaðning tókst'; $lang['uploadfail'] = 'Villa í innhlaðningu'; $lang['uploadwrong'] = 'Innhleðslu neitað. Skrár með þessari endingu eru ekki leyfðar.'; $lang['uploadexist'] = 'Skrá var þegar til staðar.'; $lang['uploadbadcontent'] = 'Innhlaðið efni var ekki við að %s skrárendingu.'; $lang['uploadspam'] = 'Þessi innhlaðning er útilokuð vegna ruslpósts svarturlisti.'; $lang['uploadxss'] = 'Þessi innhlaðning er útilokuð vegna hugsanlega skaðlegum efni.'; $lang['uploadsize'] = 'Innhlaðið skrá var of stór. (Hámark eru %s)'; $lang['deletesucc'] = 'Skrá %s hefur verið eytt.'; $lang['namespaces'] = 'Nafnrýmar'; $lang['mediafiles'] = 'Tiltækar skrár í'; $lang['mediaview'] = 'Sjá upprunalega skrá'; $lang['mediaroot'] = 'rót'; $lang['mediaextchange'] = 'Skrárending var breytt úr .%s til .%s!'; $lang['reference'] = 'Tilvísanir til'; $lang['ref_inuse'] = 'Ekki hægt að eyða skráin, því það er enn notað af eftirfarandi síðum:'; $lang['ref_hidden'] = 'Sumar tilvísanir eru að síður sem þú hefur ekki leyfi til að lesa'; $lang['hits'] = 'Samsvör'; $lang['quickhits'] = 'Samsvörun síðunöfn'; $lang['toc'] = 'Efnisyfirlit'; $lang['current'] = 'nú'; $lang['yours'] = 'Þín útgáfa'; $lang['diff'] = 'Sýna ágreiningur til núverandi endurskoðun'; $lang['diff2'] = 'Sýna ágreiningur meðal valið endurskoðun'; $lang['line'] = 'Lína'; $lang['breadcrumb'] = 'Snefill:'; $lang['youarehere'] = 'Þú ert hér:'; $lang['lastmod'] = 'Síðast breytt:'; $lang['by'] = 'af'; $lang['deleted'] = 'eytt'; $lang['created'] = 'myndað'; $lang['restored'] = 'Breytt aftur til fyrri útgáfu (%s)'; $lang['external_edit'] = 'utanaðkomandi breyta'; $lang['summary'] = 'Forskoða'; $lang['noflash'] = 'Það þarf <a href="http://www.adobe.com/products/flashplayer/">Adobe Flash viðbót</a> til að sýna sumt efnið á þessari síðu'; $lang['download'] = 'Hlaða niður til kóðabút'; $lang['mail_newpage'] = 'síðu bætt við:'; $lang['mail_changed'] = 'síðu breytt:'; $lang['mail_new_user'] = 'nýr notandi:'; $lang['mail_upload'] = 'Innhlaðið skrá:'; $lang['qb_bold'] = 'Feitletraður texti'; $lang['qb_italic'] = 'Skáletraður texti'; $lang['qb_underl'] = 'Undirstrikaður texti'; $lang['qb_code'] = 'Kóðatraður texti'; $lang['qb_strike'] = 'Yfirstrikaður texti'; $lang['qb_h1'] = 'Fyrsta stigs fyrirsögn'; $lang['qb_h2'] = 'Annars stigs fyrirsögn'; $lang['qb_h3'] = 'Þriðja stigs fyrirsögn'; $lang['qb_h4'] = 'Fjórða stigs fyrirsögn'; $lang['qb_h5'] = 'Fimmta stigs fyrirsögn'; $lang['qb_h'] = 'Fyrirsögn'; $lang['qb_hs'] = 'Veldu fyrirsögn'; $lang['qb_hplus'] = 'Hærra stigs fyrirsögn'; $lang['qb_hminus'] = 'Lægri stigs fyrirsögn'; $lang['qb_hequal'] = 'Sama stigs fyrirsögn'; $lang['qb_link'] = 'Innri tengill'; $lang['qb_extlink'] = 'Ytri tengill (muna að setja http:// á undan)'; $lang['qb_hr'] = 'Lárétt lína (notist sparlega)'; $lang['qb_ol'] = 'Númeraðaðan listatriði'; $lang['qb_ul'] = 'Ónúmeraðaðan listatriði'; $lang['qb_media'] = 'Bæta inn myndum og öðrum skrám'; $lang['qb_sig'] = 'Undirskrift þín auk tímasetningu'; $lang['qb_smileys'] = 'Broskallar'; $lang['qb_chars'] = 'Sértækir stafir'; $lang['metaedit'] = 'Breyta lýsigögnum'; $lang['metasaveerr'] = 'Vistun lýsigagna mistókst'; $lang['metasaveok'] = 'Lýsigögn vistuð'; $lang['img_title'] = 'Heiti:'; $lang['img_caption'] = 'Skýringartexti:'; $lang['img_date'] = 'Dagsetning:'; $lang['img_fname'] = 'Skrárheiti:'; $lang['img_fsize'] = 'Stærð:'; $lang['img_artist'] = 'Myndsmiður:'; $lang['img_copyr'] = 'Útgáfuréttur:'; $lang['img_format'] = 'Forsnið:'; $lang['img_camera'] = 'Myndavél:'; $lang['img_keywords'] = 'Lykilorðir:'; $lang['i_retry'] = 'Reyna aftur'; lang/is/resendpwd.txt 0000644 00000000370 15233462216 0010640 0 ustar 00 ====== Senda nýtt aðgangsorð ====== Vinsamlegast sláðu inn notendanafn þitt í formið hér fyrir neðan til að biðja um nýtt aðgangsorð fyrir reikninginn þinn í þessu wiki. A staðfesting hlekkur verður sendast á skráð netfang. lang/is/login.txt 0000644 00000000254 15233462216 0007756 0 ustar 00 ===== Innskráning ===== Þú ert ekki skráður inn! Skráuðu þig inn hér að neðan. Athugaðu að vafrinn sem að þú notar verður að styðja móttöku smákaka. lang/is/diff.txt 0000644 00000000112 15233462216 0007547 0 ustar 00 ===== Breytingar ===== Hér sést hvað hefur breyst á milli útgáfna. lang/sv/index.txt 0000644 00000000231 15233462216 0007765 0 ustar 00 ====== Innehållsförteckning ====== Detta är en innehållsförteckning över alla tillgängliga sidor, sorterad efter [[doku>namespaces|namnrymder]]. lang/sv/subscr_form.txt 0000644 00000000174 15233462216 0011210 0 ustar 00 ====== Prenumerations hantering ====== Denna sida låter dig hantera dina prenumerationer för nuvarande sida och namnrymd. lang/sv/pwconfirm.txt 0000644 00000000463 15233462216 0010671 0 ustar 00 Hej @FULLNAME@! Någon har bett om ett nytt lösenord för ditt konto på @TITLE@ (@DOKUWIKIURL@) Om det inte var du som bad om ett nytt lösenord kan du helt enkelt ignorera det här brevet. För att bekräfta att förfrågan verkligen kom från dig, var vänlig och använd följande länk. @CONFIRM@ lang/sv/register.txt 0000644 00000000601 15233462216 0010503 0 ustar 00 ====== Registrera dig som användare ====== Fyll i all information som efterfrågas i formuläret nedan för att skapa ett nytt konto i denna wiki. Var särskilt noga med att ange en **giltig e-postadress** - om du inte blir ombedd att ange ett lösenord här kommer ett nytt lösenord att skickas till den adressen. Användarnamnet skall vara ett giltigt [[doku>pagename|sidnamn]]. lang/sv/denied.txt 0000644 00000000117 15233462216 0010111 0 ustar 00 ====== Åtkomst nekad ====== Tyvärr, du har inte behörighet att fortsätta. lang/sv/revisions.txt 0000644 00000000341 15233462216 0010701 0 ustar 00 ====== Historik ====== Här visas tidigare versioner av detta dokument. För att återställa dokumentet till en tidigare version, välj den önskade versionen nedan, klicka på ''Redigera sida'' och spara sedan dokumentet. lang/sv/norev.txt 0000644 00000000252 15233462216 0010012 0 ustar 00 ====== Det finns ingen sådan version ====== Den angivna versionen finns inte. Använd ''Historik'' för en förteckning över de versioner som finns av detta dokument. lang/sv/admin.txt 0000644 00000000165 15233462216 0007754 0 ustar 00 ====== Administration ====== Nedan hittar du en lista över de tillgängliga administrativa uppgifterna i DokuWiki. lang/sv/uploadmail.txt 0000644 00000000367 15233462216 0011017 0 ustar 00 En fil har laddats upp till din DokuWiki. Här är detaljerna: Fil : @MEDIA@ Datum : @DATE@ Webbläsare : @BROWSER@ IP-adress : @IPADDRESS@ Datornamn : @HOSTNAME@ Storlek : @SIZE@ MIME-typ : @MIME@ Användare : @USER@ lang/sv/subscr_list.txt 0000644 00000000565 15233462216 0011224 0 ustar 00 Hej! Sidorna i namnrymden @PAGE@ för wikin @TITLE@ har ändrats. Följande sidor har ändrats: -------------------------------------------------------- @DIFF@ -------------------------------------------------------- För att inaktivera sidnotifieringar, logga in på wikin (@DOKUWIKIURL@), gå till @SUBSCRIBE@ och avanmäl dig från sid-och/eller namnrymd-ändringar. lang/sv/mailwrap.html 0000644 00000000301 15233462216 0010615 0 ustar 00 <html> <head> <title>@TITLE@</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> </head> <body> @HTMLBODY@ <br /><hr /> <small>@EMAILSIGNATURE@</small> </body> </html>