diff options
Diffstat (limited to 'lib/exe')
-rw-r--r-- | lib/exe/ajax.php | 435 | ||||
-rw-r--r-- | lib/exe/css.php | 343 | ||||
-rw-r--r-- | lib/exe/detail.php | 48 | ||||
-rw-r--r-- | lib/exe/fetch.php | 205 | ||||
-rw-r--r-- | lib/exe/index.html | 12 | ||||
-rw-r--r-- | lib/exe/indexer.php | 271 | ||||
-rw-r--r-- | lib/exe/js.php | 358 | ||||
-rw-r--r-- | lib/exe/mediamanager.php | 122 | ||||
-rw-r--r-- | lib/exe/opensearch.php | 38 | ||||
-rw-r--r-- | lib/exe/xmlrpc.php | 888 |
10 files changed, 2720 insertions, 0 deletions
diff --git a/lib/exe/ajax.php b/lib/exe/ajax.php new file mode 100644 index 000000000..3d1584244 --- /dev/null +++ b/lib/exe/ajax.php @@ -0,0 +1,435 @@ +<?php +/** + * DokuWiki AJAX call handler + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Andreas Gohr <andi@splitbrain.org> + */ + +if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); +require_once(DOKU_INC.'inc/init.php'); +//close session +session_write_close(); + +header('Content-Type: text/html; charset=utf-8'); + + +//call the requested function +if(isset($_POST['call'])){ + $call = $_POST['call']; +}else if(isset($_GET['call'])){ + $call = $_GET['call']; +}else{ + exit; +} +$callfn = 'ajax_'.$call; + +if(function_exists($callfn)){ + $callfn(); +}else{ + $evt = new Doku_Event('AJAX_CALL_UNKNOWN', $call); + if ($evt->advise_before()) { + print "AJAX call '".htmlspecialchars($call)."' unknown!\n"; + exit; + } + $evt->advise_after(); + unset($evt); +} + +/** + * Searches for matching pagenames + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function ajax_qsearch(){ + global $conf; + global $lang; + + $query = $_POST['q']; + if(empty($query)) $query = $_GET['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>'; + 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>'; + } + print '</ul>'; +} + +/** + * Support OpenSearch suggestions + * + * @link http://www.opensearch.org/Specifications/OpenSearch/Extensions/Suggestions/1.0 + * @author Mike Frysinger <vapier@gentoo.org> + */ +function ajax_suggestions() { + global $conf; + global $lang; + + $query = cleanID($_POST['q']); + if(empty($query)) $query = cleanID($_GET['q']); + if(empty($query)) return; + + $data = array(); + $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 + ); + $json = new JSON(); + + header('Content-Type: application/x-suggestions+json'); + print $json->encode($suggestions); +} + +/** + * Refresh a page lock and save draft + * + * Andreas Gohr <andi@splitbrain.org> + */ +function ajax_lock(){ + global $conf; + global $lang; + global $ID; + global $INFO; + + $ID = cleanID($_POST['id']); + if(empty($ID)) return; + + $INFO = pageinfo(); + + if (!$INFO['writable']) { + echo 'Permission denied'; + return; + } + + if(!checklock($ID)){ + lock($ID); + echo 1; + } + + if($conf['usedraft'] && $_POST['wikitext']){ + $client = $_SERVER['REMOTE_USER']; + if(!$client) $client = clientIP(true); + + $draft = array('id' => $ID, + 'prefix' => substr($_POST['prefix'], 0, -1), + 'text' => $_POST['wikitext'], + 'suffix' => $_POST['suffix'], + 'date' => (int) $_POST['date'], + 'client' => $client, + ); + $cname = getCacheName($draft['client'].$ID,'.draft'); + if(io_saveFile($cname,serialize($draft))){ + echo $lang['draftdate'].' '.dformat(); + } + } + +} + +/** + * Delete a draft + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function ajax_draftdel(){ + $id = cleanID($_REQUEST['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> + */ +function ajax_medians(){ + global $conf; + + // wanted namespace + $ns = cleanID($_POST['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> + */ +function ajax_medialist(){ + global $conf; + global $NS; + + $NS = $_POST['ns']; + if ($_POST['do'] == 'media') { + tpl_mediaFileList(); + } else { + tpl_mediaContent(true); + } +} + +/** + * Return the content of the right column + * (image details) for the Mediamanager + * + * @author Kate Arzamastseva <pshns@ukr.net> + */ +function ajax_mediadetails(){ + global $DEL, $NS, $IMG, $AUTH, $JUMPTO, $REV, $lang, $fullscreen, $conf; + $fullscreen = true; + require_once(DOKU_INC.'lib/exe/mediamanager.php'); + + if ($_REQUEST['image']) $image = cleanID($_REQUEST['image']); + if (isset($IMG)) $image = $IMG; + if (isset($JUMPTO)) $image = $JUMPTO; + if (isset($REV) && !$JUMPTO) $rev = $REV; + + html_msgarea(); + tpl_mediaFileDetails($image, $rev); +} + +/** + * Returns image diff representation for mediamanager + * @author Kate Arzamastseva <pshns@ukr.net> + */ +function ajax_mediadiff(){ + global $NS; + + if ($_REQUEST['image']) $image = cleanID($_REQUEST['image']); + $NS = $_POST['ns']; + $auth = auth_quickaclcheck("$ns:*"); + media_diff($image, $NS, $auth, true); +} + +function ajax_mediaupload(){ + global $NS, $MSG; + + if ($_FILES['qqfile']['tmp_name']) { + $id = ((empty($_POST['mediaid'])) ? $_FILES['qqfile']['name'] : $_POST['mediaid']); + } elseif (isset($_GET['qqfile'])) { + $id = $_GET['qqfile']; + } + + $id = cleanID($id); + + $NS = $_REQUEST['ns']; + $ns = $NS.':'.getNS($id); + + $AUTH = auth_quickaclcheck("$ns:*"); + if($AUTH >= AUTH_UPLOAD) { io_createNamespace("$ns:xxx", 'media'); } + + if ($_FILES['qqfile']['error']) unset($_FILES['qqfile']); + + if ($_FILES['qqfile']['tmp_name']) $res = media_upload($NS, $AUTH, $_FILES['qqfile']); + if (isset($_GET['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); + + if (!$result) { + $error = ''; + if (isset($MSG)) { + foreach($MSG as $msg) $error .= $msg['msg']; + } + $result = array('error' => $msg['msg'], 'ns' => $NS); + } + $json = new JSON; + echo htmlspecialchars($json->encode($result), ENT_NOQUOTES); +} + +function dir_delete($path) { + if (!is_string($path) || $path == "") return false; + + if (is_dir($path) && !is_link($path)) { + if (!$dh = @opendir($path)) return false; + + while ($f = readdir($dh)) { + if ($f == '..' || $f == '.') continue; + dir_delete("$path/$f"); + } + + closedir($dh); + return @rmdir($path); + } else { + return @unlink($path); + } + + return false; +} + +/** + * Return sub index for index view + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function ajax_index(){ + global $conf; + + // wanted namespace + $ns = cleanID($_POST['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> + */ +function ajax_linkwiz(){ + global $conf; + global $lang; + + $q = ltrim(trim($_POST['q']),':'); + $id = noNS($q); + $ns = getNS($q); + + $ns = cleanID($ns); + $id = cleanID($id); + + $nsd = utf8_encodeFN(str_replace(':','/',$ns)); + $idd = utf8_encodeFN(str_replace(':','/',$id)); + + $data = array(); + if($q && !$ns){ + + // use index to lookup matching pages + $pages = array(); + $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 = htmlspecialchars($item['id']); + } + + echo '<a href="'.$link.'" title="'.htmlspecialchars($item['id']).'" class="wikilink1">'.$name.'</a>'; + + if($item['title']){ + echo '<span>'.htmlspecialchars($item['title']).'</span>'; + } + echo '</div>'; + } + +} + +//Setup VIM: ex: et ts=2 : diff --git a/lib/exe/css.php b/lib/exe/css.php new file mode 100644 index 000000000..69b512205 --- /dev/null +++ b/lib/exe/css.php @@ -0,0 +1,343 @@ +<?php +/** + * DokuWiki StyleSheet creator + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Andreas Gohr <andi@splitbrain.org> + */ + +if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); +if(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching) +if(!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT',1); // we gzip ourself here +require_once(DOKU_INC.'inc/init.php'); + +// Main (don't run when UNIT test) +if(!defined('SIMPLE_TEST')){ + header('Content-Type: text/css; charset=utf-8'); + css_out(); +} + + +// ---------------------- functions ------------------------------ + +/** + * Output all needed Styles + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function css_out(){ + global $conf; + global $lang; + global $config_cascade; + + $mediatype = 'screen'; + if (isset($_REQUEST['s']) && + in_array($_REQUEST['s'], array('all', 'print', 'feed'))) { + $mediatype = $_REQUEST['s']; + } + + $tpl = trim(preg_replace('/[^\w-]+/','',$_REQUEST['t'])); + if($tpl){ + $tplinc = DOKU_INC.'lib/tpl/'.$tpl.'/'; + $tpldir = DOKU_BASE.'lib/tpl/'.$tpl.'/'; + }else{ + $tplinc = tpl_incdir(); + $tpldir = tpl_basedir(); + } + + // The generated script depends on some dynamic options + $cache = new cache('styles'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'].DOKU_BASE.$tplinc.$mediatype,'.css'); + + // load template styles + $tplstyles = array(); + if(@file_exists($tplinc.'style.ini')){ + $ini = parse_ini_file($tplinc.'style.ini',true); + foreach($ini['stylesheets'] as $file => $mode){ + $tplstyles[$mode][$tplinc.$file] = $tpldir; + } + } + + // Array of needed files and their web locations, the latter ones + // are needed to fix relative paths in the stylesheets + $files = array(); + // load core styles + $files[DOKU_INC.'lib/styles/'.$mediatype.'.css'] = DOKU_BASE.'lib/styles/'; + // load jQuery-UI theme + $files[DOKU_INC.'lib/scripts/jquery/jquery-ui-theme/smoothness.css'] = DOKU_BASE.'lib/scripts/jquery/jquery-ui-theme/'; + // load plugin styles + $files = array_merge($files, css_pluginstyles($mediatype)); + // load template styles + if (isset($tplstyles[$mediatype])) { + $files = array_merge($files, $tplstyles[$mediatype]); + } + // if old 'default' userstyle setting exists, make it 'screen' userstyle for backwards compatibility + if (isset($config_cascade['userstyle']['default'])) { + $config_cascade['userstyle']['screen'] = $config_cascade['userstyle']['default']; + } + // load user styles + if(isset($config_cascade['userstyle'][$mediatype])){ + $files[$config_cascade['userstyle'][$mediatype]] = DOKU_BASE; + } + // load rtl styles + // @todo: this currently adds the rtl styles only to the 'screen' media type + // but 'print' and 'all' should also be supported + if ($mediatype=='screen') { + if($lang['direction'] == 'rtl'){ + if (isset($tplstyles['rtl'])) $files = array_merge($files, $tplstyles['rtl']); + } + } + + $cache_files = array_merge(array_keys($files), getConfigFiles('main')); + $cache_files[] = $tplinc.'style.ini'; + $cache_files[] = __FILE__; + + // check cache age & handle conditional request + // This may exit if a cache can be used + http_cached($cache->cache, + $cache->useCache(array('files' => $cache_files))); + + // start output buffering and build the stylesheet + ob_start(); + + // print the default classes for interwiki links and file downloads + css_interwiki(); + css_filetypes(); + + // load files + foreach($files as $file => $location){ + print css_loadfile($file, $location); + } + + // end output buffering and get contents + $css = ob_get_contents(); + ob_end_clean(); + + // apply style replacements + $css = css_applystyle($css,$tplinc); + + // place all @import statements at the top of the file + $css = css_moveimports($css); + + // compress whitespace and comments + if($conf['compress']){ + $css = css_compress($css); + } + + // embed small images right into the stylesheet + if($conf['cssdatauri']){ + $base = preg_quote(DOKU_BASE,'#'); + $css = preg_replace_callback('#(url\([ \'"]*)('.$base.')(.*?(?:\.(png|gif)))#i','css_datauri',$css); + } + + http_cached_finish($cache->cache, $css); +} + +/** + * Does placeholder replacements in the style according to + * the ones defined in a templates style.ini file + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function css_applystyle($css,$tplinc){ + if(@file_exists($tplinc.'style.ini')){ + $ini = parse_ini_file($tplinc.'style.ini',true); + $css = strtr($css,$ini['replacements']); + } + return $css; +} + +/** + * Prints classes for interwikilinks + * + * Interwiki links have two classes: 'interwiki' and 'iw_$name>' where + * $name is the identifier given in the config. All Interwiki links get + * an default style with a default icon. If a special icon is available + * for an interwiki URL it is set in it's own class. Both classes can be + * overwritten in the template or userstyles. + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function css_interwiki(){ + + // default style + echo 'a.interwiki {'; + echo ' background: transparent url('.DOKU_BASE.'lib/images/interwiki.png) 0px 1px no-repeat;'; + echo ' padding: 1px 0px 1px 16px;'; + echo '}'; + + // additional styles when icon available + $iwlinks = getInterwiki(); + foreach(array_keys($iwlinks) as $iw){ + $class = preg_replace('/[^_\-a-z0-9]+/i','_',$iw); + if(@file_exists(DOKU_INC.'lib/images/interwiki/'.$iw.'.png')){ + echo "a.iw_$class {"; + echo ' background-image: url('.DOKU_BASE.'lib/images/interwiki/'.$iw.'.png)'; + echo '}'; + }elseif(@file_exists(DOKU_INC.'lib/images/interwiki/'.$iw.'.gif')){ + echo "a.iw_$class {"; + echo ' background-image: url('.DOKU_BASE.'lib/images/interwiki/'.$iw.'.gif)'; + echo '}'; + } + } +} + +/** + * Prints classes for file download links + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function css_filetypes(){ + + // default style + echo '.mediafile {'; + echo ' background: transparent url('.DOKU_BASE.'lib/images/fileicons/file.png) 0px 1px no-repeat;'; + echo ' padding-left: 18px;'; + echo ' padding-bottom: 1px;'; + echo '}'; + + // additional styles when icon available + // scan directory for all icons + $exts = array(); + if($dh = opendir(DOKU_INC.'lib/images/fileicons')){ + while(false !== ($file = readdir($dh))){ + if(preg_match('/([_\-a-z0-9]+(?:\.[_\-a-z0-9]+)*?)\.(png|gif)/i',$file,$match)){ + $ext = strtolower($match[1]); + $type = '.'.strtolower($match[2]); + if($ext!='file' && (!isset($exts[$ext]) || $type=='.png')){ + $exts[$ext] = $type; + } + } + } + closedir($dh); + } + foreach($exts as $ext=>$type){ + $class = preg_replace('/[^_\-a-z0-9]+/','_',$ext); + echo ".mf_$class {"; + echo ' background-image: url('.DOKU_BASE.'lib/images/fileicons/'.$ext.$type.')'; + echo '}'; + } +} + +/** + * Loads a given file and fixes relative URLs with the + * given location prefix + */ +function css_loadfile($file,$location=''){ + if(!@file_exists($file)) return ''; + $css = io_readFile($file); + if(!$location) return $css; + + $css = preg_replace('#(url\([ \'"]*)(?!/|data:|http://|https://| |\'|")#','\\1'.$location,$css); + $css = preg_replace('#(@import\s+[\'"])(?!/|data:|http://|https://)#', '\\1'.$location, $css); + + return $css; +} + +/** + * Converte local image URLs to data URLs if the filesize is small + * + * Callback for preg_replace_callback + */ +function css_datauri($match){ + global $conf; + + $pre = unslash($match[1]); + $base = unslash($match[2]); + $url = unslash($match[3]); + $ext = unslash($match[4]); + + $local = DOKU_INC.$url; + $size = @filesize($local); + if($size && $size < $conf['cssdatauri']){ + $data = base64_encode(file_get_contents($local)); + } + if($data){ + $url = 'data:image/'.$ext.';base64,'.$data; + }else{ + $url = $base.$url; + } + return $pre.$url; +} + + +/** + * Returns a list of possible Plugin Styles (no existance check here) + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function css_pluginstyles($mediatype='screen'){ + global $lang; + $list = array(); + $plugins = plugin_list(); + foreach ($plugins as $p){ + $list[DOKU_PLUGIN."$p/$mediatype.css"] = DOKU_BASE."lib/plugins/$p/"; + // alternative for screen.css + if ($mediatype=='screen') { + $list[DOKU_PLUGIN."$p/style.css"] = DOKU_BASE."lib/plugins/$p/"; + } + if($lang['direction'] == 'rtl'){ + $list[DOKU_PLUGIN."$p/rtl.css"] = DOKU_BASE."lib/plugins/$p/"; + } + } + return $list; +} + +/** + * Move all @import statements in a combined stylesheet to the top so they + * aren't ignored by the browser. + * + * @author Gabriel Birke <birke@d-scribe.de> + */ +function css_moveimports($css) +{ + if(!preg_match_all('/@import\s+(?:url\([^)]+\)|"[^"]+")\s*[^;]*;\s*/', $css, $matches, PREG_OFFSET_CAPTURE)) { + return $css; + } + $newCss = ""; + $imports = ""; + $offset = 0; + foreach($matches[0] as $match) { + $newCss .= substr($css, $offset, $match[1] - $offset); + $imports .= $match[0]; + $offset = $match[1] + strlen($match[0]); + } + $newCss .= substr($css, $offset); + return $imports.$newCss; +} + +/** + * Very simple CSS optimizer + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function css_compress($css){ + //strip comments through a callback + $css = preg_replace_callback('#(/\*)(.*?)(\*/)#s','css_comment_cb',$css); + + //strip (incorrect but common) one line comments + $css = preg_replace('/(?<!:)\/\/.*$/m','',$css); + + // strip whitespaces + $css = preg_replace('![\r\n\t ]+!',' ',$css); + $css = preg_replace('/ ?([;,{}\/]) ?/','\\1',$css); + $css = preg_replace('/ ?: /',':',$css); + + // shorten colors + $css = preg_replace("/#([0-9a-fA-F]{1})\\1([0-9a-fA-F]{1})\\2([0-9a-fA-F]{1})\\3/", "#\\1\\2\\3",$css); + + return $css; +} + +/** + * Callback for css_compress() + * + * Keeps short comments (< 5 chars) to maintain typical browser hacks + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function css_comment_cb($matches){ + if(strlen($matches[2]) > 4) return ''; + return $matches[0]; +} + +//Setup VIM: ex: et ts=4 : diff --git a/lib/exe/detail.php b/lib/exe/detail.php new file mode 100644 index 000000000..35186f5dd --- /dev/null +++ b/lib/exe/detail.php @@ -0,0 +1,48 @@ +<?php +if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); +define('DOKU_MEDIADETAIL',1); +require_once(DOKU_INC.'inc/init.php'); +//close session +session_write_close(); + +$IMG = getID('media'); +$ID = cleanID($_REQUEST['id']); + +if($conf['allowdebug'] && $_REQUEST['debug']){ + print '<pre>'; + foreach(explode(' ','basedir userewrite baseurl useslash') as $x){ + print '$'."conf['$x'] = '".$conf[$x]."';\n"; + } + foreach(explode(' ','DOCUMENT_ROOT HTTP_HOST SCRIPT_FILENAME PHP_SELF '. + 'REQUEST_URI SCRIPT_NAME PATH_INFO PATH_TRANSLATED') as $x){ + print '$'."_SERVER['$x'] = '".$_SERVER[$x]."';\n"; + } + print "getID('media'): ".getID('media')."\n"; + print "getID('media',false): ".getID('media',false)."\n"; + print '</pre>'; +} + +$ERROR = false; +// check image permissions +$AUTH = auth_quickaclcheck($IMG); +if($AUTH >= AUTH_READ){ + // check if image exists + $SRC = mediaFN($IMG); + if(!@file_exists($SRC)){ + //doesn't exist! + header("HTTP/1.0 404 File not Found"); + $ERROR = 'File not found'; + } +}else{ + // no auth + $ERROR = p_locale_xhtml('denied'); +} + +// this makes some general infos available as well as the info about the +// "parent" page +$INFO = pageinfo(); + +//start output and load template +header('Content-Type: text/html; charset=utf-8'); +include(template('detail.php')); + diff --git a/lib/exe/fetch.php b/lib/exe/fetch.php new file mode 100644 index 000000000..143d40f22 --- /dev/null +++ b/lib/exe/fetch.php @@ -0,0 +1,205 @@ +<?php +/** + * DokuWiki media passthrough file + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Andreas Gohr <andi@splitbrain.org> + */ + + if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); + define('DOKU_DISABLE_GZIP_OUTPUT', 1); + require_once(DOKU_INC.'inc/init.php'); + + //close session + session_write_close(); + + $mimetypes = getMimeTypes(); + + //get input + $MEDIA = stripctl(getID('media',false)); // no cleaning except control chars - maybe external + $CACHE = calc_cache($_REQUEST['cache']); + $WIDTH = (int) $_REQUEST['w']; + $HEIGHT = (int) $_REQUEST['h']; + $REV = (int) @$_REQUEST['rev']; + //sanitize revision + $REV = preg_replace('/[^0-9]/','',$REV); + + list($EXT,$MIME,$DL) = mimetype($MEDIA,false); + if($EXT === false){ + $EXT = 'unknown'; + $MIME = 'application/octet-stream'; + $DL = true; + } + + // check for permissions, preconditions and cache external files + list($STATUS, $STATUSMESSAGE) = checkFileStatus($MEDIA, $FILE, $REV); + + // prepare data for plugin events + $data = array('media' => $MEDIA, + 'file' => $FILE, + 'orig' => $FILE, + 'mime' => $MIME, + 'download' => $DL, + 'cache' => $CACHE, + 'ext' => $EXT, + 'width' => $WIDTH, + 'height' => $HEIGHT, + 'status' => $STATUS, + 'statusmessage' => $STATUSMESSAGE, + ); + + // handle the file status + $evt = new Doku_Event('FETCH_MEDIA_STATUS', $data); + if ( $evt->advise_before() ) { + // redirects + if($data['status'] > 300 && $data['status'] <= 304){ + send_redirect($data['statusmessage']); + } + // send any non 200 status + if($data['status'] != 200){ + header('HTTP/1.0 ' . $data['status'] . ' ' . $data['statusmessage']); + } + // die on errors + if($data['status'] > 203){ + print $data['statusmessage']; + exit; + } + } + $evt->advise_after(); + unset($evt); + + //handle image resizing/cropping + if((substr($MIME,0,5) == 'image') && $WIDTH){ + if($HEIGHT){ + $data['file'] = $FILE = media_crop_image($data['file'],$EXT,$WIDTH,$HEIGHT); + }else{ + $data['file'] = $FILE = media_resize_image($data['file'],$EXT,$WIDTH,$HEIGHT); + } + } + + // finally send the file to the client + $evt = new Doku_Event('MEDIA_SENDFILE', $data); + if ($evt->advise_before()) { + sendFile($data['file'],$data['mime'],$data['download'],$data['cache']); + } + // Do something after the download finished. + $evt->advise_after(); + +/* ------------------------------------------------------------------------ */ + +/** + * Set headers and send the file to the client + * + * @author Andreas Gohr <andi@splitbrain.org> + * @author Ben Coburn <btcoburn@silicodon.net> + */ +function sendFile($file,$mime,$dl,$cache){ + global $conf; + $fmtime = @filemtime($file); + // send headers + header("Content-Type: $mime"); + // smart http caching headers + if ($cache==-1) { + // cache + // cachetime or one hour + header('Expires: '.gmdate("D, d M Y H:i:s", time()+max($conf['cachetime'], 3600)).' GMT'); + header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.max($conf['cachetime'], 3600)); + header('Pragma: public'); + } else if ($cache>0) { + // recache + // remaining cachetime + 10 seconds so the newly recached media is used + header('Expires: '.gmdate("D, d M Y H:i:s", $fmtime+$conf['cachetime']+10).' GMT'); + header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.max($fmtime-time()+$conf['cachetime']+10, 0)); + header('Pragma: public'); + } else if ($cache==0) { + // nocache + header('Cache-Control: must-revalidate, no-transform, post-check=0, pre-check=0'); + header('Pragma: public'); + } + //send important headers first, script stops here if '304 Not Modified' response + http_conditionalRequest($fmtime); + + + //download or display? + if($dl){ + header('Content-Disposition: attachment; filename="'.basename($file).'";'); + }else{ + header('Content-Disposition: inline; filename="'.basename($file).'";'); + } + + //use x-sendfile header to pass the delivery to compatible webservers + if (http_sendfile($file)) exit; + + // send file contents + $fp = @fopen($file,"rb"); + if($fp){ + http_rangeRequest($fp,filesize($file),$mime); + }else{ + header("HTTP/1.0 500 Internal Server Error"); + print "Could not read $file - bad permissions?"; + } +} + +/** + * 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 $media reference to the media id + * @param $file reference to the file variable + * @returns array(STATUS, STATUSMESSAGE) + */ +function checkFileStatus(&$media, &$file, $rev='') { + global $MIME, $EXT, $CACHE; + + //media to local file + if(preg_match('#^(https?)://#i',$media)){ + //check hash + if(substr(md5(auth_cookiesalt().$media),0,6) != $_REQUEST['hash']){ + 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 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> + */ +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 +} + +//Setup VIM: ex: et ts=2 : diff --git a/lib/exe/index.html b/lib/exe/index.html new file mode 100644 index 000000000..d614603ac --- /dev/null +++ b/lib/exe/index.html @@ -0,0 +1,12 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="refresh" content="0; URL=../../" /> +<meta name="robots" content="noindex" /> +<title>nothing here...</title> +</head> +<body> +<!-- this is just here to prevent directory browsing --> +</body> +</html> diff --git a/lib/exe/indexer.php b/lib/exe/indexer.php new file mode 100644 index 000000000..95e2af05b --- /dev/null +++ b/lib/exe/indexer.php @@ -0,0 +1,271 @@ +<?php +/** + * DokuWiki indexer + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Andreas Gohr <andi@splitbrain.org> + */ +if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); +define('DOKU_DISABLE_GZIP_OUTPUT',1); +require_once(DOKU_INC.'inc/init.php'); +session_write_close(); //close session +if(!defined('NL')) define('NL',"\n"); + +// 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']; +if(!$defer){ + sendGIF(); // send gif +} + +$ID = cleanID($_REQUEST['id']); + +// Catch any possible output (e.g. errors) +$output = isset($_REQUEST['debug']) && $conf['allowdebug']; +if(!$output) ob_start(); + +// run one of the jobs +$tmp = array(); // No event data +$evt = new Doku_Event('INDEXER_TASKS_RUN', $tmp); +if ($evt->advise_before()) { + runIndexer() or + runSitemapper() or + sendDigest() or + runTrimRecentChanges() or + runTrimRecentChanges(true) or + $evt->advise_after(); +} +if($defer) sendGIF(); + +if(!$output) ob_end_clean(); +exit; + +// -------------------------------------------------------------------- + +/** + * Trims the recent changes cache (or imports the old changelog) as needed. + * + * @param media_changes If the media changelog shall be trimmed instead of + * the page changelog + * + * @author Ben Coburn <btcoburn@silicodon.net> + */ +function runTrimRecentChanges($media_changes = false) { + global $conf; + + $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); + return false; + } + + io_saveFile($fn.'_tmp', ''); // presave tmp as 2nd lock + $trim_time = time() - $conf['recent_days']*86400; + $out_lines = array(); + + for ($i=0; $i<count($lines); $i++) { + $log = parseChangelogLine($lines[$i]); + if ($log === false) continue; // discard junk + if ($log['date'] < $trim_time) { + $old_lines[$log['date'].".$i"] = $lines[$i]; // keep old lines for now (append .$i to prevent key collisions) + } else { + $out_lines[$log['date'].".$i"] = $lines[$i]; // definitely keep these lines + } + } + + if (count($lines)==count($out_lines)) { + // nothing to trim + @unlink($fn.'_tmp'); + io_unlock($fn); + 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); + } + + // 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); + } + return true; + } + + // nothing done + return false; +} + +/** + * Runs the indexer for the current page + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function runIndexer(){ + global $ID; + global $conf; + print "runIndexer(): started".NL; + + if(!$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 + */ +function runSitemapper(){ + print "runSitemapper(): started".NL; + $result = Sitemapper::generate() && Sitemapper::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> + */ +function sendDigest() { + echo 'sendDigest(): start'.NL; + global $ID; + global $conf; + if (!$conf['subscribers']) { + return; + } + $subscriptions = subscription_find($ID, array('style' => '(digest|list)', + 'escaped' => true)); + global $auth; + global $lang; + global $conf; + global $USERINFO; + + // remember current user info + $olduinfo = $USERINFO; + $olduser = $_SERVER['REMOTE_USER']; + + foreach($subscriptions as $id => $users) { + if (!subscription_lock($id)) { + continue; + } + foreach($users as $data) { + list($user, $style, $lastupdate) = $data; + $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); + $_SERVER['REMOTE_USER'] = $user; + if ($USERINFO === false) { + continue; + } + + if (substr($id, -1, 1) === ':') { + // The subscription target is a namespace + $changes = getRecentsSince($lastupdate, null, getNS($id)); + } else { + if(auth_quickaclcheck($id) < AUTH_READ) continue; + + $meta = p_get_metadata($id); + $changes = array($meta['last_change']); + } + + // Filter out pages only changed in small and own edits + $change_ids = array(); + foreach($changes as $rev) { + $n = 0; + while (!is_null($rev) && $rev['date'] >= $lastupdate && + ($_SERVER['REMOTE_USER'] === $rev['user'] || + $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)) { + $rev = getRevisions($rev['id'], $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']; + } + } + + if ($style === 'digest') { + foreach($change_ids as $change_id) { + subscription_send_digest($USERINFO['mail'], $change_id, + $lastupdate); + } + } elseif ($style === 'list') { + subscription_send_list($USERINFO['mail'], $change_ids, $id); + } + // TODO: Handle duplicate subscriptions. + + // Update notification time. + subscription_set($user, $id, $style, time(), true); + } + subscription_unlock($id); + } + + // restore current user info + $USERINFO = $olduinfo; + $_SERVER['REMOTE_USER'] = $olduser; +} + +/** + * Just send a 1x1 pixel blank gif to the browser + * + * @author Andreas Gohr <andi@splitbrain.org> + * @author Harry Fuecks <fuecks@gmail.com> + */ +function sendGIF(){ + if(isset($_REQUEST['debug'])){ + header('Content-Type: text/plain'); + return; + } + $img = base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7'); + header('Content-Type: image/gif'); + header('Content-Length: '.strlen($img)); + header('Connection: Close'); + print $img; + flush(); + // Browser should drop connection after this + // Thinks it's got the whole image +} + +//Setup VIM: ex: et ts=4 : +// No trailing PHP closing tag - no output please! +// See Note at http://www.php.net/manual/en/language.basic-syntax.instruction-separation.php diff --git a/lib/exe/js.php b/lib/exe/js.php new file mode 100644 index 000000000..963eebd5f --- /dev/null +++ b/lib/exe/js.php @@ -0,0 +1,358 @@ +<?php +/** + * DokuWiki JavaScript creator + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Andreas Gohr <andi@splitbrain.org> + */ + +if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); +if(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching) +if(!defined('NL')) define('NL',"\n"); +if(!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT',1); // we gzip ourself here +require_once(DOKU_INC.'inc/init.php'); + +// Main (don't run when UNIT test) +if(!defined('SIMPLE_TEST')){ + header('Content-Type: text/javascript; charset=utf-8'); + js_out(); +} + + +// ---------------------- functions ------------------------------ + +/** + * Output all needed JavaScript + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function js_out(){ + global $conf; + global $lang; + global $config_cascade; + + // The generated script depends on some dynamic options + $cache = new cache('scripts'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'], + '.js'); + + // load minified version for some files + $min = $conf['compress'] ? '.min' : ''; + + // array of core files + $files = array( + DOKU_INC."lib/scripts/jquery/jquery$min.js", + DOKU_INC.'lib/scripts/jquery/jquery.cookie.js', + DOKU_INC."lib/scripts/jquery/jquery-ui$min.js", + DOKU_INC."lib/scripts/fileuploader.js", + DOKU_INC."lib/scripts/fileuploaderextended.js", + DOKU_INC.'lib/scripts/helpers.js', + DOKU_INC.'lib/scripts/delay.js', + DOKU_INC.'lib/scripts/cookie.js', + DOKU_INC.'lib/scripts/script.js', + DOKU_INC.'lib/scripts/tw-sack.js', + DOKU_INC.'lib/scripts/qsearch.js', + DOKU_INC.'lib/scripts/tree.js', + DOKU_INC.'lib/scripts/index.js', + DOKU_INC.'lib/scripts/drag.js', + DOKU_INC.'lib/scripts/textselection.js', + DOKU_INC.'lib/scripts/toolbar.js', + DOKU_INC.'lib/scripts/edit.js', + DOKU_INC.'lib/scripts/editor.js', + DOKU_INC.'lib/scripts/locktimer.js', + DOKU_INC.'lib/scripts/linkwiz.js', + DOKU_INC.'lib/scripts/media.js', + DOKU_INC.'lib/scripts/compatibility.js', +# disabled for FS#1958 DOKU_INC.'lib/scripts/hotkeys.js', + DOKU_INC.'lib/scripts/behaviour.js', + DOKU_INC.'lib/scripts/page.js', + tpl_incdir().'script.js', + ); + + // add possible plugin scripts and userscript + $files = array_merge($files,js_pluginscripts()); + if(isset($config_cascade['userscript']['default'])){ + $files[] = $config_cascade['userscript']['default']; + } + + $cache_files = array_merge($files, getConfigFiles('main')); + $cache_files[] = __FILE__; + + // check cache age & handle conditional request + // This may exit if a cache can be used + http_cached($cache->cache, + $cache->useCache(array('files' => $cache_files))); + + // start output buffering and build the script + ob_start(); + + // add some global variables + print "var DOKU_BASE = '".DOKU_BASE."';"; + print "var DOKU_TPL = '".tpl_basedir()."';"; + // FIXME: Move those to JSINFO + print "var DOKU_UHN = ".((int) useHeading('navigation')).";"; + print "var DOKU_UHC = ".((int) useHeading('content')).";"; + + // load JS specific translations + $json = new JSON(); + $lang['js']['plugins'] = js_pluginstrings(); + echo 'LANG = '.$json->encode($lang['js']).";\n"; + + // load toolbar + toolbar_JSdefines('toolbar'); + + // load files + foreach($files as $file){ + echo "\n\n/* XXXXXXXXXX begin of ".str_replace(DOKU_INC, '', $file) ." XXXXXXXXXX */\n\n"; + js_load($file); + echo "\n\n/* XXXXXXXXXX end of " . str_replace(DOKU_INC, '', $file) . " XXXXXXXXXX */\n\n"; + } + + // init stuff + if($conf['locktime'] != 0){ + js_runonstart("dw_locktimer.init(".($conf['locktime'] - 60).",".$conf['usedraft'].")"); + } + // init hotkeys - must have been done after init of toolbar +# disabled for FS#1958 js_runonstart('initializeHotkeys()'); + + // end output buffering and get contents + $js = ob_get_contents(); + ob_end_clean(); + + // compress whitespace and comments + if($conf['compress']){ + $js = js_compress($js); + } + + $js .= "\n"; // https://bugzilla.mozilla.org/show_bug.cgi?id=316033 + + http_cached_finish($cache->cache, $js); +} + +/** + * Load the given file, handle include calls and print it + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function js_load($file){ + if(!@file_exists($file)) return; + static $loaded = array(); + + $data = io_readFile($file); + while(preg_match('#/\*\s*DOKUWIKI:include(_once)?\s+([\w\.\-_/]+)\s*\*/#',$data,$match)){ + $ifile = $match[2]; + + // is it a include_once? + if($match[1]){ + $base = basename($ifile); + if($loaded[$base]) continue; + $loaded[$base] = true; + } + + if($ifile{0} != '/') $ifile = dirname($file).'/'.$ifile; + + if(@file_exists($ifile)){ + $idata = io_readFile($ifile); + }else{ + $idata = ''; + } + $data = str_replace($match[0],$idata,$data); + } + echo "$data\n"; +} + +/** + * Returns a list of possible Plugin Scripts (no existance check here) + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function js_pluginscripts(){ + $list = array(); + $plugins = plugin_list(); + foreach ($plugins as $p){ + $list[] = DOKU_PLUGIN."$p/script.js"; + } + return $list; +} + +/** + * Return an two-dimensional array with strings from the language file of each plugin. + * + * - $lang['js'] must be an array. + * - Nothing is returned for plugins without an entry for $lang['js'] + * + * @author Gabriel Birke <birke@d-scribe.de> + */ +function js_pluginstrings() +{ + global $conf; + $pluginstrings = array(); + $plugins = plugin_list(); + foreach ($plugins as $p){ + if (isset($lang)) unset($lang); + if (@file_exists(DOKU_PLUGIN."$p/lang/en/lang.php")) { + include DOKU_PLUGIN."$p/lang/en/lang.php"; + } + if (isset($conf['lang']) && $conf['lang']!='en' && @file_exists(DOKU_PLUGIN."$p/lang/".$conf['lang']."/lang.php")) { + include DOKU_PLUGIN."$p/lang/".$conf['lang']."/lang.php"; + } + if (isset($lang['js'])) { + $pluginstrings[$p] = $lang['js']; + } + } + return $pluginstrings; +} + +/** + * Escapes a String to be embedded in a JavaScript call, keeps \n + * as newline + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function js_escape($string){ + return str_replace('\\\\n','\\n',addslashes($string)); +} + +/** + * Adds the given JavaScript code to the window.onload() event + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function js_runonstart($func){ + echo "jQuery(function(){ $func; });".NL; +} + +/** + * Strip comments and whitespaces from given JavaScript Code + * + * This is a port of Nick Galbreath's python tool jsstrip.py which is + * released under BSD license. See link for original code. + * + * @author Nick Galbreath <nickg@modp.com> + * @author Andreas Gohr <andi@splitbrain.org> + * @link http://code.google.com/p/jsstrip/ + */ +function js_compress($s){ + $s = ltrim($s); // strip all initial whitespace + $s .= "\n"; + $i = 0; // char index for input string + $j = 0; // char forward index for input string + $line = 0; // line number of file (close to it anyways) + $slen = strlen($s); // size of input string + $lch = ''; // last char added + $result = ''; // we store the final result here + + // items that don't need spaces next to them + $chars = "^&|!+\-*\/%=\?:;,{}()<>% \t\n\r'\"[]"; + + $regex_starters = array("(", "=", "[", "," , ":", "!"); + + $whitespaces_chars = array(" ", "\t", "\n", "\r", "\0", "\x0B"); + + while($i < $slen){ + // skip all "boring" characters. This is either + // reserved word (e.g. "for", "else", "if") or a + // variable/object/method (e.g. "foo.color") + while ($i < $slen && (strpos($chars,$s[$i]) === false) ){ + $result .= $s{$i}; + $i = $i + 1; + } + + $ch = $s{$i}; + // multiline comments (keeping IE conditionals) + if($ch == '/' && $s{$i+1} == '*' && $s{$i+2} != '@'){ + $endC = strpos($s,'*/',$i+2); + if($endC === false) trigger_error('Found invalid /*..*/ comment', E_USER_ERROR); + $i = $endC + 2; + continue; + } + + // singleline + if($ch == '/' && $s{$i+1} == '/'){ + $endC = strpos($s,"\n",$i+2); + if($endC === false) trigger_error('Invalid comment', E_USER_ERROR); + $i = $endC; + continue; + } + + // tricky. might be an RE + if($ch == '/'){ + // rewind, skip white space + $j = 1; + while(in_array($s{$i-$j}, $whitespaces_chars)){ + $j = $j + 1; + } + if( in_array($s{$i-$j}, $regex_starters) ){ + // yes, this is an re + // now move forward and find the end of it + $j = 1; + while($s{$i+$j} != '/'){ + while( ($s{$i+$j} != '\\') && ($s{$i+$j} != '/')){ + $j = $j + 1; + } + if($s{$i+$j} == '\\') $j = $j + 2; + } + $result .= substr($s,$i,$j+1); + $i = $i + $j + 1; + continue; + } + } + + // double quote strings + if($ch == '"'){ + $j = 1; + while( $s{$i+$j} != '"' && ($i+$j < $slen)){ + if( $s{$i+$j} == '\\' && ($s{$i+$j+1} == '"' || $s{$i+$j+1} == '\\') ){ + $j += 2; + }else{ + $j += 1; + } + } + $result .= substr($s,$i,$j+1); + $i = $i + $j + 1; + continue; + } + + // single quote strings + if($ch == "'"){ + $j = 1; + while( $s{$i+$j} != "'" && ($i+$j < $slen)){ + if( $s{$i+$j} == '\\' && ($s{$i+$j+1} == "'" || $s{$i+$j+1} == '\\') ){ + $j += 2; + }else{ + $j += 1; + } + } + $result .= substr($s,$i,$j+1); + $i = $i + $j + 1; + continue; + } + + // whitespaces + if( $ch == ' ' || $ch == "\r" || $ch == "\n" || $ch == "\t" ){ + // leading spaces + if($i+1 < $slen && (strpos($chars,$s[$i+1]) !== false)){ + $i = $i + 1; + continue; + } + // trailing spaces + // if this ch is space AND the last char processed + // is special, then skip the space + $lch = substr($result,-1); + if($lch && (strpos($chars,$lch) !== false)){ + $i = $i + 1; + continue; + } + // else after all of this convert the "whitespace" to + // a single space. It will get appended below + $ch = ' '; + } + + // other chars + $result .= $ch; + $i = $i + 1; + } + + return trim($result); +} + +//Setup VIM: ex: et ts=4 : diff --git a/lib/exe/mediamanager.php b/lib/exe/mediamanager.php new file mode 100644 index 000000000..5f09fe1f8 --- /dev/null +++ b/lib/exe/mediamanager.php @@ -0,0 +1,122 @@ +<?php + if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); + define('DOKU_MEDIAMANAGER',1); + + // for multi uploader: + @ini_set('session.use_only_cookies',0); + + require_once(DOKU_INC.'inc/init.php'); + + trigger_event('MEDIAMANAGER_STARTED',$tmp=array()); + session_write_close(); //close session + + // handle passed message + if($_REQUEST['msg1']) msg(hsc($_REQUEST['msg1']),1); + if($_REQUEST['err']) msg(hsc($_REQUEST['err']),-1); + + + // get namespace to display (either direct or from deletion order) + if($_REQUEST['delete']){ + $DEL = cleanID($_REQUEST['delete']); + $IMG = $DEL; + $NS = getNS($DEL); + }elseif($_REQUEST['edit']){ + $IMG = cleanID($_REQUEST['edit']); + $NS = getNS($IMG); + }elseif($_REQUEST['img']){ + $IMG = cleanID($_REQUEST['img']); + $NS = getNS($IMG); + }else{ + $NS = $_REQUEST['ns']; + $NS = cleanID($NS); + } + + // check auth + $AUTH = auth_quickaclcheck("$NS:*"); + + // do not display the manager if user does not have read access + if($AUTH < AUTH_READ && !$fullscreen) { + header('HTTP/1.0 403 Forbidden'); + die($lang['accessdenied']); + } + + // create the given namespace (just for beautification) + if($AUTH >= AUTH_UPLOAD) { io_createNamespace("$NS:xxx", 'media'); } + + // handle flash upload + if(isset($_FILES['Filedata'])){ + $_FILES['upload'] =& $_FILES['Filedata']; + $JUMPTO = media_upload($NS,$AUTH); + if($JUMPTO == false){ + header("HTTP/1.0 400 Bad Request"); + echo 'Upload failed'; + } + echo 'ok'; + exit; + } + + // give info on PHP catched upload errors + if($_FILES['upload']['error']){ + switch($_FILES['upload']['error']){ + case 1: + case 2: + msg(sprintf($lang['uploadsize'], + filesize_h(php_to_byte(ini_get('upload_max_filesize')))),-1); + break; + default: + msg($lang['uploadfail'].' ('.$_FILES['upload']['error'].')',-1); + } + unset($_FILES['upload']); + } + + // handle upload + if($_FILES['upload']['tmp_name']){ + $JUMPTO = media_upload($NS,$AUTH); + if($JUMPTO) $NS = getNS($JUMPTO); + } + + // handle meta saving + if($IMG && @array_key_exists('save', $_REQUEST['do'])){ + $JUMPTO = media_metasave($IMG,$AUTH,$_REQUEST['meta']); + } + + if($IMG && ($_REQUEST['mediado'] == 'save' || @array_key_exists('save', $_REQUEST['mediado']))) { + $JUMPTO = media_metasave($IMG,$AUTH,$_REQUEST['meta']); + } + + if ($_REQUEST['rev'] && $conf['mediarevisions']) $REV = (int) $_REQUEST['rev']; + + if($_REQUEST['mediado'] == 'restore' && $conf['mediarevisions']){ + $JUMPTO = media_restore($_REQUEST['image'], $REV, $AUTH); + } + + // handle deletion + if($DEL) { + $res = 0; + if(checkSecurityToken()) { + $res = media_delete($DEL,$AUTH); + } + if ($res & DOKU_MEDIA_DELETED) { + $msg = sprintf($lang['deletesucc'], noNS($DEL)); + if ($res & DOKU_MEDIA_EMPTY_NS && !$fullscreen) { + // current namespace was removed. redirecting to root ns passing msg along + send_redirect(DOKU_URL.'lib/exe/mediamanager.php?msg1='. + rawurlencode($msg).'&edid='.$_REQUEST['edid']); + } + msg($msg,1); + } elseif ($res & DOKU_MEDIA_INUSE) { + if(!$conf['refshow']) { + msg(sprintf($lang['mediainuse'],noNS($DEL)),0); + } + } else { + msg(sprintf($lang['deletefail'],noNS($DEL)),-1); + } + } + // finished - start output + + if (!$fullscreen) { + header('Content-Type: text/html; charset=utf-8'); + include(template('mediamanager.php')); + } + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ diff --git a/lib/exe/opensearch.php b/lib/exe/opensearch.php new file mode 100644 index 000000000..73939c347 --- /dev/null +++ b/lib/exe/opensearch.php @@ -0,0 +1,38 @@ +<?php +/** + * DokuWiki OpenSearch creator + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @link http://www.opensearch.org/ + * @author Mike Frysinger <vapier@gentoo.org> + * @author Andreas Gohr <andi@splitbrain.org> + */ + +if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); +if(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching) +if(!defined('NL')) define('NL',"\n"); +require_once(DOKU_INC.'inc/init.php'); + +// try to be clever about the favicon location +if(file_exists(DOKU_INC.'favicon.ico')){ + $ico = DOKU_URL.'favicon.ico'; +}elseif(file_exists(tpl_incdir().'images/favicon.ico')){ + $ico = DOKU_URL.'lib/tpl/'.$conf['template'].'/images/favicon.ico'; +}elseif(file_exists(tpl_incdir().'favicon.ico')){ + $ico = DOKU_URL.'lib/tpl/'.$conf['template'].'/favicon.ico'; +}else{ + $ico = DOKU_URL.'lib/tpl/default/images/favicon.ico'; +} + +// output +header('Content-Type: application/opensearchdescription+xml; charset=utf-8'); +echo '<?xml version="1.0"?>'.NL; +echo '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">'.NL; +echo ' <ShortName>'.htmlspecialchars($conf['title']).'</ShortName>'.NL; +echo ' <Image width="16" height="16" type="image/x-icon">'.$ico.'</Image>'.NL; +echo ' <Url type="text/html" template="'.DOKU_URL.DOKU_SCRIPT.'?do=search&id={searchTerms}" />'.NL; +echo ' <Url type="application/x-suggestions+json" template="'. + DOKU_URL.'lib/exe/ajax.php?call=suggestions&q={searchTerms}" />'.NL; +echo '</OpenSearchDescription>'.NL; + +//Setup VIM: ex: et ts=4 : diff --git a/lib/exe/xmlrpc.php b/lib/exe/xmlrpc.php new file mode 100644 index 000000000..1264ff333 --- /dev/null +++ b/lib/exe/xmlrpc.php @@ -0,0 +1,888 @@ +<?php +if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); + +// fix when '< ?xml' isn't on the very first line +if(isset($HTTP_RAW_POST_DATA)) $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA); + +/** + * Increased whenever the API is changed + */ +define('DOKU_XMLRPC_API_VERSION', 7); + +require_once(DOKU_INC.'inc/init.php'); +session_write_close(); //close session + +if(!$conf['xmlrpc']) die('XML-RPC server not enabled.'); + +/** + * Contains needed wrapper functions and registers all available + * XMLRPC functions. + */ +class dokuwiki_xmlrpc_server extends IXR_IntrospectionServer { + var $methods = array(); + var $public_methods = array(); + + /** + * Checks if the current user is allowed to execute non anonymous methods + */ + function checkAuth(){ + global $conf; + global $USERINFO; + + if(!$conf['useacl']) return true; //no ACL - then no checks + if(trim($conf['xmlrpcuser']) == '') return true; //no restrictions + + return auth_isMember($conf['xmlrpcuser'],$_SERVER['REMOTE_USER'],(array) $USERINFO['grps']); + } + + /** + * Adds a callback, extends parent method + * + * add another parameter to define if anonymous access to + * this method should be granted. + */ + function addCallback($method, $callback, $args, $help, $public=false){ + if($public) $this->public_methods[] = $method; + return parent::addCallback($method, $callback, $args, $help); + } + + /** + * Execute a call, extends parent method + * + * Checks for authentication first + */ + function call($methodname, $args){ + if(!in_array($methodname,$this->public_methods) && !$this->checkAuth()){ + if (!isset($_SERVER['REMOTE_USER'])) { + header('HTTP/1.1 401 Unauthorized'); + } else { + header('HTTP/1.1 403 Forbidden'); + } + return new IXR_Error(-32603, 'server error. not authorized to call method "'.$methodname.'".'); + } + return parent::call($methodname, $args); + } + + /** + * Constructor. Register methods and run Server + */ + function dokuwiki_xmlrpc_server(){ + $this->IXR_IntrospectionServer(); + + /* DokuWiki's own methods */ + $this->addCallback( + 'dokuwiki.getXMLRPCAPIVersion', + 'this:getAPIVersion', + array('integer'), + 'Returns the XMLRPC API version.', + true + ); + + $this->addCallback( + 'dokuwiki.getVersion', + 'getVersion', + array('string'), + 'Returns the running DokuWiki version.', + true + ); + + $this->addCallback( + 'dokuwiki.login', + 'this:login', + array('integer','string','string'), + 'Tries to login with the given credentials and sets auth cookies.', + true + ); + + $this->addCallback( + 'dokuwiki.getPagelist', + 'this:readNamespace', + array('struct','string','struct'), + 'List all pages within the given namespace.' + ); + + $this->addCallback( + 'dokuwiki.search', + 'this:search', + array('struct','string'), + 'Perform a fulltext search and return a list of matching pages' + ); + + $this->addCallback( + 'dokuwiki.getTime', + 'time', + array('int'), + 'Return the current time at the wiki server.' + ); + + $this->addCallback( + 'dokuwiki.setLocks', + 'this:setLocks', + array('struct','struct'), + 'Lock or unlock pages.' + ); + + + $this->addCallback( + 'dokuwiki.getTitle', + 'this:getTitle', + array('string'), + 'Returns the wiki title.', + true + ); + + $this->addCallback( + 'dokuwiki.appendPage', + 'this:appendPage', + array('int', 'string', 'string', 'struct'), + 'Append text to a wiki page.' + ); + + /* Wiki API v2 http://www.jspwiki.org/wiki/WikiRPCInterface2 */ + $this->addCallback( + 'wiki.getRPCVersionSupported', + 'this:wiki_RPCVersion', + array('int'), + 'Returns 2 with the supported RPC API version.', + true + ); + $this->addCallback( + 'wiki.getPage', + 'this:rawPage', + array('string','string'), + 'Get the raw Wiki text of page, latest version.' + ); + $this->addCallback( + 'wiki.getPageVersion', + 'this:rawPage', + array('string','string','int'), + 'Get the raw Wiki text of page.' + ); + $this->addCallback( + 'wiki.getPageHTML', + 'this:htmlPage', + array('string','string'), + 'Return page in rendered HTML, latest version.' + ); + $this->addCallback( + 'wiki.getPageHTMLVersion', + 'this:htmlPage', + array('string','string','int'), + 'Return page in rendered HTML.' + ); + $this->addCallback( + 'wiki.getAllPages', + 'this:listPages', + array('struct'), + 'Returns a list of all pages. The result is an array of utf8 pagenames.' + ); + $this->addCallback( + 'wiki.getAttachments', + 'this:listAttachments', + array('struct', 'string', 'struct'), + 'Returns a list of all media files.' + ); + $this->addCallback( + 'wiki.getBackLinks', + 'this:listBackLinks', + array('struct','string'), + 'Returns the pages that link to this page.' + ); + $this->addCallback( + 'wiki.getPageInfo', + 'this:pageInfo', + array('struct','string'), + 'Returns a struct with infos about the page.' + ); + $this->addCallback( + 'wiki.getPageInfoVersion', + 'this:pageInfo', + array('struct','string','int'), + 'Returns a struct with infos about the page.' + ); + $this->addCallback( + 'wiki.getPageVersions', + 'this:pageVersions', + array('struct','string','int'), + 'Returns the available revisions of the page.' + ); + $this->addCallback( + 'wiki.putPage', + 'this:putPage', + array('int', 'string', 'string', 'struct'), + 'Saves a wiki page.' + ); + $this->addCallback( + 'wiki.listLinks', + 'this:listLinks', + array('struct','string'), + 'Lists all links contained in a wiki page.' + ); + $this->addCallback( + 'wiki.getRecentChanges', + 'this:getRecentChanges', + array('struct','int'), + 'Returns a struct about all recent changes since given timestamp.' + ); + $this->addCallback( + 'wiki.getRecentMediaChanges', + 'this:getRecentMediaChanges', + array('struct','int'), + 'Returns a struct about all recent media changes since given timestamp.' + ); + $this->addCallback( + 'wiki.aclCheck', + 'this:aclCheck', + array('int', 'string'), + 'Returns the permissions of a given wiki page.' + ); + $this->addCallback( + 'wiki.putAttachment', + 'this:putAttachment', + array('struct', 'string', 'base64', 'struct'), + 'Upload a file to the wiki.' + ); + $this->addCallback( + 'wiki.deleteAttachment', + 'this:deleteAttachment', + array('int', 'string'), + 'Delete a file from the wiki.' + ); + $this->addCallback( + 'wiki.getAttachment', + 'this:getAttachment', + array('base64', 'string'), + 'Download a file from the wiki.' + ); + $this->addCallback( + 'wiki.getAttachmentInfo', + 'this:getAttachmentInfo', + array('struct', 'string'), + 'Returns a struct with infos about the attachment.' + ); + + /** + * Trigger XMLRPC_CALLBACK_REGISTER, action plugins can use this event + * to extend the XMLRPC interface and register their own callbacks. + * + * Event data: + * The XMLRPC server object: + * + * $event->data->addCallback() - register a callback, the second + * paramter has to be of the form "plugin:<pluginname>:<plugin + * method>" + * + * $event->data->callbacks - an array which holds all awaylable + * callbacks + */ + trigger_event('XMLRPC_CALLBACK_REGISTER', $this); + + $this->serve(); + } + + /** + * Return a raw wiki page + */ + function rawPage($id,$rev=''){ + $id = cleanID($id); + if(auth_quickaclcheck($id) < AUTH_READ){ + return new IXR_Error(111, 'You are not allowed to read this page'); + } + $text = rawWiki($id,$rev); + if(!$text) { + return pageTemplate($id); + } else { + return $text; + } + } + + /** + * Return a media file encoded in base64 + * + * @author Gina Haeussge <osd@foosel.net> + */ + function getAttachment($id){ + $id = cleanID($id); + if (auth_quickaclcheck(getNS($id).':*') < AUTH_READ) + return new IXR_Error(211, 'You are not allowed to read this file'); + + $file = mediaFN($id); + if (!@ file_exists($file)) + return new IXR_Error(221, 'The requested file does not exist'); + + $data = io_readFile($file, false); + $base64 = new IXR_Base64($data); + return $base64; + } + + /** + * Return info about a media file + * + * @author Gina Haeussge <osd@foosel.net> + */ + function getAttachmentInfo($id){ + $id = cleanID($id); + $info = array( + 'lastModified' => 0, + 'size' => 0, + ); + + $file = mediaFN($id); + if ((auth_quickaclcheck(getNS($id).':*') >= AUTH_READ) && file_exists($file)){ + $info['lastModified'] = new IXR_Date(filemtime($file)); + $info['size'] = filesize($file); + } + + return $info; + } + + /** + * Return a wiki page rendered to html + */ + function htmlPage($id,$rev=''){ + $id = cleanID($id); + if(auth_quickaclcheck($id) < AUTH_READ){ + return new IXR_Error(111, 'You are not allowed to read this page'); + } + return p_wiki_xhtml($id,$rev,false); + } + + /** + * List all pages - we use the indexer list here + */ + 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'] = new IXR_Date(@filemtime(wikiFN($pages[$idx]))); + $list[] = $page; + } + + return $list; + } + + /** + * List all pages in the given namespace (and below) + */ + function readNamespace($ns,$opts){ + 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) + */ + function search($query){ + require_once(DOKU_INC.'inc/fulltext.php'); + + $regex = ''; + $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, + ); + } + return $pages; + } + + /** + * Returns the wiki title. + */ + 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> + */ + function listAttachments($ns, $options = array()) { + global $conf; + global $lang; + + $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]['lastModified'] = new IXR_Date($data[$i]['mtime']); + } + return $data; + } else { + return new IXR_Error(215, 'You are not allowed to list media files.'); + } + } + + /** + * Return a list of backlinks + */ + function listBackLinks($id){ + return ft_backlinks(cleanID($id)); + } + + /** + * Return some basic data about a page + */ + function pageInfo($id,$rev=''){ + $id = cleanID($id); + if(auth_quickaclcheck($id) < AUTH_READ){ + return new IXR_Error(111, 'You are not allowed to read this page'); + } + $file = wikiFN($id,$rev); + $time = @filemtime($file); + if(!$time){ + return new IXR_Error(121, 'The requested page does not exist'); + } + + $info = getRevisionInfo($id, $time, 1024); + + $data = array( + 'name' => $id, + 'lastModified' => new IXR_Date($time), + 'author' => (($info['user']) ? $info['user'] : $info['ip']), + 'version' => $time + ); + + return ($data); + } + + /** + * Save a wiki page + * + * @author Michael Klier <chi@chimeric.de> + */ + function putPage($id, $text, $params) { + global $TEXT; + global $lang; + global $conf; + + $id = cleanID($id); + $TEXT = cleanText($text); + $sum = $params['sum']; + $minor = $params['minor']; + + if(empty($id)) + return new IXR_Error(131, 'Empty page ID'); + + if(!page_exists($id) && trim($TEXT) == '' ) { + return new IXR_ERROR(132, 'Refusing to write an empty new wiki page'); + } + + if(auth_quickaclcheck($id) < AUTH_EDIT) + return new IXR_Error(112, 'You are not allowed to edit this page'); + + // Check, if page is locked + if(checklock($id)) + return new IXR_Error(133, 'The page is currently locked'); + + // SPAM check + if(checkwordblock()) + return new IXR_Error(134, 'Positive wordblock check'); + + // 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 0; + } + + /** + * Appends text to a wiki page. + */ + function appendPage($id, $text, $params) { + $currentpage = $this->rawPage($id); + if (!is_string($currentpage)) { + return $currentpage; + } + return $this->putPage($id, $currentpage.$text, $params); + } + + /** + * Uploads a file to the wiki. + * + * Michael Klier <chi@chimeric.de> + */ + function putAttachment($id, $file, $params) { + $id = cleanID($id); + $auth = auth_quickaclcheck(getNS($id).':*'); + + if(!isset($id)) { + return new IXR_ERROR(231, 'Filename not given.'); + } + + global $conf; + + $ftmp = $conf['tmpdir'] . '/' . md5($id.clientIP()); + + // save temporary file + @unlink($ftmp); + if (preg_match('/^[A-Za-z0-9\+\/]*={0,2}$/', $file) === 1) { + // DEPRECATED: Double-decode file if it still looks like base64 + // after first decoding (which is done by the library) + $file = base64_decode($file); + } + io_saveFile($ftmp, $file); + + $res = media_save(array('name' => $ftmp), $id, $params['ow'], $auth, 'rename'); + if (is_array($res)) { + return new IXR_ERROR(-$res[1], $res[0]); + } else { + return $res; + } + } + + /** + * Deletes a file from the wiki. + * + * @author Gina Haeussge <osd@foosel.net> + */ + 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) { + return new IXR_ERROR(212, "You don't have permissions to delete files."); + } elseif ($res & DOKU_MEDIA_INUSE) { + return new IXR_ERROR(232, 'File is still referenced'); + } else { + return new IXR_ERROR(233, 'Could not delete file'); + } + } + + /** + * Returns the permissions of a given wiki page + */ + function aclCheck($id) { + $id = cleanID($id); + return auth_quickaclcheck($id); + } + + /** + * Lists all links contained in a wiki page + * + * @author Michael Klier <chi@chimeric.de> + */ + function listLinks($id) { + $id = cleanID($id); + if(auth_quickaclcheck($id) < AUTH_READ){ + return new IXR_Error(111, 'You are not allowed to read this page'); + } + $links = array(); + + // resolve page instructions + $ins = p_cached_instructions(wikiFN($id)); + + // instantiate new Renderer - needed for interwiki links + include(DOKU_INC.'inc/parser/xhtml.php'); + $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> + */ + function getRecentChanges($timestamp) { + if(strlen($timestamp) != 10) + return new IXR_Error(311, 'The provided value is not a valid timestamp'); + + $recents = getRecentsSince($timestamp); + + $changes = array(); + + foreach ($recents as $recent) { + $change = array(); + $change['name'] = $recent['id']; + $change['lastModified'] = new IXR_Date($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 + return new IXR_Error(321, 'There are no changes in the specified timeframe'); + } + } + + /** + * Returns a list of recent media changes since give timestamp + * + * @author Michael Hamann <michael@content-space.de> + * @author Michael Klier <chi@chimeric.de> + */ + function getRecentMediaChanges($timestamp) { + if(strlen($timestamp) != 10) + return new IXR_Error(311, 'The provided value is not a valid timestamp'); + + $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES); + + $changes = array(); + + foreach ($recents as $recent) { + $change = array(); + $change['name'] = $recent['id']; + $change['lastModified'] = new IXR_Date($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 + return new IXR_Error(321, 'There are no changes in the specified timeframe'); + } + } + + /** + * Returns a list of available revisions of a given wiki page + * + * @author Michael Klier <chi@chimeric.de> + */ + function pageVersions($id, $first) { + $id = cleanID($id); + if(auth_quickaclcheck($id) < AUTH_READ){ + return new IXR_Error(111, 'You are not allowed to read this page'); + } + global $conf; + + $versions = array(); + + if(empty($id)) + return new IXR_Error(131, 'Empty page ID'); + + $revisions = getRevisions($id, $first, $conf['recent']+1); + + if(count($revisions)==0 && $first!=0) { + $first=0; + $revisions = getRevisions($id, $first, $conf['recent']+1); + } + + if(count($revisions)>0 && $first==0) { + array_unshift($revisions, ''); // include current revision + array_pop($revisions); // remove extra log entry + } + + $hasNext = false; + if(count($revisions)>$conf['recent']) { + $hasNext = true; + 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){ + $info = getRevisionInfo($id, $time, 1024); + if(!empty($info)) { + $data['user'] = $info['user']; + $data['ip'] = $info['ip']; + $data['type'] = $info['type']; + $data['sum'] = $info['sum']; + $data['modified'] = new IXR_Date($info['date']); + $data['version'] = $info['date']; + array_push($versions, $data); + } + } + } + return $versions; + } else { + return array(); + } + } + + /** + * The version of Wiki RPC API supported + */ + function wiki_RPCVersion(){ + 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. + */ + function setLocks($set){ + $locked = array(); + $lockfail = array(); + $unlocked = array(); + $unlockfail = array(); + + foreach((array) $set['lock'] as $id){ + $id = cleanID($id); + if(auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)){ + $lockfail[] = $id; + }else{ + lock($id); + $locked[] = $id; + } + } + + foreach((array) $set['unlock'] as $id){ + $id = cleanID($id); + if(auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)){ + $unlockfail[] = $id; + }else{ + $unlocked[] = $id; + } + } + + return array( + 'locked' => $locked, + 'lockfail' => $lockfail, + 'unlocked' => $unlocked, + 'unlockfail' => $unlockfail, + ); + } + + function getAPIVersion(){ + return DOKU_XMLRPC_API_VERSION; + } + + function login($user,$pass){ + global $conf; + global $auth; + if(!$conf['useacl']) return 0; + if(!$auth) return 0; + + @session_start(); // reopen session for login + if($auth->canDo('external')){ + $ok = $auth->trustExternal($user,$pass,false); + }else{ + $evdata = array( + 'user' => $user, + 'password' => $pass, + 'sticky' => false, + 'silent' => true, + ); + $ok = trigger_event('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper'); + } + session_write_close(); // we're done with the session + + return $ok; + } + + +} + +$server = new dokuwiki_xmlrpc_server(); + +// vim:ts=4:sw=4:et: |