summaryrefslogtreecommitdiff
path: root/lib/exe
diff options
context:
space:
mode:
Diffstat (limited to 'lib/exe')
-rw-r--r--lib/exe/ajax.php435
-rw-r--r--lib/exe/css.php343
-rw-r--r--lib/exe/detail.php48
-rw-r--r--lib/exe/fetch.php205
-rw-r--r--lib/exe/index.html12
-rw-r--r--lib/exe/indexer.php271
-rw-r--r--lib/exe/js.php358
-rw-r--r--lib/exe/mediamanager.php122
-rw-r--r--lib/exe/opensearch.php38
-rw-r--r--lib/exe/xmlrpc.php888
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&amp;id={searchTerms}" />'.NL;
+echo ' <Url type="application/x-suggestions+json" template="'.
+ DOKU_URL.'lib/exe/ajax.php?call=suggestions&amp;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: