diff --git a/.gitignore b/.gitignore
index 6ad14d206163763202e8bc4ae8bd269c52bda931..f70efa66584272e181a6fadc9ee689cda535c22d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@
 /conf/words.aspell
 /conf/lang/*
 /conf/plugin_lang/*
+/conf/plugins.local.*
 .htaccess
 *.swp
 *.bak
diff --git a/_test/cases/inc/search/data/ns1/ns3/page3.txt b/_test/cases/inc/search/data/ns1/ns3/page3.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/_test/cases/inc/search/data/ns1/page1.txt b/_test/cases/inc/search/data/ns1/page1.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/_test/cases/inc/search/data/ns1/page2.txt b/_test/cases/inc/search/data/ns1/page2.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/_test/cases/inc/search/data/ns2/nopage.ext b/_test/cases/inc/search/data/ns2/nopage.ext
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/_test/cases/inc/search/data/ns2/page1.txt b/_test/cases/inc/search/data/ns2/page1.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/_test/cases/inc/search/search.test.php b/_test/cases/inc/search/search.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..a6f15e9b68eb7495887689c9bb8411bd78a79339
--- /dev/null
+++ b/_test/cases/inc/search/search.test.php
@@ -0,0 +1,102 @@
+<?php
+require_once DOKU_INC.'inc/search.php';
+
+class search_test extends UnitTestCase {
+    function strip_index_data($entry) {
+        $n_entry = array();
+        foreach(array('id', 'type', 'level', 'open') as $k) {
+            $n_entry[$k] = $entry[$k];
+        }
+        return $n_entry;
+    }
+
+    function test_search_index(){
+        $data = array();
+        search($data, dirname(__FILE__) . '/data', 'search_index',
+               array('ns' => 'ns2'));
+        $this->assertEqual(array_map(array($this, 'strip_index_data'), $data),
+                           array(
+                              array(
+                                'id'   => 'ns1',
+                                'type' => 'd',
+                                'level' => 1,
+                                'open' => false
+                              ), array(
+                                'id'   => 'ns2',
+                                'type' => 'd',
+                                'level' => 1,
+                                'open' => true
+                              ), array(
+                                'id' => 'ns2:page1',
+                                'type' => 'f',
+                                'level' => 2,
+                                'open' => true,
+                              ), ));
+        $data = array();
+        search($data, dirname(__FILE__) . '/data', 'search_index',
+               array('ns' => 'ns1/ns3'));
+        $this->assertEqual(array_map(array($this, 'strip_index_data'), $data),
+                           array(
+                              array(
+                                'id' => 'ns1',
+                                'type' => 'd',
+                                'level' => 1,
+                                'open' => true,
+                              ),
+                              array(
+                                'id' => 'ns1:ns3',
+                                'type' => 'd',
+                                'level' => 2,
+                                'open' => true,
+                              ),
+                              array(
+                                'id' => 'ns1:ns3:page3',
+                                'type' => 'f',
+                                'level' => 3,
+                                'open' => true,
+                              ),
+                              array(
+                                'id' => 'ns1:page1',
+                                'type' => 'f',
+                                'level' => 2,
+                                'open' => true,
+                              ),
+                              array(
+                                'id' => 'ns1:page2',
+                                'type' => 'f',
+                                'level' => 2,
+                                'open' => true,
+                              ),
+                              array(
+                                'id' => 'ns2',
+                                'type' => 'd',
+                                'level' => 1,
+                                'open' => false,
+                              ), ));
+        $data = array();
+        search($data, dirname(__FILE__) . '/data', 'search_index',
+               array('ns' => 'ns1/ns3', 'nofiles' => true));
+        $this->assertEqual(array_map(array($this, 'strip_index_data'), $data),
+                           array(
+                              array(
+                                'id' => 'ns1',
+                                'type' => 'd',
+                                'level' => 1,
+                                'open' => true,
+                              ),
+                              array(
+                                'id' => 'ns1:ns3',
+                                'type' => 'd',
+                                'level' => 2,
+                                'open' => true,
+                              ),
+                              array(
+                                'id' => 'ns2',
+                                'type' => 'd',
+                                'level' => 1,
+                                'open' => false,
+                              ), ));
+
+    }
+}
+//Setup VIM: ex: et ts=4 :
diff --git a/conf/dokuwiki.php b/conf/dokuwiki.php
index 5e185e9c21149fa2bbd30a94369b6008a870be51..298c8e5721f340c0d456ff344c74fdc430c205d3 100644
--- a/conf/dokuwiki.php
+++ b/conf/dokuwiki.php
@@ -20,6 +20,7 @@ $conf['basedir']     = '';                //absolute dir from serveroot - blank
 $conf['baseurl']     = '';                //URL to server including protocol - blank for autodetect
 $conf['savedir']     = './data';          //where to store all the files
 $conf['allowdebug']  = 0;                 //allow debug output, enable if needed 0|1
+$conf['mediarevisions'] = 1;              //enable/disable media revisions
 
 /* Display Options */
 
diff --git a/conf/mediameta.php b/conf/mediameta.php
index ba71176431400e290d60aadaee32344eac2cc877..0428a4b88d0d7a3c00fa4b5d11bcb13a86b2b654 100644
--- a/conf/mediameta.php
+++ b/conf/mediameta.php
@@ -29,29 +29,63 @@ $fields = array(
                 'img_title',
                 'text'),
 
-    20 => array('Iptc.Caption',
+    20 => array('',
+                'img_date',
+                'date',
+                array('Date.EarliestTime')),
+
+    30 => array('',
+                'img_fname',
+                'text',
+                array('File.Name')),
+
+    40 => array('Iptc.Caption',
                 'img_caption',
                 'textarea',
                 array('Exif.UserComment',
                       'Exif.TIFFImageDescription',
                       'Exif.TIFFUserComment')),
 
-    30 => array('Iptc.Byline',
+    50 => array('Iptc.Byline',
                 'img_artist',
                 'text',
                 array('Exif.TIFFArtist',
                       'Exif.Artist',
                       'Iptc.Credit')),
 
-    40 => array('Iptc.CopyrightNotice',
+    60 => array('Iptc.CopyrightNotice',
                 'img_copyr',
                 'text',
                 array('Exif.TIFFCopyright',
                       'Exif.Copyright')),
 
-    50 => array('Iptc.Keywords',
+    70 => array('',
+                'img_format',
+                'text',
+                array('File.Format')),
+
+    80 => array('',
+                'img_fsize',
+                'text',
+                array('File.NiceSize')),
+
+    90 => array('',
+                'img_width',
+                'text',
+                array('File.Width')),
+
+    100 => array('',
+                'img_height',
+                'text',
+                array('File.Height')),
+
+    110 => array('',
+                'img_camera',
+                'text',
+                array('Simple.Camera')),
+
+    120 => array('Iptc.Keywords',
                 'img_keywords',
                 'text',
                 array('Exif.Category')),
 );
-
diff --git a/conf/plugins.protected.php b/conf/plugins.protected.php
new file mode 100644
index 0000000000000000000000000000000000000000..26eb8888b75bc29fea03f0d34dcbe042fa5690d3
--- /dev/null
+++ b/conf/plugins.protected.php
@@ -0,0 +1,11 @@
+<?php
+/**
+ * This file configures the enabled/disabled status of plugins, which are also protected
+ * from changes by the extention manager. These settings will override any local settings.
+ * It is not recommended to change this file, as it is overwritten on DokuWiki upgrades.
+ */
+$plugins['acl']         = 1;
+$plugins['plugin']      = 1;
+$plugins['config']      = 1;
+$plugins['usermanager'] = 1;
+$plugins['revert']      = 1;
diff --git a/data/media_attic/_dummy b/data/media_attic/_dummy
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/data/media_meta/_dummy b/data/media_meta/_dummy
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/feed.php b/feed.php
index cca8661f454f2fdfdfeead794c88bfa549ba0d65..298777eb9ac1e05d05f42ade89f6ad7dac45b1cd 100644
--- a/feed.php
+++ b/feed.php
@@ -117,6 +117,8 @@ function rss_parseOptions(){
                   'show_minor'   => array('minor', false),
                   // String, only used in search mode
                   'search_query' => array('q', null),
+                // One of: pages, media, both
+                  'content_type' => array('view', 'both')
 
                  ) as $name => $val) {
         $opt[$name] = (isset($_REQUEST[$val[0]]) && !empty($_REQUEST[$val[0]]))
@@ -188,7 +190,9 @@ function rss_buildItems(&$rss,&$data,$opt){
 
             $item = new FeedItem();
             $id   = $ditem['id'];
-            $meta = p_get_metadata($id);
+            if(!$ditem['media']) {
+                $meta = p_get_metadata($id);
+            }
 
             // add date
             if($ditem['date']){
@@ -213,62 +217,134 @@ function rss_buildItems(&$rss,&$data,$opt){
             // add item link
             switch ($opt['link_to']){
                 case 'page':
-                    $item->link = wl($id,'rev='.$date,true,'&');
+                    if ($ditem['media']) {
+                        $item->link = media_managerURL(array('image' => $id,
+                            'ns' => getNS($id),
+                            'rev' => $date), '&', true);
+                    } else {
+                        $item->link = wl($id,'rev='.$date,true,'&', true);
+                    }
                     break;
                 case 'rev':
-                    $item->link = wl($id,'do=revisions&rev='.$date,true,'&');
+                    if ($ditem['media']) {
+                        $item->link = media_managerURL(array('image' => $id,
+                            'ns' => getNS($id),
+                            'rev' => $date,
+                            'tab_details' => 'history'), '&', true);
+                    } else {
+                        $item->link = wl($id,'do=revisions&rev='.$date,true,'&');
+                    }
                     break;
                 case 'current':
-                    $item->link = wl($id, '', true,'&');
+                    if ($ditem['media']) {
+                        $item->link = media_managerURL(array('image' => $id,
+                            'ns' => getNS($id)), '&', true);
+                    } else {
+                        $item->link = wl($id, '', true,'&');
+                    }
                     break;
                 case 'diff':
                 default:
-                    $item->link = wl($id,'rev='.$date.'&do=diff',true,'&');
+                    if ($ditem['media']) {
+                        $item->link = media_managerURL(array('image' => $id,
+                            'ns' => getNS($id),
+                            'rev' => $date,
+                            'tab_details' => 'history',
+                            'mediado' => 'diff'), '&', true);
+                    } else {
+                        $item->link = wl($id,'rev='.$date.'&do=diff',true,'&');
+                    }
             }
 
             // add item content
             switch ($opt['item_content']){
                 case 'diff':
                 case 'htmldiff':
-                    require_once(DOKU_INC.'inc/DifferenceEngine.php');
-                    $revs = getRevisions($id, 0, 1);
-                    $rev = $revs[0];
-
-                    if($rev){
-                        $df  = new Diff(explode("\n",htmlspecialchars(rawWiki($id,$rev))),
-                                        explode("\n",htmlspecialchars(rawWiki($id,''))));
-                    }else{
-                        $df  = new Diff(array(''),
-                                        explode("\n",htmlspecialchars(rawWiki($id,''))));
-                    }
-
-                    if($opt['item_content'] == 'htmldiff'){
-                        $tdf = new TableDiffFormatter();
-                        $content  = '<table>';
-                        $content .= '<tr><th colspan="2" width="50%">'.$rev.'</th>';
-                        $content .= '<th colspan="2" width="50%">'.$lang['current'].'</th></tr>';
-                        $content .= $tdf->format($df);
-                        $content .= '</table>';
-                    }else{
-                        $udf = new UnifiedDiffFormatter();
-                        $content = "<pre>\n".$udf->format($df)."\n</pre>";
+                    if ($ditem['media']) {
+                        $revs = getRevisions($id, 0, 1, 8192, true);
+                        $rev = $revs[0];
+                        $src_r = '';
+                        $src_l = '';
+
+                        if ($size = media_image_preview_size($id, false, new JpegMeta(mediaFN($id)), 300)) {
+                            $more = 'w='.$size[0].'&h='.$size[1].'t='.@filemtime(mediaFN($id));
+                            $src_r = ml($id, $more);
+                        }
+                        if ($rev && $size = media_image_preview_size($id, $rev, new JpegMeta(mediaFN($id, $rev)), 300)){
+                            $more = 'rev='.$rev.'&w='.$size[0].'&h='.$size[1];
+                            $src_l = ml($id, $more);
+                        }
+                        $content = '';
+                        if ($src_r) {
+                            $content  = '<table>';
+                            $content .= '<tr><th width="50%">'.$rev.'</th>';
+                            $content .= '<th width="50%">'.$lang['current'].'</th></tr>';
+                            $content .= '<tr align="center"><td><img src="'.$src_l.'" alt="" /></td><td>';
+                            $content .= '<img src="'.$src_r.'" alt="'.$id.'" /></td></tr>';
+                            $content .= '</table>';
+                        }
+
+                    } else {
+                        require_once(DOKU_INC.'inc/DifferenceEngine.php');
+                        $revs = getRevisions($id, 0, 1);
+                        $rev = $revs[0];
+
+                        if($rev){
+                            $df  = new Diff(explode("\n",htmlspecialchars(rawWiki($id,$rev))),
+                                            explode("\n",htmlspecialchars(rawWiki($id,''))));
+                        }else{
+                            $df  = new Diff(array(''),
+                                            explode("\n",htmlspecialchars(rawWiki($id,''))));
+                        }
+
+                        if($opt['item_content'] == 'htmldiff'){
+                            $tdf = new TableDiffFormatter();
+                            $content  = '<table>';
+                            $content .= '<tr><th colspan="2" width="50%">'.$rev.'</th>';
+                            $content .= '<th colspan="2" width="50%">'.$lang['current'].'</th></tr>';
+                            $content .= $tdf->format($df);
+                            $content .= '</table>';
+                        }else{
+                            $udf = new UnifiedDiffFormatter();
+                            $content = "<pre>\n".$udf->format($df)."\n</pre>";
+                        }
                     }
                     break;
                 case 'html':
-                    $content = p_wiki_xhtml($id,$date,false);
-                    // no TOC in feeds
-                    $content = preg_replace('/(<!-- TOC START -->).*(<!-- TOC END -->)/s','',$content);
-
-                    // make URLs work when canonical is not set, regexp instead of rerendering!
-                    if(!$conf['canonical']){
-                        $base = preg_quote(DOKU_REL,'/');
-                        $content = preg_replace('/(<a href|<img src)="('.$base.')/s','$1="'.DOKU_URL,$content);
+                    if ($ditem['media']) {
+                        if ($size = media_image_preview_size($id, false, new JpegMeta(mediaFN($id)))) {
+                            $more = 'w='.$size[0].'&h='.$size[1].'t='.@filemtime(mediaFN($id));
+                            $src = ml($id, $more);
+                            $content = '<img src="'.$src.'" alt="'.$id.'" />';
+                        } else {
+                            $content = '';
+                        }
+                    } else {
+                        $content = p_wiki_xhtml($id,$date,false);
+                        // no TOC in feeds
+                        $content = preg_replace('/(<!-- TOC START -->).*(<!-- TOC END -->)/s','',$content);
+
+                        // make URLs work when canonical is not set, regexp instead of rerendering!
+                        if(!$conf['canonical']){
+                            $base = preg_quote(DOKU_REL,'/');
+                            $content = preg_replace('/(<a href|<img src)="('.$base.')/s','$1="'.DOKU_URL,$content);
+                        }
                     }
 
                     break;
                 case 'abstract':
                 default:
-                    $content = $meta['description']['abstract'];
+                    if ($ditem['media']) {
+                        if ($size = media_image_preview_size($id, false, new JpegMeta(mediaFN($id)))) {
+                            $more = 'w='.$size[0].'&h='.$size[1].'t='.@filemtime(mediaFN($id));
+                            $src = ml($id, $more);
+                            $content = '<img src="'.$src.'" alt="'.$id.'" />';
+                        } else {
+                            $content = '';
+                        }
+                    } else {
+                        $content = $meta['description']['abstract'];
+                    }
             }
             $item->description = $content; //FIXME a plugin hook here could be senseful
 
@@ -334,8 +410,11 @@ function rss_buildItems(&$rss,&$data,$opt){
  * @author Andreas Gohr <andi@splitbrain.org>
  */
 function rssRecentChanges($opt){
+    global $conf;
     $flags = RECENTS_SKIP_DELETED;
     if(!$opt['show_minor']) $flags += RECENTS_SKIP_MINORS;
+    if($opt['content_type'] == 'media' && $conf['mediarevisions']) $flags += RECENTS_MEDIA_CHANGES;
+    if($opt['content_type'] == 'both' && $conf['mediarevisions']) $flags += RECENTS_MEDIA_PAGES_MIXED;
 
     $recents = getRecents(0,$opt['items'],$opt['namespace'],$flags);
     return $recents;
diff --git a/inc/actions.php b/inc/actions.php
index ecf09036f773dabc23af9a2c33f502fcbea3bc5c..1a0ae4028c19d740a409a5cb6c516200e0d1d3b6 100644
--- a/inc/actions.php
+++ b/inc/actions.php
@@ -227,7 +227,7 @@ function act_clean($act){
                     'preview','search','show','check','index','revisions',
                     'diff','recent','backlink','admin','subscribe','revert',
                     'unsubscribe','profile','resendpwd','recover',
-                    'draftdel','subscribens','unsubscribens','sitemap')) && substr($act,0,7) != 'export_' ) {
+                    'draftdel','subscribens','unsubscribens','sitemap','media')) && substr($act,0,7) != 'export_' ) {
         msg('Command unknown: '.htmlspecialchars($act),-1);
         return 'show';
     }
diff --git a/inc/changelog.php b/inc/changelog.php
index 15cd46d778c9108af53176bdbc6d0aeb216fa350..fea39f9f7067e446821484e50336099c55c019ac 100644
--- a/inc/changelog.php
+++ b/inc/changelog.php
@@ -138,6 +138,7 @@ function addMediaLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='',
     // add changelog lines
     $logline = implode("\t", $logline)."\n";
     io_saveFile($conf['media_changelog'],$logline,true); //global media changelog cache
+    io_saveFile(mediaMetaFN($id,'.changes'),$logline,true); //media file's changelog
 }
 
 /**
@@ -151,6 +152,7 @@ function addMediaLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='',
  * RECENTS_SKIP_MINORS    - don't include minor changes
  * RECENTS_SKIP_SUBSPACES - don't include subspaces
  * RECENTS_MEDIA_CHANGES  - return media changes instead of page changes
+ * RECENTS_MEDIA_PAGES_MIXED  - return both media changes and page changes
  *
  * @param int    $first   number of first entry returned (for paginating
  * @param int    $num     return $num entries
@@ -158,6 +160,7 @@ function addMediaLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='',
  * @param bool   $flags   see above
  *
  * @author Ben Coburn <btcoburn@silicodon.net>
+ * @author Kate Arzamastseva <pshns@ukr.net>
  */
 function getRecents($first,$num,$ns='',$flags=0){
     global $conf;
@@ -173,20 +176,48 @@ function getRecents($first,$num,$ns='',$flags=0){
     } else {
         $lines = @file($conf['changelog']);
     }
+    $lines_position = count($lines)-1;
+
+    if ($flags & RECENTS_MEDIA_PAGES_MIXED) {
+        $media_lines = @file($conf['media_changelog']);
+        $media_lines_position = count($media_lines)-1;
+    }
 
-    // handle lines
     $seen = array(); // caches seen lines, _handleRecent() skips them
-    for($i = count($lines)-1; $i >= 0; $i--){
-        $rec = _handleRecent($lines[$i], $ns, $flags, $seen);
-        if($rec !== false) {
-            if(--$first >= 0) continue; // skip first entries
-            $recent[] = $rec;
-            $count++;
-            // break when we have enough entries
-            if($count >= $num){ break; }
+
+    // handle lines
+    while ($lines_position >= 0 || (($flags & RECENTS_MEDIA_PAGES_MIXED) && $media_lines_position >=0)) {
+        if (empty($rec) && $lines_position >= 0) {
+            $rec = _handleRecent(@$lines[$lines_position], $ns, $flags & ~RECENTS_MEDIA_CHANGES, $seen);
+            if (!$rec) {
+                $lines_position --;
+                continue;
+            }
+        }
+        if (($flags & RECENTS_MEDIA_PAGES_MIXED) && empty($media_rec) && $media_lines_position >= 0) {
+            $media_rec = _handleRecent(@$media_lines[$media_lines_position], $ns, $flags | RECENTS_MEDIA_CHANGES, $seen);
+            if (!$media_rec) {
+            	$media_lines_position --;
+            	continue;
+            }
         }
+        if (($flags & RECENTS_MEDIA_PAGES_MIXED) && @$media_rec['date'] >= @$rec['date']) {
+            $media_lines_position--;
+            $x = $media_rec;
+            $x['media'] = true;
+            $media_rec = false;
+        } else {
+            $lines_position--;
+            $x = $rec;
+            if ($flags & RECENTS_MEDIA_CHANGES) $x['media'] = true;
+            $rec = false;
+        }
+        if(--$first >= 0) continue; // skip first entries
+        $recent[] = $x;
+        $count++;
+        // break when we have enough entries
+        if($count >= $num){ break; }
     }
-
     return $recent;
 }
 
@@ -281,7 +312,11 @@ function _handleRecent($line,$ns,$flags,&$seen){
     if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false;
 
     // check ACL
-    $recent['perms'] = auth_quickaclcheck($recent['id']);
+    if ($flags & RECENTS_MEDIA_CHANGES) {
+        $recent['perms'] = auth_quickaclcheck(getNS($recent['id']).':*');
+    } else {
+        $recent['perms'] = auth_quickaclcheck($recent['id']);
+    }
     if ($recent['perms'] < AUTH_READ) return false;
 
     // check existance
@@ -300,8 +335,9 @@ function _handleRecent($line,$ns,$flags,&$seen){
  * requested changelog line is read.
  *
  * @author Ben Coburn <btcoburn@silicodon.net>
+ * @author Kate Arzamastseva <pshns@ukr.net>
  */
-function getRevisionInfo($id, $rev, $chunk_size=8192) {
+function getRevisionInfo($id, $rev, $chunk_size=8192, $media=false) {
     global $cache_revinfo;
     $cache =& $cache_revinfo;
     if (!isset($cache[$id])) { $cache[$id] = array(); }
@@ -312,7 +348,11 @@ function getRevisionInfo($id, $rev, $chunk_size=8192) {
         return $cache[$id][$rev];
     }
 
-    $file = metaFN($id, '.changes');
+    if ($media) {
+        $file = mediaMetaFN($id, '.changes');
+    } else {
+        $file = metaFN($id, '.changes');
+    }
     if (!@file_exists($file)) { return false; }
     if (filesize($file)<$chunk_size || $chunk_size==0) {
         // read whole file
@@ -397,8 +437,9 @@ function getRevisionInfo($id, $rev, $chunk_size=8192) {
  * lines are recieved.
  *
  * @author Ben Coburn <btcoburn@silicodon.net>
+ * @author Kate Arzamastseva <pshns@ukr.net>
  */
-function getRevisions($id, $first, $num, $chunk_size=8192) {
+function getRevisions($id, $first, $num, $chunk_size=8192, $media=false) {
     global $cache_revinfo;
     $cache =& $cache_revinfo;
     if (!isset($cache[$id])) { $cache[$id] = array(); }
@@ -406,11 +447,15 @@ function getRevisions($id, $first, $num, $chunk_size=8192) {
     $revs = array();
     $lines = array();
     $count  = 0;
-    $file = metaFN($id, '.changes');
+    if ($media) {
+        $file = mediaMetaFN($id, '.changes');
+    } else {
+        $file = metaFN($id, '.changes');
+    }
     $num = max($num, 0);
     $chunk_size = max($chunk_size, 0);
     if ($first<0) { $first = 0; }
-    else if (@file_exists(wikiFN($id))) {
+    else if (!$media && @file_exists(wikiFN($id)) || $media && @file_exists(mediaFN($id))) {
         // skip current revision if the page exists
         $first = max($first+1, 0);
     }
diff --git a/inc/common.php b/inc/common.php
index 239cfcf9957323f135efbd2dc49b9c72ad33102a..56a7fb060dc6c200c1f3aaf94805c888614db012 100644
--- a/inc/common.php
+++ b/inc/common.php
@@ -15,6 +15,7 @@ define('RECENTS_SKIP_DELETED',2);
 define('RECENTS_SKIP_MINORS',4);
 define('RECENTS_SKIP_SUBSPACES',8);
 define('RECENTS_MEDIA_CHANGES',16);
+define('RECENTS_MEDIA_PAGES_MIXED',32);
 
 /**
  * Wrapper around htmlspecialchars()
diff --git a/inc/config_cascade.php b/inc/config_cascade.php
index 48ed5a000b36d77db56dfb2610089ade8b3d1775..c01778e99f552e8a1cbf48556ffcf5eb668f203a 100644
--- a/inc/config_cascade.php
+++ b/inc/config_cascade.php
@@ -64,6 +64,11 @@ $config_cascade = array_merge(
         'plainauth.users' => array(
             'default' => DOKU_CONF.'users.auth.php',
             ),
+        
+        'plugins' => array(
+            'local'     => array(DOKU_CONF.'plugins.local.php'),
+            'protected' => array(DOKU_CONF.'plugins.protected.php'),
+            ),
         ),
         $config_cascade
 );
diff --git a/inc/html.php b/inc/html.php
index 6e9cce7dfa29c838dfe364abca4f0b28775032b7..1c48d60594cb3479d78280e12b325c25fb7a78f2 100644
--- a/inc/html.php
+++ b/inc/html.php
@@ -415,20 +415,28 @@ function html_locked(){
  *
  * @author Andreas Gohr <andi@splitbrain.org>
  * @author Ben Coburn <btcoburn@silicodon.net>
+ * @author Kate Arzamastseva <pshns@ukr.net>
  */
-function html_revisions($first=0){
+function html_revisions($first=0, $media_id = false){
     global $ID;
     global $INFO;
     global $conf;
     global $lang;
+    $id = $ID;
     /* we need to get one additionally log entry to be able to
      * decide if this is the last page or is there another one.
      * see html_recent()
      */
-    $revisions = getRevisions($ID, $first, $conf['recent']+1);
+    if (!$media_id) $revisions = getRevisions($ID, $first, $conf['recent']+1);
+    else {
+        $revisions = getRevisions($media_id, $first, $conf['recent']+1, 8192, true);
+        $id = $media_id;
+    }
+
     if(count($revisions)==0 && $first!=0){
         $first=0;
-        $revisions = getRevisions($ID, $first, $conf['recent']+1);;
+        if (!$media_id) $revisions = getRevisions($ID, $first, $conf['recent']+1);
+        else $revisions = getRevisions($media_id, $first, $conf['recent']+1, 8192, true);
     }
     $hasNext = false;
     if (count($revisions)>$conf['recent']) {
@@ -436,14 +444,22 @@ function html_revisions($first=0){
         array_pop($revisions); // remove extra log entry
     }
 
-    $date = dformat($INFO['lastmod']);
+    if (!$media_id) $date = dformat($INFO['lastmod']);
+    else $date = dformat(@filemtime(mediaFN($id)));
+
+    if (!$media_id) print p_locale_xhtml('revisions');
 
-    print p_locale_xhtml('revisions');
+    $params = array('id' => 'page__revisions');
+    if ($media_id) $params['action'] = media_managerURL(array('image' => $media_id), '&');
 
-    $form = new Doku_Form(array('id' => 'page__revisions'));
+    $form = new Doku_Form($params);
     $form->addElement(form_makeOpenTag('ul'));
-    if($INFO['exists'] && $first==0){
-        if (isset($INFO['meta']) && isset($INFO['meta']['last_change']) && $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT)
+
+    if (!$media_id) $exists = $INFO['exists'];
+    else $exists = @file_exists(mediaFN($id));
+
+    if($exists && $first==0){
+        if (!$media_id && isset($INFO['meta']) && isset($INFO['meta']['last_change']) && $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT)
             $form->addElement(form_makeOpenTag('li', array('class' => 'minor')));
         else
             $form->addElement(form_makeOpenTag('li'));
@@ -459,30 +475,53 @@ function html_revisions($first=0){
 
         $form->addElement('<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />');
 
+        if (!$media_id) $href = wl($id);
+        else $href = media_managerURL(array('image' => $id, 'tab_details' => 'view'), '&');
         $form->addElement(form_makeOpenTag('a', array(
                         'class' => 'wikilink1',
-                        'href'  => wl($ID))));
-        $form->addElement($ID);
+                        'href'  => $href)));
+        $form->addElement($id);
         $form->addElement(form_makeCloseTag('a'));
 
-        $form->addElement(form_makeOpenTag('span', array('class' => 'sum')));
-        $form->addElement(' &ndash; ');
-        $form->addElement(htmlspecialchars($INFO['sum']));
-        $form->addElement(form_makeCloseTag('span'));
+        if ($media_id) $form->addElement(form_makeOpenTag('div'));
+
+        if (!$media_id) {
+            $form->addElement(form_makeOpenTag('span', array('class' => 'sum')));
+            $form->addElement(' &ndash; ');
+            $form->addElement(htmlspecialchars($INFO['sum']));
+            $form->addElement(form_makeCloseTag('span'));
+        }
 
         $form->addElement(form_makeOpenTag('span', array('class' => 'user')));
-        $form->addElement((empty($INFO['editor']))?('('.$lang['external_edit'].')'):editorinfo($INFO['editor']));
+        if (!$media_id) $editor = $INFO['editor'];
+        else {
+            $revinfo = getRevisionInfo($id, @filemtime(fullpath(mediaFN($id))), 1024, true);
+            if($revinfo['user']){
+                $editor = $revinfo['user'];
+            }else{
+                $editor = $revinfo['ip'];
+            }
+        }
+        $form->addElement((empty($editor))?('('.$lang['external_edit'].')'):editorinfo($editor));
         $form->addElement(form_makeCloseTag('span'));
 
         $form->addElement('('.$lang['current'].')');
+
+        if ($media_id) $form->addElement(form_makeCloseTag('div'));
+
         $form->addElement(form_makeCloseTag('div'));
         $form->addElement(form_makeCloseTag('li'));
     }
 
     foreach($revisions as $rev){
-        $date   = dformat($rev);
-        $info   = getRevisionInfo($ID,$rev,true);
-        $exists = page_exists($ID,$rev);
+        $date = dformat($rev);
+        if (!$media_id) {
+            $info = getRevisionInfo($id,$rev,true);
+            $exists = page_exists($id,$rev);
+        }  else {
+            $info = getRevisionInfo($id,$rev,true,true);
+            $exists = @file_exists(mediaFN($id,$rev));
+        }
 
         if ($info['type']===DOKU_CHANGE_TYPE_MINOR_EDIT)
             $form->addElement(form_makeOpenTag('li', array('class' => 'minor')));
@@ -503,7 +542,9 @@ function html_revisions($first=0){
         $form->addElement(form_makeCloseTag('span'));
 
         if($exists){
-            $form->addElement(form_makeOpenTag('a', array('href' => wl($ID,"rev=$rev,do=diff", false, '&'), 'class' => 'diff_link')));
+            if (!$media_id) $href = wl($id,"rev=$rev,do=diff", false, '&');
+            else $href = media_managerURL(array('image' => $id, 'rev' => $rev, 'mediado' => 'diff'), '&');
+            $form->addElement(form_makeOpenTag('a', array('href' => $href, 'class' => 'diff_link')));
             $form->addElement(form_makeTag('img', array(
                             'src'    => DOKU_BASE.'lib/images/diff.png',
                             'width'  => 15,
@@ -511,19 +552,24 @@ function html_revisions($first=0){
                             'title'  => $lang['diff'],
                             'alt'    => $lang['diff'])));
             $form->addElement(form_makeCloseTag('a'));
-
-            $form->addElement(form_makeOpenTag('a', array('href' => wl($ID,"rev=$rev",false,'&'), 'class' => 'wikilink1')));
-            $form->addElement($ID);
+            if (!$media_id) $href = wl($id,"rev=$rev",false,'&');
+            else $href = media_managerURL(array('image' => $id, 'tab_details' => 'view', 'rev' => $rev), '&');
+            $form->addElement(form_makeOpenTag('a', array('href' => $href, 'class' => 'wikilink1')));
+            $form->addElement($id);
             $form->addElement(form_makeCloseTag('a'));
         }else{
             $form->addElement('<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />');
-            $form->addElement($ID);
+            $form->addElement($id);
         }
 
-        $form->addElement(form_makeOpenTag('span', array('class' => 'sum')));
-        $form->addElement(' &ndash; ');
-        $form->addElement(htmlspecialchars($info['sum']));
-        $form->addElement(form_makeCloseTag('span'));
+        if ($media_id) $form->addElement(form_makeOpenTag('div'));
+
+        if ($info['sum']) {
+            $form->addElement(form_makeOpenTag('span', array('class' => 'sum')));
+            if (!$media_id) $form->addElement(' &ndash; ');
+            $form->addElement(htmlspecialchars($info['sum']));
+            $form->addElement(form_makeCloseTag('span'));
+        }
 
         $form->addElement(form_makeOpenTag('span', array('class' => 'user')));
         if($info['user']){
@@ -536,11 +582,18 @@ function html_revisions($first=0){
         }
         $form->addElement(form_makeCloseTag('span'));
 
+        if ($media_id) $form->addElement(form_makeCloseTag('div'));
+
         $form->addElement(form_makeCloseTag('div'));
         $form->addElement(form_makeCloseTag('li'));
     }
     $form->addElement(form_makeCloseTag('ul'));
-    $form->addElement(form_makeButton('submit', 'diff', $lang['diff2']));
+    if (!$media_id) {
+        $form->addElement(form_makeButton('submit', 'diff', $lang['diff2']));
+    } else {
+        $form->addHidden('mediado', 'diff');
+        $form->addElement(form_makeButton('submit', '', $lang['diff2']));
+    }
     html_form('revisions', $form);
 
     print '<div class="pagenav">';
@@ -549,12 +602,20 @@ function html_revisions($first=0){
         $first -= $conf['recent'];
         if ($first < 0) $first = 0;
         print '<div class="pagenav-prev">';
-        print html_btn('newer',$ID,"p",array('do' => 'revisions', 'first' => $first));
+        if ($media_id) {
+            print html_btn('newer',$media_id,"p",media_managerURL(array('first' => $first), '&amp;', false, true));
+        } else {
+            print html_btn('newer',$id,"p",array('do' => 'revisions', 'first' => $first));
+        }
         print '</div>';
     }
     if ($hasNext) {
         print '<div class="pagenav-next">';
-        print html_btn('older',$ID,"n",array('do' => 'revisions', 'first' => $last));
+        if ($media_id) {
+            print html_btn('older',$media_id,"n",media_managerURL(array('first' => $last), '&amp;', false, true));
+        } else {
+            print html_btn('older',$id,"n",array('do' => 'revisions', 'first' => $last));
+        }
         print '</div>';
     }
     print '</div>';
@@ -567,8 +628,9 @@ function html_revisions($first=0){
  * @author Andreas Gohr <andi@splitbrain.org>
  * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
  * @author Ben Coburn <btcoburn@silicodon.net>
+ * @author Kate Arzamastseva <pshns@ukr.net>
  */
-function html_recent($first=0){
+function html_recent($first=0, $show_changes='both'){
     global $conf;
     global $lang;
     global $ID;
@@ -576,10 +638,20 @@ function html_recent($first=0){
      * decide if this is the last page or is there another one.
      * This is the cheapest solution to get this information.
      */
-    $recents = getRecents($first,$conf['recent'] + 1,getNS($ID));
+    $flags = 0;
+    if ($show_changes == 'mediafiles' && $conf['mediarevisions']) {
+        $flags = RECENTS_MEDIA_CHANGES;
+    } elseif ($show_changes == 'pages') {
+        $flags = 0;
+    } elseif ($conf['mediarevisions']) {
+        $show_changes = 'both';
+        $flags = RECENTS_MEDIA_PAGES_MIXED;
+    }
+
+    $recents = getRecents($first,$conf['recent'] + 1,getNS($ID),$flags);
     if(count($recents) == 0 && $first != 0){
         $first=0;
-        $recents = getRecents($first,$conf['recent'] + 1,getNS($ID));
+        $recents = getRecents($first,$conf['recent'] + 1,getNS($ID),$flags);
     }
     $hasNext = false;
     if (count($recents)>$conf['recent']) {
@@ -596,6 +668,22 @@ function html_recent($first=0){
     $form->addHidden('sectok', null);
     $form->addHidden('do', 'recent');
     $form->addHidden('id', $ID);
+
+    if ($conf['mediarevisions']) {
+        $form->addElement(form_makeListboxField(
+                    'show_changes',
+                    array(
+                        'pages'      => $lang['pages_changes'],
+                        'mediafiles' => $lang['media_changes'],
+                        'both'       => $lang['both_changes']),
+                    $show_changes,
+                    $lang['changes_type'],
+                    '','',
+                    array('class'=>'quickselect')));
+
+        $form->addElement(form_makeButton('submit', 'recent', $lang['btn_apply']));
+    }
+
     $form->addElement(form_makeOpenTag('ul'));
 
     foreach($recents as $recent){
@@ -607,21 +695,48 @@ function html_recent($first=0){
 
         $form->addElement(form_makeOpenTag('div', array('class' => 'li')));
 
+        if ($recent['media']) {
+            $form->addElement(media_printicon($recent['id']));
+        } else {
+            $icon = DOKU_BASE.'lib/images/fileicons/file.png';
+            $form->addElement('<img src="'.$icon.'" alt="'.$filename.'" class="icon" />');
+        }
+
         $form->addElement(form_makeOpenTag('span', array('class' => 'date')));
         $form->addElement($date);
         $form->addElement(form_makeCloseTag('span'));
 
-        $form->addElement(form_makeOpenTag('a', array('class' => 'diff_link', 'href' => wl($recent['id'],"do=diff", false, '&'))));
-        $form->addElement(form_makeTag('img', array(
-                        'src'   => DOKU_BASE.'lib/images/diff.png',
-                        'width' => 15,
-                        'height'=> 11,
-                        'title' => $lang['diff'],
-                        'alt'   => $lang['diff']
-                        )));
-        $form->addElement(form_makeCloseTag('a'));
+        if ($recent['media']) {
+            $diff = (count(getRevisions($recent['id'], 0, 1, 8192, true)) && @file_exists(mediaFN($recent['id'])));
+            if ($diff) {
+                $href = media_managerURL(array('tab_details' => 'history',
+                    'mediado' => 'diff', 'image' => $recent['id'], 'ns' => getNS($recent['id'])), '&');
+            }
+        } else {
+            $href = wl($recent['id'],"do=diff", false, '&');
+        }
 
-        $form->addElement(form_makeOpenTag('a', array('class' => 'revisions_link', 'href' => wl($recent['id'],"do=revisions",false,'&'))));
+        if ($recent['media'] && !$diff) {
+            $form->addElement('<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />');
+        } else {
+            $form->addElement(form_makeOpenTag('a', array('class' => 'diff_link', 'href' => $href)));
+            $form->addElement(form_makeTag('img', array(
+                            'src'   => DOKU_BASE.'lib/images/diff.png',
+                            'width' => 15,
+                            'height'=> 11,
+                            'title' => $lang['diff'],
+                            'alt'   => $lang['diff']
+                            )));
+            $form->addElement(form_makeCloseTag('a'));
+        }
+
+        if ($recent['media']) {
+            $href = media_managerURL(array('tab_details' => 'history',
+                'image' => $recent['id'], 'ns' => getNS($recent['id'])), '&');
+        } else {
+            $href = wl($recent['id'],"do=revisions",false,'&');
+        }
+        $form->addElement(form_makeOpenTag('a', array('class' => 'revisions_link', 'href' => $href)));
         $form->addElement(form_makeTag('img', array(
                         'src'   => DOKU_BASE.'lib/images/history.png',
                         'width' => 12,
@@ -631,8 +746,15 @@ function html_recent($first=0){
                         )));
         $form->addElement(form_makeCloseTag('a'));
 
-        $form->addElement(html_wikilink(':'.$recent['id'],useHeading('navigation')?null:$recent['id']));
-
+        if ($recent['media']) {
+            $href = media_managerURL(array('tab_details' => 'view', 'image' => $recent['id'], 'ns' => getNS($recent['id'])), '&');
+            $class = (file_exists(mediaFN($recent['id']))) ? 'wikilink1' : $class = 'wikilink2';
+            $form->addElement(form_makeOpenTag('a', array('class' => $class, 'href' => $href)));
+            $form->addElement($recent['id']);
+            $form->addElement(form_makeCloseTag('a'));
+        } else {
+            $form->addElement(html_wikilink(':'.$recent['id'],useHeading('navigation')?null:$recent['id']));
+        }
         $form->addElement(form_makeOpenTag('span', array('class' => 'sum')));
         $form->addElement(' &ndash; '.htmlspecialchars($recent['sum']));
         $form->addElement(form_makeCloseTag('span'));
@@ -665,7 +787,7 @@ function html_recent($first=0){
                     'value' => $lang['btn_newer'],
                     'accesskey' => 'n',
                     'title' => $lang['btn_newer'].' [N]',
-                    'class' => 'button'
+                    'class' => 'button show'
                     )));
         $form->addElement(form_makeCloseTag('div'));
     }
@@ -677,7 +799,7 @@ function html_recent($first=0){
                         'value' => $lang['btn_older'],
                         'accesskey' => 'p',
                         'title' => $lang['btn_older'].' [P]',
-                        'class' => 'button'
+                        'class' => 'button show'
                         )));
         $form->addElement(form_makeCloseTag('div'));
     }
diff --git a/inc/infoutils.php b/inc/infoutils.php
index 786661d01d69eb478ce3cf92bf126e875d6639a1..f1deec66b2d999ea325339edb4f0847216fef1d8 100644
--- a/inc/infoutils.php
+++ b/inc/infoutils.php
@@ -66,8 +66,8 @@ function getVersionData(){
             $chunk = fread($fh,2000);
             fclose($fh);
             $chunk = trim($chunk);
-            $chunk = array_pop(explode("\n",$chunk));   //last log line
-            $chunk = array_shift(explode("\t",$chunk)); //strip commit msg
+            $chunk = @array_pop(explode("\n",$chunk));   //last log line
+            $chunk = @array_shift(explode("\t",$chunk)); //strip commit msg
             $chunk = explode(" ",$chunk);
             array_pop($chunk); //strip timezone
             $date = date('Y-m-d',array_pop($chunk));
diff --git a/inc/init.php b/inc/init.php
index dfa25f8b186a3c9e4df3b413020d605a0613842f..b3acf2e33ced3cae2ac6d494c39b3857b4da18cc 100644
--- a/inc/init.php
+++ b/inc/init.php
@@ -231,7 +231,9 @@ function init_paths(){
     $paths = array('datadir'   => 'pages',
             'olddir'    => 'attic',
             'mediadir'  => 'media',
+            'mediaolddir' => 'media_attic',
             'metadir'   => 'meta',
+            'mediametadir' => 'media_meta',
             'cachedir'  => 'cache',
             'indexdir'  => 'index',
             'lockdir'   => 'locks',
diff --git a/inc/lang/de-informal/lang.php b/inc/lang/de-informal/lang.php
index 16686bc4cd2cb45a7f5adb71334f380363f3fa8c..4f762b55db150768568434656a950815be4d3781 100644
--- a/inc/lang/de-informal/lang.php
+++ b/inc/lang/de-informal/lang.php
@@ -16,9 +16,10 @@
  * @author Alexander Fischer <tbanus@os-forge.net>
  * @author Juergen Schwarzer <jschwarzer@freenet.de>
  * @author Marcel Metz <marcel_metz@gmx.de>
- * @author Matthias Schulte <post@lupo49.de>
+ * @author Matthias Schulte <mailinglist@lupo49.de>
  * @author Christian Wichmann <nospam@zone0.de>
  */
+ 
 $lang['encoding']              = 'utf-8';
 $lang['direction']             = 'ltr';
 $lang['doublequoteopening']    = '„';
@@ -26,6 +27,7 @@ $lang['doublequoteclosing']    = '“';
 $lang['singlequoteopening']    = '‚';
 $lang['singlequoteclosing']    = '‘';
 $lang['apostrophe']            = '’';
+
 $lang['btn_edit']              = 'Diese Seite bearbeiten';
 $lang['btn_source']            = 'Zeige Quelltext';
 $lang['btn_show']              = 'Seite anzeigen';
@@ -59,6 +61,9 @@ $lang['btn_recover']           = 'Entwurf wiederherstellen';
 $lang['btn_draftdel']          = 'Entwurf löschen';
 $lang['btn_revert']            = 'Wiederherstellen';
 $lang['btn_register']          = 'Registrieren';
+$lang['btn_apply']             = 'Ãœbernehmen';
+$lang['btn_media']             = 'Medien-Manager';
+
 $lang['loggedinas']            = 'Angemeldet als';
 $lang['user']                  = 'Benutzername';
 $lang['pass']                  = 'Passwort';
@@ -73,6 +78,7 @@ $lang['badlogin']              = 'Nutzername oder Passwort sind falsch.';
 $lang['minoredit']             = 'kleine Änderung';
 $lang['draftdate']             = 'Entwurf gespeichert am';
 $lang['nosecedit']             = 'Diese Seite wurde in der Zwischenzeit geändert, da das Sektionsinfo veraltet ist. Die ganze Seite wird stattdessen geladen.';
+
 $lang['regmissing']            = 'Alle Felder müssen ausgefüllt werden';
 $lang['reguexists']            = 'Der Nutzername existiert leider schon.';
 $lang['regsuccess']            = 'Der neue Nutzer wurde angelegt und das Passwort per E-Mail versandt.';
@@ -82,10 +88,12 @@ $lang['regbadmail']            = 'Die angegebene Mail-Adresse scheint ungültig
 $lang['regbadpass']            = 'Die beiden eingegeben Passwörter stimmen nicht überein. Bitte versuche es noch einmal.';
 $lang['regpwmail']             = 'Ihr DokuWiki Passwort';
 $lang['reghere']               = 'Du hast noch keinen Zugang? Hier registrieren';
+
 $lang['profna']                = 'Änderung des Benutzerprofils in diesem Wiki nicht möglich.';
 $lang['profnochange']          = 'Keine Änderungen, nichts zu tun.';
 $lang['profnoempty']           = 'Es muss ein Name oder eine E-Mail Adresse angegeben werden.';
 $lang['profchanged']           = 'Benutzerprofil erfolgreich geändert.';
+
 $lang['pwdforget']             = 'Passwort vergessen? Fordere ein neues an';
 $lang['resendna']              = 'Passwörter versenden ist in diesem Wiki nicht möglich.';
 $lang['resendpwd']             = 'Neues Passwort senden für';
@@ -94,8 +102,10 @@ $lang['resendpwdnouser']       = 'Es tut mir Leid, aber der Benutzer existiert n
 $lang['resendpwdbadauth']      = 'Es tut mir Leid, aber dieser Authentifizierungscode ist ungültig. Stelle sicher, dass du den kompletten Bestätigungslink verwendet haben.';
 $lang['resendpwdconfirm']      = 'Ein Bestätigungslink wurde per E-Mail versandt.';
 $lang['resendpwdsuccess']      = 'Dein neues Passwort wurde per E-Mail versandt.';
+
 $lang['license']               = 'Falls nicht anders bezeichnet, ist der Inhalt dieses Wikis unter der folgenden Lizenz veröffentlicht:';
 $lang['licenseok']             = 'Hinweis: Durch das Bearbeiten dieser Seite gibst du dein Einverständnis, dass dein Inhalt unter der folgenden Lizenz veröffentlicht wird:';
+
 $lang['searchmedia']           = 'Suche nach Datei:';
 $lang['searchmedia_in']        = 'Suche in %s';
 $lang['txt_upload']            = 'Datei zum Hochladen auswählen';
@@ -103,6 +113,7 @@ $lang['txt_filename']          = 'Hochladen als (optional)';
 $lang['txt_overwrt']           = 'Bestehende Datei überschreiben';
 $lang['lockedby']              = 'Momentan gesperrt von';
 $lang['lockexpire']            = 'Sperre läuft ab am';
+
 $lang['js']['willexpire']            = 'Die Sperre zur Bearbeitung dieser Seite läuft in einer Minute ab.\nUm Bearbeitungskonflikte zu vermeiden, solltest du sie durch einen Klick auf den Vorschau-Knopf verlängern.';
 $lang['js']['notsavedyet']     = 'Nicht gespeicherte Änderungen gehen verloren!';
 $lang['js']['searchmedia']     = 'Suche nach Dateien';
@@ -134,8 +145,21 @@ $lang['js']['linkwiz']         = 'Link-Assistent';
 $lang['js']['linkto']          = 'Link zu:';
 $lang['js']['del_confirm']     = 'Die ausgewählten Dateien wirklich löschen?';
 $lang['js']['mu_btn']          = 'Mehrere Dateien gleichzeitig hochladen';
+$lang['js']['restore_confirm'] = 'Really restore this version?';
+$lang['js']['media_diff']          = 'Unterschiede anzeigen:';
+$lang['js']['media_diff_both']     = 'Side by Side';
+$lang['js']['media_diff_opacity']  = 'Ãœberblenden';
+$lang['js']['media_diff_portions'] = 'Ãœbergang';
+$lang['js']['media_select']        = 'Dateien auswählen…';
+$lang['js']['media_upload_btn']    = 'Hochladen';
+$lang['js']['media_done_btn']      = 'Fertig';
+$lang['js']['media_drop']          = 'Dateien hier draufziehen um sie hochzuladen';
+$lang['js']['media_cancel']        = 'Entfernen';
+$lang['js']['media_overwrt']       = 'Existierende Dateien überschreiben';
+
 $lang['rssfailed']             = 'Es ist ein Fehler beim Laden des Feeds aufgetreten: ';
 $lang['nothingfound']          = 'Nichts gefunden.';
+
 $lang['mediaselect']           = 'Dateiauswahl';
 $lang['fileupload']            = 'Datei hochladen';
 $lang['uploadsucc']            = 'Datei wurde erfolgreich hochgeladen';
@@ -160,6 +184,7 @@ $lang['mediaextchange']        = 'Dateiendung vom .%s nach .%s geändert!';
 $lang['reference']             = 'Verwendung von';
 $lang['ref_inuse']             = 'Diese Datei kann nicht gelöscht werden, da sie noch von folgenden Seiten benutzt wird:';
 $lang['ref_hidden']            = 'Einige Verweise sind auf Seiten, für die du keine Leseberechtigung hast.';
+
 $lang['hits']                  = 'Treffer';
 $lang['quickhits']             = 'Passende Seitennamen';
 $lang['toc']                   = 'Inhaltsverzeichnis';
@@ -183,11 +208,18 @@ $lang['external_edit']         = 'Externe Bearbeitung';
 $lang['summary']               = 'Zusammenfassung';
 $lang['noflash']               = 'Das <a href="http://www.adobe.com/products/flashplayer/">Adobe Flash Plugin</a> wird benötigt, um diesen Inhalt anzuzeigen.';
 $lang['download']              = 'Download-Teil';
+
 $lang['mail_newpage']          = 'Neue Seite:';
 $lang['mail_changed']          = 'Seite geändert:';
 $lang['mail_subscribe_list']   = 'Seite hat sich im Namespace geändert:';
 $lang['mail_new_user']         = 'Neuer Benutzer:';
 $lang['mail_upload']           = 'Datei hochgeladen:';
+
+$lang['changes_type']          = 'Änderungen anzeigen von';
+$lang['pages_changes']         = 'Seiten';
+$lang['media_changes']         = 'Mediendateien';
+$lang['both_changes']          = 'Beides, Seiten- und Mediendateien';
+
 $lang['qb_bold']               = 'Fetter Text';
 $lang['qb_italic']             = 'Kursiver Text';
 $lang['qb_underl']             = 'Unterstrichener Text';
@@ -212,8 +244,11 @@ $lang['qb_media']              = 'Bilder und andere Dateien hinzufügen';
 $lang['qb_sig']                = 'Unterschrift einfügen';
 $lang['qb_smileys']            = 'Smileys';
 $lang['qb_chars']              = 'Sonderzeichen';
+
 $lang['upperns']               = 'Gehe zum übergeordneten Namensraum';
+
 $lang['admin_register']        = 'Neuen Benutzer anmelden';
+
 $lang['metaedit']              = 'Metadaten bearbeiten';
 $lang['metasaveerr']           = 'Die Metadaten konnten nicht gesichert werden';
 $lang['metasaveok']            = 'Metadaten gesichert';
@@ -228,6 +263,10 @@ $lang['img_copyr']             = 'Copyright';
 $lang['img_format']            = 'Format';
 $lang['img_camera']            = 'Kamera';
 $lang['img_keywords']          = 'Schlagwörter';
+$lang['img_width']             = 'Breite';
+$lang['img_height']            = 'Höhe';
+$lang['img_manager']           = 'Im Medien-Manager anzeigen';
+
 $lang['subscr_subscribe_success'] = 'Die Seite %s wurde zur Abonnementenliste von %s hinzugefügt';
 $lang['subscr_subscribe_error'] = 'Fehler beim Hinzufügen von %s zur Abonnementenliste von %s';
 $lang['subscr_subscribe_noaddress'] = 'In deinem Account ist keine E-Mail-Adresse hinterlegt. Dadurch kann die Seite nicht abonniert werden';
@@ -235,6 +274,7 @@ $lang['subscr_unsubscribe_success'] = 'Die Seite %s wurde von der Abonnementenli
 $lang['subscr_unsubscribe_error'] = 'Fehler beim Entfernen von %s von der Abonnementenliste von %s';
 $lang['subscr_already_subscribed'] = '%s ist bereits auf der Abonnementenliste von %s';
 $lang['subscr_not_subscribed'] = '%s ist nicht auf der Abonnementenliste von %s';
+// Manage page for subscriptions
 $lang['subscr_m_not_subscribed'] = 'Du hast kein Abonnement von dieser Seite oder dem Namensraum.';
 $lang['subscr_m_new_header']   = 'Abonnementen hinzufügen';
 $lang['subscr_m_current_header'] = 'Aktive Abonnements';
@@ -244,8 +284,12 @@ $lang['subscr_m_receive']      = 'Erhalten';
 $lang['subscr_style_every']    = 'E-Mail bei jeder Änderung';
 $lang['subscr_style_digest']   = 'E-Mail mit zusammengefasster Übersicht der Seitenänderungen (alle %.2f Tage)';
 $lang['subscr_style_list']     = 'Auflistung aller geänderten Seiten seit der letzten E-Mail (alle %.2f Tage)';
+
+/* auth.class language support */
 $lang['authmodfailed']         = 'Benutzerüberprüfung nicht möglich. Bitte wende dich an den Admin.';
 $lang['authtempfail']          = 'Benutzerüberprüfung momentan nicht möglich. Falls das Problem andauert, wende dich an den Admin.';
+
+/* installer strings */
 $lang['i_chooselang']          = 'Wähle deine Sprache';
 $lang['i_installer']           = 'DokuWiki-Installation';
 $lang['i_wikiname']            = 'Wiki-Name';
@@ -268,6 +312,7 @@ $lang['i_pol1']                = 'Öffentliches Wiki (lesen für alle, schreiben
 $lang['i_pol2']                = 'Geschlossenes Wiki (lesen, schreiben, hochladen nur für registrierte Nutzer)';
 $lang['i_retry']               = 'Wiederholen';
 $lang['i_license']             = 'Bitte wähle die Lizenz aus unter der die Wiki-Inhalte veröffentlicht werden sollen:';
+
 $lang['mu_intro']              = 'In diesem Bereich kannst du mehrere Dateien gleichzeitig hochladen. Benutze die Schaltfläche "Durchsuchen", um sie der Warteschlange zuzufügen. Betätige die Schaltfläche "Hochladen", um die Übertragung zu starten.';
 $lang['mu_gridname']           = 'Dateiname';
 $lang['mu_gridsize']           = 'Größe';
@@ -283,6 +328,7 @@ $lang['mu_progress']           = '@PCT@% hochgeladen';
 $lang['mu_filetypes']          = 'Erlaubte Dateitypen';
 $lang['mu_info']               = 'Dateien hochgeladen.';
 $lang['mu_lasterr']            = 'Letzter Fehler:';
+
 $lang['recent_global']         = 'Im Moment siehst du die Änderungen im Namensraum <b>%s</b>. Du kannst auch <a href="%s">die Änderungen im gesamten Wiki sehen</a>.';
 $lang['years']                 = 'vor %d Jahren';
 $lang['months']                = 'vor %d Monaten';
@@ -291,4 +337,29 @@ $lang['days']                  = 'vor %d Tagen';
 $lang['hours']                 = 'vor %d Stunden';
 $lang['minutes']               = 'vor %d Minuten';
 $lang['seconds']               = 'vor %d Sekunden';
+
 $lang['wordblock']             = 'Deine Bearbeitung wurde nicht gespeichert, da sie gesperrten Text enthielt (Spam).';
+
+$lang['media_uploadtab']       = 'Hochladen';
+$lang['media_searchtab']       = 'Suchen';
+$lang['media_viewtab']         = 'Anzeigen';
+$lang['media_edittab']         = 'Bearbeiten';
+$lang['media_historytab']      = 'Verlauf';
+$lang['media_thumbsview']      = 'Miniaturansicht';
+$lang['media_listview']        = 'Detailansicht';
+$lang['media_sort']            = 'Sortieren';
+$lang['media_sort_name']       = 'nach Name';
+$lang['media_sort_date']       = 'nach Datum';
+$lang['media_upload']          = 'In den <strong>%s</strong> Namespace hochladen.';
+$lang['media_search']          = 'Im Namespace <strong>%s</strong> suchen.';
+$lang['media_edit']            = 'Bearbeiten';
+$lang['media_history']         = 'Versionsverlauf der Datei.';
+$lang['media_meta_edited']     = 'Meta-Informationen bearbeitet';
+$lang['media_perm_read']       = 'Du besitzt nicht die notwendigen Berechtigungen um die Datei anzuzeigen.';
+$lang['media_perm_upload']     = 'Du besitzt nicht die notwendigen Berechtigungen um Dateien hochzuladen.';
+$lang['media_update']          = 'Neue Version hochladen';
+$lang['media_restore']         = 'Diese Version wiederherstellen';
+
+$lang['plugin_install_err']    = "Plugin nicht korrekt installiert. Plugin-Verzeichnis von '%s' nach '%s' umbenennen.";
+
+//Setup VIM: ex: et ts=2 :
\ No newline at end of file
diff --git a/inc/lang/de-informal/uploadmail.txt b/inc/lang/de-informal/uploadmail.txt
index 7239cc10cc34d4db1719cd82a89770b72d64724d..69f11400f4a10b4e2308b833344a288462cc148a 100644
--- a/inc/lang/de-informal/uploadmail.txt
+++ b/inc/lang/de-informal/uploadmail.txt
@@ -1,13 +1,14 @@
 Eine Datei wurde in deinem Wiki hochgeladen. Hier sind die Details:
 
-Datei      : @MEDIA@
-Datum      : @DATE@
-Browser    : @BROWSER@
-IP-Adresse : @IPADDRESS@
-Hostname   : @HOSTNAME@
-Größe      : @SIZE@
-MIME-Typ   : @MIME@
-Benutzer   : @USER@
+Datei       : @MEDIA@
+Alte Version: @OLD@
+Datum       : @DATE@
+Browser     : @BROWSER@
+IP-Adresse  : @IPADDRESS@
+Hostname    : @HOSTNAME@
+Größe       : @SIZE@
+MIME-Typ    : @MIME@
+Benutzer    : @USER@
 
 -- 
 Diese Mail wurde vom DokuWiki auf
diff --git a/inc/lang/de/lang.php b/inc/lang/de/lang.php
index 3ae06dc7129a2f29bbcf80adfbaa08a65ab0ab6b..a289f99874132ba214a15a08dc53a148fb9608fe 100644
--- a/inc/lang/de/lang.php
+++ b/inc/lang/de/lang.php
@@ -18,14 +18,16 @@
  * @author Robert Bogenschneider <robog@gmx.de>
  * @author Niels Lange <niels@boldencursief.nl>
  * @author Christian Wichmann <nospam@zone0.de>
+ * @author Matthias Schulte <mailinglist@lupo49.de>
  */
 $lang['encoding']              = 'utf-8';
 $lang['direction']             = 'ltr';
-$lang['doublequoteopening']    = '„';
-$lang['doublequoteclosing']    = '“';
-$lang['singlequoteopening']    = '‚';
-$lang['singlequoteclosing']    = '‘';
-$lang['apostrophe']            = '’';
+$lang['doublequoteopening']    = '„'; //&ldquo;
+$lang['doublequoteclosing']    = '“'; //&rdquo;
+$lang['singlequoteopening']    = '‚'; //&lsquo;
+$lang['singlequoteclosing']    = '‘'; //&rsquo;
+$lang['apostrophe']            = '’'; //&rsquo;
+
 $lang['btn_edit']              = 'Diese Seite bearbeiten';
 $lang['btn_source']            = 'Zeige Quelltext';
 $lang['btn_show']              = 'Seite anzeigen';
@@ -59,6 +61,9 @@ $lang['btn_recover']           = 'Entwurf wiederherstellen';
 $lang['btn_draftdel']          = 'Entwurf löschen';
 $lang['btn_revert']            = 'Wiederherstellen';
 $lang['btn_register']          = 'Registrieren';
+$lang['btn_apply']             = 'Ãœbernehmen';
+$lang['btn_media']             = 'Medien-Manager';
+
 $lang['loggedinas']            = 'Angemeldet als';
 $lang['user']                  = 'Benutzername';
 $lang['pass']                  = 'Passwort';
@@ -71,8 +76,9 @@ $lang['email']                 = 'E-Mail';
 $lang['profile']               = 'Benutzerprofil';
 $lang['badlogin']              = 'Nutzername oder Passwort sind falsch.';
 $lang['minoredit']             = 'kleine Änderung';
-$lang['draftdate']             = 'Entwurf gespeichert am';
+$lang['draftdate']             = 'Entwurf gespeichert am'; // full dformat date will be added
 $lang['nosecedit']             = 'Diese Seite wurde in der Zwischenzeit geändert, Sektionsinfo ist veraltet, lade stattdessen volle Seite.';
+
 $lang['regmissing']            = 'Alle Felder müssen ausgefüllt werden.';
 $lang['reguexists']            = 'Der Nutzername existiert leider schon.';
 $lang['regsuccess']            = 'Der neue Nutzer wurde angelegt und das Passwort per E-Mail versandt.';
@@ -82,10 +88,12 @@ $lang['regbadmail']            = 'Die angegebene E-Mail-Adresse scheint ungülti
 $lang['regbadpass']            = 'Die beiden eingegeben Passwörter stimmen nicht überein. Bitte versuchen Sie es noch einmal.';
 $lang['regpwmail']             = 'Ihr DokuWiki Passwort';
 $lang['reghere']               = 'Sie haben noch keinen Zugang? Hier registrieren';
+
 $lang['profna']                = 'Änderung des Benutzerprofils in diesem Wiki nicht möglich.';
 $lang['profnochange']          = 'Keine Änderungen, nichts zu tun.';
 $lang['profnoempty']           = 'Es muss ein Name und eine E-Mail-Adresse angegeben werden.';
 $lang['profchanged']           = 'Benutzerprofil erfolgreich geändert.';
+
 $lang['pwdforget']             = 'Passwort vergessen? Fordere ein neues an';
 $lang['resendna']              = 'Passwörter versenden ist in diesem Wiki nicht möglich.';
 $lang['resendpwd']             = 'Neues Passwort senden für';
@@ -94,8 +102,10 @@ $lang['resendpwdnouser']       = 'Es tut mir Leid, aber der Benutzer existiert n
 $lang['resendpwdbadauth']      = 'Es tut mir Leid, aber dieser Authentifizierungscode ist ungültig. Stellen Sie sicher, dass Sie den kompletten Bestätigungslink verwendet haben.';
 $lang['resendpwdconfirm']      = 'Ein Bestätigungslink wurde per E-Mail versandt.';
 $lang['resendpwdsuccess']      = 'Ihr neues Passwort wurde per E-Mail versandt.';
+
 $lang['license']               = 'Falls nicht anders bezeichnet, ist der Inhalt dieses Wikis unter der folgenden Lizenz veröffentlicht:';
 $lang['licenseok']             = 'Hinweis: Durch das Bearbeiten dieser Seite geben Sie Ihr Einverständnis, dass Ihr Inhalt unter der folgenden Lizenz veröffentlicht wird:';
+
 $lang['searchmedia']           = 'Suche Dateinamen:';
 $lang['searchmedia_in']        = 'Suche in %s';
 $lang['txt_upload']            = 'Datei zum Hochladen auswählen';
@@ -103,7 +113,8 @@ $lang['txt_filename']          = 'Hochladen als (optional)';
 $lang['txt_overwrt']           = 'Bestehende Datei überschreiben';
 $lang['lockedby']              = 'Momentan gesperrt von';
 $lang['lockexpire']            = 'Sperre läuft ab am';
-$lang['js']['willexpire']            = 'Die Sperre zur Bearbeitung dieser Seite läuft in einer Minute ab.\nUm Bearbeitungskonflikte zu vermeiden, sollten Sie sie durch einen Klick auf den Vorschau-Knopf verlängern.';
+
+$lang['js']['willexpire']      = 'Die Sperre zur Bearbeitung dieser Seite läuft in einer Minute ab.\nUm Bearbeitungskonflikte zu vermeiden, sollten Sie sie durch einen Klick auf den Vorschau-Knopf verlängern.';
 $lang['js']['notsavedyet']     = 'Nicht gespeicherte Änderungen gehen verloren!';
 $lang['js']['searchmedia']     = 'Suche Dateien';
 $lang['js']['keepopen']        = 'Fenster nach Auswahl nicht schließen';
@@ -129,14 +140,26 @@ $lang['js']['medialeft']       = 'Das Bild links anordnen.';
 $lang['js']['mediaright']      = 'Das Bild rechts anordnen.';
 $lang['js']['mediacenter']     = 'Das Bild in der Mitte anordnen.';
 $lang['js']['medianoalign']    = 'Keine Anordnung benutzen.';
-$lang['js']['nosmblinks']      = 'Das Verlinken von Windows-Freigaben funktioniert nur im Microsoft Internet Explorer.
-Der Link kann jedoch durch Kopieren und Einfügen verwendet werden.';
+$lang['js']['nosmblinks']      = 'Das Verlinken von Windows-Freigaben funktioniert nur im Microsoft Internet Explorer.\nDer Link kann jedoch durch Kopieren und Einfügen verwendet werden.';
 $lang['js']['linkwiz']         = 'Link-Assistent';
 $lang['js']['linkto']          = 'Link nach:';
 $lang['js']['del_confirm']     = 'Eintrag wirklich löschen?';
 $lang['js']['mu_btn']          = 'Mehrere Dateien gleichzeitig hochladen';
+$lang['js']['restore_confirm'] = 'Really restore this version?';
+$lang['js']['media_diff']          = 'Unterschiede anzeigen:';
+$lang['js']['media_diff_both']     = 'Side by Side';
+$lang['js']['media_diff_opacity']  = 'Ãœberblenden';
+$lang['js']['media_diff_portions'] = 'Ãœbergang';
+$lang['js']['media_select']        = 'Dateien auswählen…';
+$lang['js']['media_upload_btn']    = 'Hochladen';
+$lang['js']['media_done_btn']      = 'Fertig';
+$lang['js']['media_drop']          = 'Dateien hier draufziehen um sie hochzuladen';
+$lang['js']['media_cancel']        = 'Entfernen';
+$lang['js']['media_overwrt']       = 'Existierende Dateien überschreiben';
+
 $lang['rssfailed']             = 'Es ist ein Fehler beim Laden des Feeds aufgetreten: ';
 $lang['nothingfound']          = 'Nichts gefunden.';
+
 $lang['mediaselect']           = 'Dateiauswahl';
 $lang['fileupload']            = 'Datei hochladen';
 $lang['uploadsucc']            = 'Datei wurde erfolgreich hochgeladen';
@@ -161,6 +184,7 @@ $lang['mediaextchange']        = 'Dateiendung vom .%s nach .%s geändert!';
 $lang['reference']             = 'Verwendung von';
 $lang['ref_inuse']             = 'Diese Datei kann nicht gelöscht werden, da sie noch von folgenden Seiten benutzt wird:';
 $lang['ref_hidden']            = 'Einige Verweise sind auf Seiten, für die Sie keine Leseberechtigung haben.';
+
 $lang['hits']                  = 'Treffer';
 $lang['quickhits']             = 'Passende Seitennamen';
 $lang['toc']                   = 'Inhaltsverzeichnis';
@@ -184,11 +208,18 @@ $lang['external_edit']         = 'Externe Bearbeitung';
 $lang['summary']               = 'Zusammenfassung';
 $lang['noflash']               = 'Das <a href="http://www.adobe.com/products/flashplayer/">Adobe Flash Plugin</a> wird benötigt, um diesen Inhalt anzuzeigen.';
 $lang['download']              = 'Schnipsel herunterladen';
+
 $lang['mail_newpage']          = 'Neue Seite:';
 $lang['mail_changed']          = 'Seite geändert:';
 $lang['mail_subscribe_list']   = 'Geänderte Seiten im Namensraum:';
 $lang['mail_new_user']         = 'Neuer Benutzer:';
 $lang['mail_upload']           = 'Datei hochgeladen:';
+
+$lang['changes_type']          = 'Änderungen anzeigen von';
+$lang['pages_changes']         = 'Seiten';
+$lang['media_changes']         = 'Mediendateien';
+$lang['both_changes']          = 'Beides, Seiten- und Mediendateien';
+
 $lang['qb_bold']               = 'Fetter Text';
 $lang['qb_italic']             = 'Kursiver Text';
 $lang['qb_underl']             = 'Unterstrichener Text';
@@ -213,8 +244,11 @@ $lang['qb_media']              = 'Bilder und andere Dateien hinzufügen';
 $lang['qb_sig']                = 'Unterschrift einfügen';
 $lang['qb_smileys']            = 'Smileys';
 $lang['qb_chars']              = 'Sonderzeichen';
+
 $lang['upperns']               = 'zum übergeordneten Namensraum springen';
+
 $lang['admin_register']        = 'Neuen Benutzer anmelden';
+
 $lang['metaedit']              = 'Metadaten bearbeiten';
 $lang['metasaveerr']           = 'Die Metadaten konnten nicht gesichert werden';
 $lang['metasaveok']            = 'Metadaten gesichert';
@@ -229,24 +263,33 @@ $lang['img_copyr']             = 'Copyright';
 $lang['img_format']            = 'Format';
 $lang['img_camera']            = 'Kamera';
 $lang['img_keywords']          = 'Schlagwörter';
-$lang['subscr_subscribe_success'] = '%s hat nun Änderungen der Seite %s abonniert';
-$lang['subscr_subscribe_error'] = '%s kann die Änderungen der Seite %s nicht abonnieren';
+$lang['img_width']             = 'Breite';
+$lang['img_height']            = 'Höhe';
+$lang['img_manager']           = 'Im Medien-Manager anzeigen';
+
+$lang['subscr_subscribe_success']   = '%s hat nun Änderungen der Seite %s abonniert';
+$lang['subscr_subscribe_error']     = '%s kann die Änderungen der Seite %s nicht abonnieren';
 $lang['subscr_subscribe_noaddress'] = 'Weil Ihre E-Mail-Adresse fehlt, können Sie das Thema nicht abonnieren';
 $lang['subscr_unsubscribe_success'] = 'Das Abonnement von %s für die Seite %s wurde aufgelöst';
-$lang['subscr_unsubscribe_error'] = 'Das Abonnement von %s für die Seite %s konnte nicht aufgelöst werden';
-$lang['subscr_already_subscribed'] = '%s hat %s bereits abonniert';
-$lang['subscr_not_subscribed'] = '%s hat %s nicht abonniert';
-$lang['subscr_m_not_subscribed'] = 'Sie haben die aktuelle Seite und ihre Namensräume nicht abonniert.';
-$lang['subscr_m_new_header']   = 'Abonnement hinzufügen';
-$lang['subscr_m_current_header'] = 'Aktuelle Abonnements';
-$lang['subscr_m_unsubscribe']  = 'Löschen';
-$lang['subscr_m_subscribe']    = 'Abonnieren';
-$lang['subscr_m_receive']      = 'Benachrichtigung';
-$lang['subscr_style_every']    = 'E-Mail bei jeder Bearbeitung';
-$lang['subscr_style_digest']   = 'Zusammenfassung der Änderungen für jede veränderte Seite (Alle %.2f Tage)';
-$lang['subscr_style_list']     = 'Liste der geänderten Seiten (Alle %.2f Tage)';
+$lang['subscr_unsubscribe_error']   = 'Das Abonnement von %s für die Seite %s konnte nicht aufgelöst werden';
+$lang['subscr_already_subscribed']  = '%s hat %s bereits abonniert';
+$lang['subscr_not_subscribed']      = '%s hat %s nicht abonniert';
+// Manage page for subscriptions
+$lang['subscr_m_not_subscribed']    = 'Sie haben die aktuelle Seite und ihre Namensräume nicht abonniert.';
+$lang['subscr_m_new_header']        = 'Abonnement hinzufügen';
+$lang['subscr_m_current_header']    = 'Aktuelle Abonnements';
+$lang['subscr_m_unsubscribe']       = 'Löschen';
+$lang['subscr_m_subscribe']         = 'Abonnieren';
+$lang['subscr_m_receive']           = 'Benachrichtigung';
+$lang['subscr_style_every']         = 'E-Mail bei jeder Bearbeitung';
+$lang['subscr_style_digest']        = 'Zusammenfassung der Änderungen für jede veränderte Seite (Alle %.2f Tage)';
+$lang['subscr_style_list']          = 'Liste der geänderten Seiten (Alle %.2f Tage)';
+
+/* auth.class language support */
 $lang['authmodfailed']         = 'Benutzerüberprüfung nicht möglich. Bitte wenden Sie sich an den Systembetreuer.';
 $lang['authtempfail']          = 'Benutzerüberprüfung momentan nicht möglich. Falls das Problem andauert, wenden Sie sich an den Systembetreuer.';
+
+/* installer strings */
 $lang['i_chooselang']          = 'Wählen Sie Ihre Sprache';
 $lang['i_installer']           = 'DokuWiki Installation';
 $lang['i_wikiname']            = 'Wiki-Name';
@@ -269,6 +312,7 @@ $lang['i_pol1']                = 'Öffentliches Wiki (lesen für alle, schreiben
 $lang['i_pol2']                = 'Geschlossenes Wiki (lesen, schreiben, hochladen nur für registrierte Nutzer)';
 $lang['i_retry']               = 'Wiederholen';
 $lang['i_license']             = 'Bitte wählen Sie die Lizenz, unter die Sie Ihre Inhalte stellen möchten:';
+
 $lang['mu_intro']              = 'In diesem Bereich können Sie mehrere Dateien gleichzeitig hochladen. Benutzen Sie die Schaltfläche "Durchsuchen" um sie der Warteschlange zuzufügen. Betätigen Sie die Schaltfläche "Hochladen" um die Übertragung zu starten.';
 $lang['mu_gridname']           = 'Dateiname';
 $lang['mu_gridsize']           = 'Größe';
@@ -284,6 +328,7 @@ $lang['mu_progress']           = '@PCT@% hochgeladen';
 $lang['mu_filetypes']          = 'Erlaubte Dateitypen';
 $lang['mu_info']               = 'Dateien hochgeladen!';
 $lang['mu_lasterr']            = 'Letzter Fehler:';
+
 $lang['recent_global']         = 'Im Moment sehen Sie die Änderungen im Namensraum <b>%s</b>. Sie können auch <a href="%s">die Änderungen im gesamten Wiki sehen</a>.';
 $lang['years']                 = 'vor %d Jahren';
 $lang['months']                = 'vor %d Monaten';
@@ -292,4 +337,29 @@ $lang['days']                  = 'vor %d Tagen';
 $lang['hours']                 = 'vor %d Stunden';
 $lang['minutes']               = 'vor %d Minuten';
 $lang['seconds']               = 'vor %d Sekunden';
+
 $lang['wordblock']             = 'Ihre Bearbeitung wurde nicht gespeichert, da sie gesperrten Text enthielt (Spam).';
+
+$lang['media_uploadtab']       = 'Hochladen';
+$lang['media_searchtab']       = 'Suchen';
+$lang['media_viewtab']         = 'Anzeigen';
+$lang['media_edittab']         = 'Bearbeiten';
+$lang['media_historytab']      = 'Verlauf';
+$lang['media_thumbsview']      = 'Miniaturansicht';
+$lang['media_listview']        = 'Detailansicht';
+$lang['media_sort']            = 'Sortieren';
+$lang['media_sort_name']       = 'nach Name';
+$lang['media_sort_date']       = 'nach Datum';
+$lang['media_upload']          = 'In den <strong>%s</strong> Namespace hochladen.';
+$lang['media_search']          = 'Im Namespace <strong>%s</strong> suchen.';
+$lang['media_edit']            = 'Bearbeiten';
+$lang['media_history']         = 'Versionsverlauf der Datei.';
+$lang['media_meta_edited']     = 'Meta-Informationen bearbeitet';
+$lang['media_perm_read']       = 'Sie besitzen nicht die notwendigen Berechtigungen um die Datei anzuzeigen.';
+$lang['media_perm_upload']     = 'Sie besitzen nicht die notwendigen Berechtigungen um Dateien hochzuladen.';
+$lang['media_update']          = 'Neue Version hochladen';
+$lang['media_restore']         = 'Diese Version wiederherstellen';
+
+$lang['plugin_install_err']    = "Plugin nicht korrekt installiert. Plugin-Verzeichnis von '%s' nach '%s' umbenennen.";
+
+//Setup VIM: ex: et ts=2 :
\ No newline at end of file
diff --git a/inc/lang/de/uploadmail.txt b/inc/lang/de/uploadmail.txt
index 7577298041f3104f4fb5ab4aa1f7a36c688c2a70..977e7561c22dd0ae60ef5d7bc11aa7ac2b752581 100644
--- a/inc/lang/de/uploadmail.txt
+++ b/inc/lang/de/uploadmail.txt
@@ -1,13 +1,14 @@
 Eine Datei wurde in Ihrem Wiki hochgeladen. Hier sind die Details:
 
-Datei      : @MEDIA@
-Datum      : @DATE@
-Browser    : @BROWSER@
-IP-Adresse : @IPADDRESS@
-Hostname   : @HOSTNAME@
-Größe      : @SIZE@
-MIME-Typ   : @MIME@
-Benutzer   : @USER@
+Datei       : @MEDIA@
+Alte Version: @OLD@
+Datum       : @DATE@
+Browser     : @BROWSER@
+IP-Adresse  : @IPADDRESS@
+Hostname    : @HOSTNAME@
+Größe       : @SIZE@
+MIME-Typ    : @MIME@
+Benutzer    : @USER@
 
 -- 
 Diese Mail kommt vom DokuWiki auf
diff --git a/inc/lang/en/lang.php b/inc/lang/en/lang.php
index 95356f7b4cf6a0e9f72f18ee52041f29a26e0d76..d97de87d5814daf3052e86bf237257b11c2e90b2 100644
--- a/inc/lang/en/lang.php
+++ b/inc/lang/en/lang.php
@@ -6,317 +6,352 @@
  * @author     Andreas Gohr <andi@splitbrain.org>
  * @author     Anika Henke <anika@selfthinker.org>
  * @author     Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ * @author     Matthias Schulte <mailinglist@lupo49.de>
  */
-$lang['encoding']   = 'utf-8';
-$lang['direction']  = 'ltr';
-$lang['doublequoteopening']  = '“';//&ldquo;
-$lang['doublequoteclosing']  = '”';//&rdquo;
-$lang['singlequoteopening']  = '‘';//&lsquo;
-$lang['singlequoteclosing']  = '’';//&rsquo;
-$lang['apostrophe']          = '’';//&rsquo;
-
-$lang['btn_edit']   = 'Edit this page';
-$lang['btn_source'] = 'Show pagesource';
-$lang['btn_show']   = 'Show page';
-$lang['btn_create'] = 'Create this page';
-$lang['btn_search'] = 'Search';
-$lang['btn_save']   = 'Save';
-$lang['btn_preview']= 'Preview';
-$lang['btn_top']    = 'Back to top';
-$lang['btn_newer']  = '<< more recent';
-$lang['btn_older']  = 'less recent >>';
-$lang['btn_revs']   = 'Old revisions';
-$lang['btn_recent'] = 'Recent changes';
-$lang['btn_upload'] = 'Upload';
-$lang['btn_cancel'] = 'Cancel';
-$lang['btn_index']  = 'Sitemap';
-$lang['btn_secedit']= 'Edit';
-$lang['btn_login']  = 'Login';
-$lang['btn_logout'] = 'Logout';
-$lang['btn_admin']  = 'Admin';
-$lang['btn_update'] = 'Update';
-$lang['btn_delete'] = 'Delete';
-$lang['btn_back']   = 'Back';
-$lang['btn_backlink']    = "Backlinks";
-$lang['btn_backtomedia'] = 'Back to Mediafile Selection';
-$lang['btn_subscribe']   = 'Manage Subscriptions';
-$lang['btn_profile']     = 'Update Profile';
-$lang['btn_reset']       = 'Reset';
-$lang['btn_resendpwd']   = 'Send new password';
-$lang['btn_draft']    = 'Edit draft';
-$lang['btn_recover']  = 'Recover draft';
-$lang['btn_draftdel'] = 'Delete draft';
-$lang['btn_revert']   = 'Restore';
-$lang['btn_register'] = 'Register';
-
-$lang['loggedinas'] = 'Logged in as';
-$lang['user']       = 'Username';
-$lang['pass']       = 'Password';
-$lang['newpass']    = 'New password';
-$lang['oldpass']    = 'Confirm current password';
-$lang['passchk']    = 'once again';
-$lang['remember']   = 'Remember me';
-$lang['fullname']   = 'Real name';
-$lang['email']      = 'E-Mail';
-$lang['profile']    = 'User Profile';
-$lang['badlogin']   = 'Sorry, username or password was wrong.';
-$lang['minoredit']  = 'Minor Changes';
-$lang['draftdate']  = 'Draft autosaved on'; // full dformat date will be added
-$lang['nosecedit']  = 'The page was changed in the meantime, section info was out of date loaded full page instead.';
-
-$lang['regmissing'] = 'Sorry, you must fill in all fields.';
-$lang['reguexists'] = 'Sorry, a user with this login already exists.';
-$lang['regsuccess'] = 'The user has been created and the password was sent by email.';
-$lang['regsuccess2']= 'The user has been created.';
-$lang['regmailfail']= 'Looks like there was an error on sending the password mail. Please contact the admin!';
-$lang['regbadmail'] = 'The given email address looks invalid - if you think this is an error, contact the admin';
-$lang['regbadpass'] = 'The two given passwords are not identical, please try again.';
-$lang['regpwmail']  = 'Your DokuWiki password';
-$lang['reghere']    = 'You don\'t have an account yet? Just get one';
-
-$lang['profna']       = 'This wiki does not support profile modification';
-$lang['profnochange'] = 'No changes, nothing to do.';
-$lang['profnoempty']  = 'An empty name or email address is not allowed.';
-$lang['profchanged']  = 'User profile successfully updated.';
-
-$lang['pwdforget'] = 'Forgotten your password? Get a new one';
-$lang['resendna']  = 'This wiki does not support password resending.';
-$lang['resendpwd'] = 'Send new password for';
-$lang['resendpwdmissing'] = 'Sorry, you must fill in all fields.';
-$lang['resendpwdnouser']  = 'Sorry, we can\'t find this user in our database.';
-$lang['resendpwdbadauth'] = 'Sorry, this auth code is not valid. Make sure you used the complete confirmation link.';
-$lang['resendpwdconfirm'] = 'A confirmation link has been sent by email.';
-$lang['resendpwdsuccess'] = 'Your new password has been sent by email.';
-
-$lang['license']   = 'Except where otherwise noted, content on this wiki is licensed under the following license:';
-$lang['licenseok'] = 'Note: By editing this page you agree to license your content under the following license:';
-
-$lang['searchmedia']  = 'Search file name:';
-$lang['searchmedia_in']  = 'Search in %s';
-$lang['txt_upload']   = 'Select file to upload';
-$lang['txt_filename'] = 'Upload as (optional)';
-$lang['txt_overwrt']  = 'Overwrite existing file';
-$lang['lockedby']     = 'Currently locked by';
-$lang['lockexpire']   = 'Lock expires at';
-$lang['js']['willexpire']   = 'Your lock for editing this page is about to expire in a minute.\nTo avoid conflicts use the preview button to reset the locktimer.';
-
-$lang['js']['notsavedyet'] = "Unsaved changes will be lost.";
-$lang['rssfailed']   = 'An error occurred while fetching this feed: ';
-$lang['nothingfound']= 'Nothing was found.';
-
-$lang['mediaselect'] = 'Media Files';
-$lang['fileupload']  = 'Media File Upload';
-$lang['uploadsucc']  = 'Upload successful';
-$lang['uploadfail']  = 'Upload failed. Maybe wrong permissions?';
-$lang['uploadwrong'] = 'Upload denied. This file extension is forbidden!';
-$lang['uploadexist'] = 'File already exists. Nothing done.';
-$lang['uploadbadcontent'] = 'The uploaded content did not match the %s file extension.';
-$lang['uploadspam']  = 'The upload was blocked by the spam blacklist.';
-$lang['uploadxss']   = 'The upload was blocked for possibly malicious content.';
-$lang['uploadsize']  = 'The uploaded file was too big. (max. %s)';
-$lang['deletesucc']  = 'The file "%s" has been deleted.';
-$lang['deletefail']  = '"%s" couldn\'t be deleted - check permissions.';
-$lang['mediainuse']  = 'The file "%s" hasn\'t been deleted - it is still in use.';
-$lang['namespaces']  = 'Namespaces';
-$lang['mediafiles']  = 'Available files in';
-$lang['accessdenied'] = 'You are not allowed to view this page.';
-
-$lang['js']['searchmedia']    = 'Search for files';
-$lang['js']['keepopen']    = 'Keep window open on selection';
-$lang['js']['hidedetails'] = 'Hide Details';
-$lang['mediausage']  = 'Use the following syntax to reference this file:';
-$lang['mediaview']   = 'View original file';
-$lang['mediaroot']   = 'root';
-$lang['mediaupload'] = 'Upload a file to the current namespace here. To create subnamespaces, prepend them to your "Upload as" filename separated by colons.';
-$lang['mediaextchange'] = 'Filextension changed from .%s to .%s!';
-
-$lang['js']['mediatitle']       = 'Link settings';
-$lang['js']['mediadisplay']     = 'Link type';
-$lang['js']['mediaalign']       = 'Alignment';
-$lang['js']['mediasize']        = 'Image size';
-$lang['js']['mediatarget']      = 'Link target';
-$lang['js']['mediaclose']       = 'Close';
-$lang['js']['mediainsert']      = 'Insert';
-$lang['js']['mediadisplayimg']  = 'Show the image.';
-$lang['js']['mediadisplaylnk']  = 'Show only the link.';
-$lang['js']['mediasmall']       = 'Small version';
-$lang['js']['mediamedium']      = 'Medium version';
-$lang['js']['medialarge']       = 'Large version';
-$lang['js']['mediaoriginal']    = 'Original version';
-$lang['js']['medialnk']         = 'Link to detail page';
-$lang['js']['mediadirect']      = 'Direct link to original';
-$lang['js']['medianolnk']       = 'No link';
-$lang['js']['medianolink']      = 'Do not link the image';
-$lang['js']['medialeft']        = 'Align the image on the left.';
-$lang['js']['mediaright']       = 'Align the image on the right.';
-$lang['js']['mediacenter']      = 'Align the image in the middle.';
-$lang['js']['medianoalign']     = 'Use no align.';
-
-$lang['reference']   = 'References for';
-$lang['ref_inuse']   = 'The file can\'t be deleted, because it\'s still used by the following pages:';
-$lang['ref_hidden']  = 'Some references  are on pages you don\'t have permission to read';
-
-$lang['hits']       = 'Hits';
-$lang['quickhits']  = 'Matching pagenames';
-$lang['toc']        = 'Table of Contents';
-$lang['current']    = 'current';
-$lang['yours']      = 'Your Version';
-$lang['diff']       = 'Show differences to current revisions';
-$lang['diff2']      = 'Show differences between selected revisions';
-$lang['difflink']   = 'Link to this comparison view';
-$lang['diff_type']  = 'View differences:';
-$lang['diff_inline']= 'Inline';
-$lang['diff_side']  = 'Side by Side';
-$lang['line']       = 'Line';
-$lang['breadcrumb'] = 'Trace';
-$lang['youarehere'] = 'You are here';
-$lang['lastmod']    = 'Last modified';
-$lang['by']         = 'by';
-$lang['deleted']    = 'removed';
-$lang['created']    = 'created';
-$lang['restored']   = 'old revision restored';
-$lang['external_edit'] = 'external edit';
-$lang['summary']    = 'Edit summary';
-$lang['noflash']    = 'The <a href="http://www.adobe.com/products/flashplayer/">Adobe Flash Plugin</a> is needed to display this content.';
-$lang['download']   = 'Download Snippet';
-
-$lang['mail_newpage']  = 'page added:';
-$lang['mail_changed']  = 'page changed:';
-$lang['mail_subscribe_list'] = 'pages changed in namespace:';
-$lang['mail_new_user'] = 'new user:';
-$lang['mail_upload']   = 'file uploaded:';
-
-$lang['js']['nosmblinks'] = "Linking to Windows shares only works in Microsoft Internet Explorer.\nYou still can copy and paste the link.";
-
-$lang['qb_bold']    = 'Bold Text';
-$lang['qb_italic']  = 'Italic Text';
-$lang['qb_underl']  = 'Underlined Text';
-$lang['qb_code']    = 'Code Text';
-$lang['qb_strike']  = 'Strike-through Text';
-$lang['qb_h1']      = 'Level 1 Headline';
-$lang['qb_h2']      = 'Level 2 Headline';
-$lang['qb_h3']      = 'Level 3 Headline';
-$lang['qb_h4']      = 'Level 4 Headline';
-$lang['qb_h5']      = 'Level 5 Headline';
-
-$lang['qb_h']       = 'Headline';
-$lang['qb_hs']      = 'Select Headline';
-$lang['qb_hplus']   = 'Higher Headline';
-$lang['qb_hminus']  = 'Lower Headline';
-$lang['qb_hequal']  = 'Same Level Headline';
-
-$lang['qb_link']    = 'Internal Link';
-$lang['qb_extlink'] = 'External Link';
-$lang['qb_hr']      = 'Horizontal Rule';
-$lang['qb_ol']      = 'Ordered List Item';
-$lang['qb_ul']      = 'Unordered List Item';
-$lang['qb_media']   = 'Add Images and other files';
-$lang['qb_sig']     = 'Insert Signature';
-$lang['qb_smileys'] = 'Smileys';
-$lang['qb_chars']   = 'Special Chars';
-
-$lang['upperns']       = 'jump to parent namespace';
-$lang['js']['linkwiz'] = 'Link Wizard';
-$lang['js']['linkto']  = 'Link to:';
-
-$lang['js']['del_confirm']= 'Really delete selected item(s)?';
-$lang['admin_register']= 'Add new user';
-
-$lang['metaedit']    = 'Edit Metadata';
-$lang['metasaveerr'] = 'Writing metadata failed';
-$lang['metasaveok']  = 'Metadata saved';
-$lang['img_backto']  = 'Back to';
-$lang['img_title']   = 'Title';
-$lang['img_caption'] = 'Caption';
-$lang['img_date']    = 'Date';
-$lang['img_fname']   = 'Filename';
-$lang['img_fsize']   = 'Size';
-$lang['img_artist']  = 'Photographer';
-$lang['img_copyr']   = 'Copyright';
-$lang['img_format']  = 'Format';
-$lang['img_camera']  = 'Camera';
-$lang['img_keywords']= 'Keywords';
-
-$lang['subscr_subscribe_success']  = 'Added %s to subscription list for %s';
-$lang['subscr_subscribe_error']    = 'Error adding %s to subscription list for %s';
-$lang['subscr_subscribe_noaddress']= 'There is no address associated with your login, you cannot be added to the subscription list';
-$lang['subscr_unsubscribe_success']= 'Removed %s from subscription list for %s';
-$lang['subscr_unsubscribe_error']  = 'Error removing %s from subscription list for %s';
-$lang['subscr_already_subscribed'] = '%s is already subscribed to %s';
-$lang['subscr_not_subscribed']     = '%s is not subscribed to %s';
+$lang['encoding']              = 'utf-8';
+$lang['direction']             = 'ltr';
+$lang['doublequoteopening']    = '“'; //&ldquo;
+$lang['doublequoteclosing']    = '”'; //&rdquo;
+$lang['singlequoteopening']    = '‘'; //&lsquo;
+$lang['singlequoteclosing']    = '’'; //&rsquo;
+$lang['apostrophe']            = '’'; //&rsquo;
+
+$lang['btn_edit']              = 'Edit this page';
+$lang['btn_source']            = 'Show pagesource';
+$lang['btn_show']              = 'Show page';
+$lang['btn_create']            = 'Create this page';
+$lang['btn_search']            = 'Search';
+$lang['btn_save']              = 'Save';
+$lang['btn_preview']           = 'Preview';
+$lang['btn_top']               = 'Back to top';
+$lang['btn_newer']             = '<< more recent';
+$lang['btn_older']             = 'less recent >>';
+$lang['btn_revs']              = 'Old revisions';
+$lang['btn_recent']            = 'Recent changes';
+$lang['btn_upload']            = 'Upload';
+$lang['btn_cancel']            = 'Cancel';
+$lang['btn_index']             = 'Sitemap';
+$lang['btn_secedit']           = 'Edit';
+$lang['btn_login']             = 'Login';
+$lang['btn_logout']            = 'Logout';
+$lang['btn_admin']             = 'Admin';
+$lang['btn_update']            = 'Update';
+$lang['btn_delete']            = 'Delete';
+$lang['btn_back']              = 'Back';
+$lang['btn_backlink']          = "Backlinks";
+$lang['btn_backtomedia']       = 'Back to Mediafile Selection';
+$lang['btn_subscribe']         = 'Manage Subscriptions';
+$lang['btn_profile']           = 'Update Profile';
+$lang['btn_reset']             = 'Reset';
+$lang['btn_resendpwd']         = 'Send new password';
+$lang['btn_draft']             = 'Edit draft';
+$lang['btn_recover']           = 'Recover draft';
+$lang['btn_draftdel']          = 'Delete draft';
+$lang['btn_revert']            = 'Restore';
+$lang['btn_register']          = 'Register';
+$lang['btn_apply']             = 'Apply';
+$lang['btn_media']             = 'Media Manager';
+
+$lang['loggedinas']            = 'Logged in as';
+$lang['user']                  = 'Username';
+$lang['pass']                  = 'Password';
+$lang['newpass']               = 'New password';
+$lang['oldpass']               = 'Confirm current password';
+$lang['passchk']               = 'once again';
+$lang['remember']              = 'Remember me';
+$lang['fullname']              = 'Real name';
+$lang['email']                 = 'E-Mail';
+$lang['profile']               = 'User Profile';
+$lang['badlogin']              = 'Sorry, username or password was wrong.';
+$lang['minoredit']             = 'Minor Changes';
+$lang['draftdate']             = 'Draft autosaved on'; // full dformat date will be added
+$lang['nosecedit']             = 'The page was changed in the meantime, section info was out of date loaded full page instead.';
+
+$lang['regmissing']            = 'Sorry, you must fill in all fields.';
+$lang['reguexists']            = 'Sorry, a user with this login already exists.';
+$lang['regsuccess']            = 'The user has been created and the password was sent by email.';
+$lang['regsuccess2']           = 'The user has been created.';
+$lang['regmailfail']           = 'Looks like there was an error on sending the password mail. Please contact the admin!';
+$lang['regbadmail']            = 'The given email address looks invalid - if you think this is an error, contact the admin';
+$lang['regbadpass']            = 'The two given passwords are not identical, please try again.';
+$lang['regpwmail']             = 'Your DokuWiki password';
+$lang['reghere']               = 'You don\'t have an account yet? Just get one';
+
+$lang['profna']                = 'This wiki does not support profile modification';
+$lang['profnochange']          = 'No changes, nothing to do.';
+$lang['profnoempty']           = 'An empty name or email address is not allowed.';
+$lang['profchanged']           = 'User profile successfully updated.';
+
+$lang['pwdforget']             = 'Forgotten your password? Get a new one';
+$lang['resendna']              = 'This wiki does not support password resending.';
+$lang['resendpwd']             = 'Send new password for';
+$lang['resendpwdmissing']      = 'Sorry, you must fill in all fields.';
+$lang['resendpwdnouser']       = 'Sorry, we can\'t find this user in our database.';
+$lang['resendpwdbadauth']      = 'Sorry, this auth code is not valid. Make sure you used the complete confirmation link.';
+$lang['resendpwdconfirm']      = 'A confirmation link has been sent by email.';
+$lang['resendpwdsuccess']      = 'Your new password has been sent by email.';
+
+$lang['license']               = 'Except where otherwise noted, content on this wiki is licensed under the following license:';
+$lang['licenseok']             = 'Note: By editing this page you agree to license your content under the following license:';
+
+$lang['searchmedia']           = 'Search file name:';
+$lang['searchmedia_in']        = 'Search in %s';
+$lang['txt_upload']            = 'Select file to upload';
+$lang['txt_filename']          = 'Upload as (optional)';
+$lang['txt_overwrt']           = 'Overwrite existing file';
+$lang['lockedby']              = 'Currently locked by';
+$lang['lockexpire']            = 'Lock expires at';
+
+$lang['js']['willexpire']      = 'Your lock for editing this page is about to expire in a minute.\nTo avoid conflicts use the preview button to reset the locktimer.';
+$lang['js']['notsavedyet']     = "Unsaved changes will be lost.";
+$lang['js']['searchmedia']     = 'Search for files';
+$lang['js']['keepopen']        = 'Keep window open on selection';
+$lang['js']['hidedetails']     = 'Hide Details';
+$lang['js']['mediatitle']      = 'Link settings';
+$lang['js']['mediadisplay']    = 'Link type';
+$lang['js']['mediaalign']      = 'Alignment';
+$lang['js']['mediasize']       = 'Image size';
+$lang['js']['mediatarget']     = 'Link target';
+$lang['js']['mediaclose']      = 'Close';
+$lang['js']['mediainsert']     = 'Insert';
+$lang['js']['mediadisplayimg'] = 'Show the image.';
+$lang['js']['mediadisplaylnk'] = 'Show only the link.';
+$lang['js']['mediasmall']      = 'Small version';
+$lang['js']['mediamedium']     = 'Medium version';
+$lang['js']['medialarge']      = 'Large version';
+$lang['js']['mediaoriginal']   = 'Original version';
+$lang['js']['medialnk']        = 'Link to detail page';
+$lang['js']['mediadirect']     = 'Direct link to original';
+$lang['js']['medianolnk']      = 'No link';
+$lang['js']['medianolink']     = 'Do not link the image';
+$lang['js']['medialeft']       = 'Align the image on the left.';
+$lang['js']['mediaright']      = 'Align the image on the right.';
+$lang['js']['mediacenter']     = 'Align the image in the middle.';
+$lang['js']['medianoalign']    = 'Use no align.';
+$lang['js']['nosmblinks']      = "Linking to Windows shares only works in Microsoft Internet Explorer.\nYou still can copy and paste the link.";
+$lang['js']['linkwiz']         = 'Link Wizard';
+$lang['js']['linkto']          = 'Link to:';
+$lang['js']['del_confirm']     = 'Really delete selected item(s)?';
+$lang['js']['mu_btn']          = 'Upload multiple files at once';
+$lang['js']['restore_confirm'] = 'Really restore this version?';
+$lang['js']['media_diff']          = 'View differences:';
+$lang['js']['media_diff_both']     = 'Side by Side';
+$lang['js']['media_diff_opacity']  = 'Overlay';
+$lang['js']['media_diff_portions'] = 'Slider';
+$lang['js']['media_select']        = 'Select files…';
+$lang['js']['media_upload_btn']    = 'Upload';
+$lang['js']['media_done_btn']      = 'Done';
+$lang['js']['media_drop']          = 'Drop files here to upload';
+$lang['js']['media_cancel']        = 'remove';
+$lang['js']['media_overwrt']       = 'Overwrite existing files';
+
+$lang['rssfailed']             = 'An error occurred while fetching this feed: ';
+$lang['nothingfound']          = 'Nothing was found.';
+
+$lang['mediaselect']           = 'Media Files';
+$lang['fileupload']            = 'Media File Upload';
+$lang['uploadsucc']            = 'Upload successful';
+$lang['uploadfail']            = 'Upload failed. Maybe wrong permissions?';
+$lang['uploadwrong']           = 'Upload denied. This file extension is forbidden!';
+$lang['uploadexist']           = 'File already exists. Nothing done.';
+$lang['uploadbadcontent']      = 'The uploaded content did not match the %s file extension.';
+$lang['uploadspam']            = 'The upload was blocked by the spam blacklist.';
+$lang['uploadxss']             = 'The upload was blocked for possibly malicious content.';
+$lang['uploadsize']            = 'The uploaded file was too big. (max. %s)';
+$lang['deletesucc']            = 'The file "%s" has been deleted.';
+$lang['deletefail']            = '"%s" couldn\'t be deleted - check permissions.';
+$lang['mediainuse']            = 'The file "%s" hasn\'t been deleted - it is still in use.';
+$lang['namespaces']            = 'Namespaces';
+$lang['mediafiles']            = 'Available files in';
+$lang['accessdenied']          = 'You are not allowed to view this page.';
+$lang['mediausage']            = 'Use the following syntax to reference this file:';
+$lang['mediaview']             = 'View original file';
+$lang['mediaroot']             = 'root';
+$lang['mediaupload']           = 'Upload a file to the current namespace here. To create subnamespaces, prepend them to your "Upload as" filename separated by colons. Files also can be selected by drag and drop.';
+$lang['mediaextchange']        = 'Filextension changed from .%s to .%s!';
+$lang['reference']             = 'References for';
+$lang['ref_inuse']             = 'The file can\'t be deleted, because it\'s still used by the following pages:';
+$lang['ref_hidden']            = 'Some references  are on pages you don\'t have permission to read';
+
+$lang['hits']                  = 'Hits';
+$lang['quickhits']             = 'Matching pagenames';
+$lang['toc']                   = 'Table of Contents';
+$lang['current']               = 'current';
+$lang['yours']                 = 'Your Version';
+$lang['diff']                  = 'Show differences to current revisions';
+$lang['diff2']                 = 'Show differences between selected revisions';
+$lang['difflink']              = 'Link to this comparison view';
+$lang['diff_type']             = 'View differences:';
+$lang['diff_inline']           = 'Inline';
+$lang['diff_side']             = 'Side by Side';
+$lang['line']                  = 'Line';
+$lang['breadcrumb']            = 'Trace';
+$lang['youarehere']            = 'You are here';
+$lang['lastmod']               = 'Last modified';
+$lang['by']                    = 'by';
+$lang['deleted']               = 'removed';
+$lang['created']               = 'created';
+$lang['restored']              = 'old revision restored';
+$lang['external_edit']         = 'external edit';
+$lang['summary']               = 'Edit summary';
+$lang['noflash']               = 'The <a href="http://www.adobe.com/products/flashplayer/">Adobe Flash Plugin</a> is needed to display this content.';
+$lang['download']              = 'Download Snippet';
+
+$lang['mail_newpage']          = 'page added:';
+$lang['mail_changed']          = 'page changed:';
+$lang['mail_subscribe_list']   = 'pages changed in namespace:';
+$lang['mail_new_user']         = 'new user:';
+$lang['mail_upload']           = 'file uploaded:';
+
+$lang['changes_type']          = 'View changes of';
+$lang['pages_changes']         = 'Pages';
+$lang['media_changes']         = 'Media files';
+$lang['both_changes']          = 'Both pages and media files';
+
+$lang['qb_bold']               = 'Bold Text';
+$lang['qb_italic']             = 'Italic Text';
+$lang['qb_underl']             = 'Underlined Text';
+$lang['qb_code']               = 'Code Text';
+$lang['qb_strike']             = 'Strike-through Text';
+$lang['qb_h1']                 = 'Level 1 Headline';
+$lang['qb_h2']                 = 'Level 2 Headline';
+$lang['qb_h3']                 = 'Level 3 Headline';
+$lang['qb_h4']                 = 'Level 4 Headline';
+$lang['qb_h5']                 = 'Level 5 Headline';
+$lang['qb_h']                  = 'Headline';
+$lang['qb_hs']                 = 'Select Headline';
+$lang['qb_hplus']              = 'Higher Headline';
+$lang['qb_hminus']             = 'Lower Headline';
+$lang['qb_hequal']             = 'Same Level Headline';
+$lang['qb_link']               = 'Internal Link';
+$lang['qb_extlink']            = 'External Link';
+$lang['qb_hr']                 = 'Horizontal Rule';
+$lang['qb_ol']                 = 'Ordered List Item';
+$lang['qb_ul']                 = 'Unordered List Item';
+$lang['qb_media']              = 'Add Images and other files';
+$lang['qb_sig']                = 'Insert Signature';
+$lang['qb_smileys']            = 'Smileys';
+$lang['qb_chars']              = 'Special Chars';
+
+$lang['upperns']               = 'jump to parent namespace';
+
+$lang['admin_register']        = 'Add new user';
+
+$lang['metaedit']              = 'Edit Metadata';
+$lang['metasaveerr']           = 'Writing metadata failed';
+$lang['metasaveok']            = 'Metadata saved';
+$lang['img_backto']            = 'Back to';
+$lang['img_title']             = 'Title';
+$lang['img_caption']           = 'Caption';
+$lang['img_date']              = 'Date';
+$lang['img_fname']             = 'Filename';
+$lang['img_fsize']             = 'Size';
+$lang['img_artist']            = 'Photographer';
+$lang['img_copyr']             = 'Copyright';
+$lang['img_format']            = 'Format';
+$lang['img_camera']            = 'Camera';
+$lang['img_keywords']          = 'Keywords';
+$lang['img_width']             = 'Width';
+$lang['img_height']            = 'Height';
+$lang['img_manager']           = 'View in media manager';
+
+$lang['subscr_subscribe_success']   = 'Added %s to subscription list for %s';
+$lang['subscr_subscribe_error']     = 'Error adding %s to subscription list for %s';
+$lang['subscr_subscribe_noaddress'] = 'There is no address associated with your login, you cannot be added to the subscription list';
+$lang['subscr_unsubscribe_success'] = 'Removed %s from subscription list for %s';
+$lang['subscr_unsubscribe_error']   = 'Error removing %s from subscription list for %s';
+$lang['subscr_already_subscribed']  = '%s is already subscribed to %s';
+$lang['subscr_not_subscribed']      = '%s is not subscribed to %s';
 // Manage page for subscriptions
-$lang['subscr_m_not_subscribed']   = 'You are currently not subscribed to the current page or namespace.';
-$lang['subscr_m_new_header']       = 'Add subscription';
-$lang['subscr_m_current_header']   = 'Current subscriptions';
-$lang['subscr_m_unsubscribe']      = 'Unsubscribe';
-$lang['subscr_m_subscribe']        = 'Subscribe';
-$lang['subscr_m_receive']          = 'Receive';
-$lang['subscr_style_every']        = 'email on every change';
-$lang['subscr_style_digest']       = 'digest email of changes for each page (every %.2f days)';
-$lang['subscr_style_list']         = 'list of changed pages since last email (every %.2f days)';
-
+$lang['subscr_m_not_subscribed']    = 'You are currently not subscribed to the current page or namespace.';
+$lang['subscr_m_new_header']        = 'Add subscription';
+$lang['subscr_m_current_header']    = 'Current subscriptions';
+$lang['subscr_m_unsubscribe']       = 'Unsubscribe';
+$lang['subscr_m_subscribe']         = 'Subscribe';
+$lang['subscr_m_receive']           = 'Receive';
+$lang['subscr_style_every']         = 'email on every change';
+$lang['subscr_style_digest']        = 'digest email of changes for each page (every %.2f days)';
+$lang['subscr_style_list']          = 'list of changed pages since last email (every %.2f days)';
 
 /* auth.class language support */
-$lang['authmodfailed']   = 'Bad user authentication configuration. Please inform your Wiki Admin.';
-$lang['authtempfail']    = 'User authentication is temporarily unavailable. If this situation persists, please inform your Wiki Admin.';
+$lang['authmodfailed']         = 'Bad user authentication configuration. Please inform your Wiki Admin.';
+$lang['authtempfail']          = 'User authentication is temporarily unavailable. If this situation persists, please inform your Wiki Admin.';
 
 /* installer strings */
-$lang['i_chooselang'] = 'Choose your language';
-$lang['i_installer']  = 'DokuWiki Installer';
-$lang['i_wikiname']   = 'Wiki Name';
-$lang['i_enableacl']  = 'Enable ACL (recommended)';
-$lang['i_superuser']  = 'Superuser';
-$lang['i_problems']   = 'The installer found some problems, indicated below. You can not continue until you have fixed them.';
-$lang['i_modified']   = 'For security reasons this script will only work with a new and unmodified Dokuwiki installation.
-                         You should either re-extract the files from the downloaded package or consult the complete
-                         <a href="http://dokuwiki.org/install">Dokuwiki installation instructions</a>';
-$lang['i_funcna']     = 'PHP function <code>%s</code> is not available. Maybe your hosting provider disabled it for some reason?';
-$lang['i_phpver']     = 'Your PHP version <code>%s</code> is lower than the needed <code>%s</code>. You need to upgrade your PHP install.';
-$lang['i_permfail']   = '<code>%s</code> is not writable by DokuWiki. You need to fix the permission settings of this directory!';
-$lang['i_confexists'] = '<code>%s</code> already exists';
-$lang['i_writeerr']   = 'Unable to create <code>%s</code>. You will need to check directory/file permissions and create the file manually.';
-$lang['i_badhash']    = 'unrecognised or modified dokuwiki.php (hash=<code>%s</code>)';
-$lang['i_badval']     = '<code>%s</code> - illegal or empty value';
-$lang['i_success']    = 'The configuration was finished successfully. You may delete the install.php file now. Continue to
-                        <a href="doku.php">your new DokuWiki</a>.';
-$lang['i_failure']    = 'Some errors occurred while writing the configuration files. You may need to fix them manually before
-                         you can use <a href="doku.php">your new DokuWiki</a>.';
-$lang['i_policy']     = 'Initial ACL policy';
-$lang['i_pol0']       = 'Open Wiki (read, write, upload for everyone)';
-$lang['i_pol1']       = 'Public Wiki (read for everyone, write and upload for registered users)';
-$lang['i_pol2']       = 'Closed Wiki (read, write, upload for registered users only)';
-
-$lang['i_retry']      = 'Retry';
-$lang['i_license']    = 'Please choose the license you want to put your content under:';
-
-$lang['mu_intro']     = 'Here you can upload multiple files at once. Click the browse button to add them to the queue. Press upload when done.';
-$lang['js']['mu_btn'] = 'Upload multiple files at once';
-$lang['mu_gridname']  = 'Filename';
-$lang['mu_gridsize']  = 'Size';
-$lang['mu_gridstat']  = 'Status';
-$lang['mu_namespace'] = 'Namespace';
-$lang['mu_browse']    = 'Browse';
-$lang['mu_toobig']    = 'too big';
-$lang['mu_ready']     = 'ready for upload';
-$lang['mu_done']      = 'complete';
-$lang['mu_fail']      = 'failed';
-$lang['mu_authfail']  = 'session expired';
-$lang['mu_progress']  = '@PCT@% uploaded';
-$lang['mu_filetypes'] = 'Allowed Filetypes';
-$lang['mu_info']      = 'files uploaded.';
-$lang['mu_lasterr']   = 'Last error:';
-
-$lang['recent_global'] = 'You\'re currently watching the changes inside the <b>%s</b> namespace. You can also <a href="%s">view the recent changes of the whole wiki</a>.';
-
-$lang['years']   = '%d years ago';
-$lang['months']  = '%d months ago';
-$lang['weeks']   = '%d weeks ago';
-$lang['days']    = '%d days ago';
-$lang['hours']   = '%d hours ago';
-$lang['minutes'] = '%d minutes ago';
-$lang['seconds'] = '%d seconds ago';
-
-$lang['wordblock'] = 'Your change was not saved because it contains blocked text (spam).';
-
-
-//Setup VIM: ex: et ts=2 :
+$lang['i_chooselang']          = 'Choose your language';
+$lang['i_installer']           = 'DokuWiki Installer';
+$lang['i_wikiname']            = 'Wiki Name';
+$lang['i_enableacl']           = 'Enable ACL (recommended)';
+$lang['i_superuser']           = 'Superuser';
+$lang['i_problems']            = 'The installer found some problems, indicated below. You can not continue until you have fixed them.';
+$lang['i_modified']            = 'For security reasons this script will only work with a new and unmodified Dokuwiki installation.
+                                  You should either re-extract the files from the downloaded package or consult the complete
+                                  <a href="http://dokuwiki.org/install">Dokuwiki installation instructions</a>';
+$lang['i_funcna']              = 'PHP function <code>%s</code> is not available. Maybe your hosting provider disabled it for some reason?';
+$lang['i_phpver']              = 'Your PHP version <code>%s</code> is lower than the needed <code>%s</code>. You need to upgrade your PHP install.';
+$lang['i_permfail']            = '<code>%s</code> is not writable by DokuWiki. You need to fix the permission settings of this directory!';
+$lang['i_confexists']          = '<code>%s</code> already exists';
+$lang['i_writeerr']            = 'Unable to create <code>%s</code>. You will need to check directory/file permissions and create the file manually.';
+$lang['i_badhash']             = 'unrecognised or modified dokuwiki.php (hash=<code>%s</code>)';
+$lang['i_badval']              = '<code>%s</code> - illegal or empty value';
+$lang['i_success']             = 'The configuration was finished successfully. You may delete the install.php file now. Continue to
+                                 <a href="doku.php">your new DokuWiki</a>.';
+$lang['i_failure']             = 'Some errors occurred while writing the configuration files. You may need to fix them manually before
+                                  you can use <a href="doku.php">your new DokuWiki</a>.';
+$lang['i_policy']              = 'Initial ACL policy';
+$lang['i_pol0']                = 'Open Wiki (read, write, upload for everyone)';
+$lang['i_pol1']                = 'Public Wiki (read for everyone, write and upload for registered users)';
+$lang['i_pol2']                = 'Closed Wiki (read, write, upload for registered users only)';
+$lang['i_retry']               = 'Retry';
+$lang['i_license']             = 'Please choose the license you want to put your content under:';
+
+$lang['mu_intro']              = 'Here you can upload multiple files at once. Click the browse button to add them to the queue. Press upload when done.';
+$lang['mu_gridname']           = 'Filename';
+$lang['mu_gridsize']           = 'Size';
+$lang['mu_gridstat']           = 'Status';
+$lang['mu_namespace']          = 'Namespace';
+$lang['mu_browse']             = 'Browse';
+$lang['mu_toobig']             = 'too big';
+$lang['mu_ready']              = 'ready for upload';
+$lang['mu_done']               = 'complete';
+$lang['mu_fail']               = 'failed';
+$lang['mu_authfail']           = 'session expired';
+$lang['mu_progress']           = '@PCT@% uploaded';
+$lang['mu_filetypes']          = 'Allowed Filetypes';
+$lang['mu_info']               = 'files uploaded.';
+$lang['mu_lasterr']            = 'Last error:';
+
+$lang['recent_global']         = 'You\'re currently watching the changes inside the <b>%s</b> namespace. You can also <a href="%s">view the recent changes of the whole wiki</a>.';
+$lang['years']                 = '%d years ago';
+$lang['months']                = '%d months ago';
+$lang['weeks']                 = '%d weeks ago';
+$lang['days']                  = '%d days ago';
+$lang['hours']                 = '%d hours ago';
+$lang['minutes']               = '%d minutes ago';
+$lang['seconds']               = '%d seconds ago';
+
+$lang['wordblock']             = 'Your change was not saved because it contains blocked text (spam).';
+
+$lang['media_uploadtab']       = 'Upload';
+$lang['media_searchtab']       = 'Search';
+$lang['media_viewtab']         = 'View';
+$lang['media_edittab']         = 'Edit';
+$lang['media_historytab']      = 'History';
+$lang['media_thumbsview']      = 'Thumbnails';
+$lang['media_listview']        = 'List';
+$lang['media_sort']            = 'Sort';
+$lang['media_sort_name']       = 'by name';
+$lang['media_sort_date']       = 'by date';
+$lang['media_upload']          = 'Upload to the <strong>%s</strong> namespace.';
+$lang['media_search']          = 'Search in the <strong>%s</strong> namespace.';
+$lang['media_edit']            = 'Edit';
+$lang['media_history']         = 'These are the older revisions of the file.';
+$lang['media_meta_edited']     = 'metadata edited';
+$lang['media_perm_read']       = 'Sorry, you don\'t have enough rights to read files.';
+$lang['media_perm_upload']     = 'Sorry, you don\'t have enough rights to upload files.';
+$lang['media_update']          = 'Upload new version';
+$lang['media_restore']         = 'Restore this version';
+
+$lang['plugin_install_err']    = "Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.";
+
+//Setup VIM: ex: et ts=2 :
\ No newline at end of file
diff --git a/inc/lang/en/uploadmail.txt b/inc/lang/en/uploadmail.txt
index 6fa196730de481ad9ebe11bb69002b388318a64a..16bb6989cb826ad24818782fec695e1f28e2f925 100644
--- a/inc/lang/en/uploadmail.txt
+++ b/inc/lang/en/uploadmail.txt
@@ -1,6 +1,7 @@
 A file was uploaded to your DokuWiki. Here are the details:
 
 File        : @MEDIA@
+Old revision: @OLD@
 Date        : @DATE@
 Browser     : @BROWSER@
 IP-Address  : @IPADDRESS@
diff --git a/inc/media.php b/inc/media.php
index 731ba16682700bbbbdc088c5b9673db3030aedb1..451a205db15d5c2e9d6c1ec0191ce80c8a5b2dfa 100644
--- a/inc/media.php
+++ b/inc/media.php
@@ -40,6 +40,7 @@ function media_filesinuse($data,$id){
  * Handles the saving of image meta data
  *
  * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Kate Arzamastseva <pshns@ukr.net>
  */
 function media_metasave($id,$auth,$data){
     if($auth < AUTH_UPLOAD) return false;
@@ -60,8 +61,19 @@ function media_metasave($id,$auth,$data){
         }
     }
 
+    $old = @filemtime($src);
+    if(!@file_exists(mediaFN($id, $old)) && @file_exists($src)) {
+        // add old revision to the attic
+        media_saveOldRevision($id);
+    }
+
     if($meta->save()){
         if($conf['fperm']) chmod($src, $conf['fperm']);
+
+        $new = @filemtime($src);
+        // add a log entry to the media changelog
+        addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, $lang['media_meta_edited']);
+
         msg($lang['metasaveok'],1);
         return $id;
     }else{
@@ -74,33 +86,35 @@ function media_metasave($id,$auth,$data){
  * Display the form to edit image meta data
  *
  * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Kate Arzamastseva <pshns@ukr.net>
  */
 function media_metaform($id,$auth){
-    if($auth < AUTH_UPLOAD) return false;
     global $lang, $config_cascade;
 
+    if($auth < AUTH_UPLOAD) {
+        echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.NL;
+        return false;
+    }
+
     // load the field descriptions
     static $fields = null;
     if(is_null($fields)){
-
-        foreach (array('default','local') as $config_group) {
-            if (empty($config_cascade['mediameta'][$config_group])) continue;
-            foreach ($config_cascade['mediameta'][$config_group] as $config_file) {
-                if(@file_exists($config_file)){
-                    include($config_file);
-                }
-            }
+        $config_files = getConfigFiles('mediameta');
+        foreach ($config_files as $config_file) {
+            if(@file_exists($config_file)) include($config_file);
         }
     }
 
     $src = mediaFN($id);
 
     // output
-    echo '<h1>'.hsc(noNS($id)).'</h1>'.NL;
-    echo '<form action="'.DOKU_BASE.'lib/exe/mediamanager.php" accept-charset="utf-8" method="post" class="meta">'.NL;
+    $action = media_managerURL(array('tab_details' => 'view'));
+    echo '<form action="'.$action.'" id="mediamanager__save_meta" accept-charset="utf-8" method="post" class="meta">'.NL;
+
     formSecurityToken();
     foreach($fields as $key => $field){
         // get current value
+        if (empty($field[0])) continue;
         $tags = array($field[0]);
         if(is_array($field[3])) $tags = array_merge($tags,$field[3]);
         $value = tpl_img_getTag($tags,'',$src);
@@ -132,10 +146,11 @@ function media_metaform($id,$auth){
     }
     echo '<div class="buttons">'.NL;
     echo '<input type="hidden" name="img" value="'.hsc($id).'" />'.NL;
-    echo '<input name="do[save]" type="submit" value="'.$lang['btn_save'].
+    echo '<input type="hidden" name="mediado" value="save" />';
+
+    $do = 'mediado';
+    echo '<input name="'.$do.'[save]" type="submit" value="'.$lang['btn_save'].
         '" title="'.$lang['btn_save'].' [S]" accesskey="s" class="button" />'.NL;
-    echo '<input name="do[cancel]" type="submit" value="'.$lang['btn_cancel'].
-        '" title="'.$lang['btn_cancel'].' [C]" accesskey="c" class="button" />'.NL;
     echo '</div>'.NL;
     echo '</form>'.NL;
 }
@@ -178,6 +193,7 @@ define('DOKU_MEDIA_EMPTY_NS', 8);
                        DOKU_MEDIA_INUSE
  */
 function media_delete($id,$auth){
+    global $lang;
     if($auth < AUTH_DELETE) return DOKU_MEDIA_NOT_AUTH;
     if(media_inuse($id)) return DOKU_MEDIA_INUSE;
 
@@ -193,9 +209,15 @@ function media_delete($id,$auth){
     $data['del'] = false;
     $evt = new Doku_Event('MEDIA_DELETE_FILE',$data);
     if ($evt->advise_before()) {
+        $old = @filemtime($file);
+        if(!@file_exists(mediaFN($id, $old)) && @file_exists($file)) {
+            // add old revision to the attic
+            media_saveOldRevision($id);
+        }
+
         $data['unl'] = @unlink($file);
         if($data['unl']){
-            addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_DELETE);
+            addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_DELETE, $lang['deleted']);
             $data['del'] = io_sweepNS($id,'mediadir');
         }
     }
@@ -209,6 +231,45 @@ function media_delete($id,$auth){
     return $data['unl'] ? DOKU_MEDIA_DELETED : 0;
 }
 
+/**
+ * Handle file uploads via XMLHttpRequest
+ *
+ * @return mixed false on error, id of the new file on success
+ */
+function media_upload_xhr($ns,$auth){
+    if(!checkSecurityToken()) return false;
+
+    $id = $_GET['qqfile'];
+    list($ext,$mime,$dl) = mimetype($id);
+    $input = fopen("php://input", "r");
+    $temp = tmpfile();
+    $realSize = stream_copy_to_stream($input, $temp);
+    fclose($input);
+    if ($realSize != (int)$_SERVER["CONTENT_LENGTH"]) return false;
+    if (!($tmp = io_mktmpdir())) return false;
+    $path = $tmp.'/'.md5($id);
+    $target = fopen($path, "w");
+    fseek($temp, 0, SEEK_SET);
+    stream_copy_to_stream($temp, $target);
+    fclose($target);
+    $res = media_save(
+        array('name' => $path,
+            'mime' => $mime,
+            'ext'  => $ext),
+        $ns.':'.$id,
+        (($_REQUEST['ow'] == 'checked') ? true : false),
+        $auth,
+        'copy'
+    );
+    unlink($path);
+    if ($tmp) dir_delete($tmp);
+    if (is_array($res)) {
+        msg($res[0], $res[1]);
+        return false;
+    }
+    return $res;
+}
+
 /**
  * Handles media file uploads
  *
@@ -216,13 +277,13 @@ function media_delete($id,$auth){
  * @author Michael Klier <chi@chimeric.de>
  * @return mixed false on error, id of the new file on success
  */
-function media_upload($ns,$auth){
+function media_upload($ns,$auth,$file=false){
     if(!checkSecurityToken()) return false;
     global $lang;
 
     // get file and id
-    $id   = $_POST['id'];
-    $file = $_FILES['upload'];
+    $id   = $_POST['mediaid'];
+    if (!$file) $file = $_FILES['upload'];
     if(empty($id)) $id = $file['name'];
 
     // check for errors (messages are done in lib/exe/mediamanager.php)
@@ -280,7 +341,7 @@ function media_save($file, $id, $ow, $auth, $move) {
         }
     }
 
-    global $lang;
+    global $lang, $conf;
 
     // get filename
     $id   = cleanID($id,false,true);
@@ -298,7 +359,8 @@ function media_save($file, $id, $ow, $auth, $move) {
 
     //check for overwrite
     $overwrite = @file_exists($fn);
-    if($overwrite && (!$ow || $auth < AUTH_DELETE)) {
+    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
+    if($overwrite && (!$ow || $auth < $auth_ow)) {
         return array($lang['uploadexist'], 0);
     }
     // check for valid content
@@ -341,26 +403,38 @@ function _media_upload_action($data) {
  *
  * @author Andreas Gohr <andi@splitbrain.org>
  * @author Michael Klier <chi@chimeric.de>
+ * @author Kate Arzamastseva <pshns@ukr.net>
  */
 function media_upload_finish($fn_tmp, $fn, $id, $imime, $overwrite, $move = 'move_uploaded_file') {
     global $conf;
     global $lang;
+    global $REV;
+
+    $old = @filemtime($fn);
+    if(!@file_exists(mediaFN($id, $old)) && @file_exists($fn)) {
+        // add old revision to the attic if missing
+        media_saveOldRevision($id);
+    }
 
     // prepare directory
     io_createNamespace($id, 'media');
 
     if($move($fn_tmp, $fn)) {
+        @clearstatcache(true,$fn);
+        $new = @filemtime($fn);
         // Set the correct permission here.
         // Always chmod media because they may be saved with different permissions than expected from the php umask.
         // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.)
         chmod($fn, $conf['fmode']);
         msg($lang['uploadsucc'],1);
-        media_notify($id,$fn,$imime);
+        media_notify($id,$fn,$imime,$old);
         // add a log entry to the media changelog
-        if ($overwrite) {
-            addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_EDIT);
+        if ($REV){
+            addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_REVERT, $lang['restored'], $REV);
+        } elseif ($overwrite) {
+            addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT);
         } else {
-            addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_CREATE);
+            addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created']);
         }
         return $id;
     }else{
@@ -368,6 +442,43 @@ function media_upload_finish($fn_tmp, $fn, $id, $imime, $overwrite, $move = 'mov
     }
 }
 
+/**
+ * Moves the current version of media file to the media_attic
+ * directory
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ * @param string $id
+ * @return int - revision date
+ */
+function media_saveOldRevision($id){
+    global $conf, $lang;
+
+    $oldf = mediaFN($id);
+    if(!@file_exists($oldf)) return '';
+    $date = filemtime($oldf);
+    if (!$conf['mediarevisions']) return $date;
+
+    if (!getRevisionInfo($id, $date, 8192, true)) {
+        // there was an external edit,
+        // there is no log entry for current version of file
+        if (!@file_exists(mediaMetaFN($id,'.changes'))) {
+            addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_CREATE, $lang['created']);
+        } else {
+            addMediaLogEntry($date, $id, DOKU_CHANGE_TYPE_EDIT);
+        }
+    }
+
+    $newf = mediaFN($id,$date);
+    io_makeFileDir($newf);
+    if(copy($oldf, $newf)) {
+        // Set the correct permission here.
+        // Always chmod media because they may be saved with different permissions than expected from the php umask.
+        // (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.)
+        chmod($newf, $conf['fmode']);
+    }
+    return $date;
+}
+
 /**
  * This function checks if the uploaded content is really what the
  * mimetype says it is. We also do spam checking for text types here.
@@ -416,7 +527,7 @@ function media_contentcheck($file,$mime){
  *
  * @author Andreas Gohr <andi@splitbrain.org>
  */
-function media_notify($id,$file,$mime){
+function media_notify($id,$file,$mime,$old_rev=false){
     global $lang;
     global $conf;
     global $INFO;
@@ -434,6 +545,11 @@ function media_notify($id,$file,$mime){
     $text = str_replace('@MIME@',$mime,$text);
     $text = str_replace('@MEDIA@',ml($id,'',true,'&',true),$text);
     $text = str_replace('@SIZE@',filesize_h(filesize($file)),$text);
+    if ($old_rev && $conf['mediarevisions']) {
+        $text = str_replace('@OLD@', ml($id, "rev=$old_rev", true, '&', true), $text);
+    } else {
+        $text = str_replace('@OLD@', '', $text);
+    }
 
     if(empty($conf['mailprefix'])) {
         $subject = '['.$conf['title'].'] '.$lang['mail_upload'].' '.$id;
@@ -447,7 +563,7 @@ function media_notify($id,$file,$mime){
 /**
  * List all files in a given Media namespace
  */
-function media_filelist($ns,$auth=null,$jump=''){
+function media_filelist($ns,$auth=null,$jump='',$fullscreenview=false,$sort=false){
     global $conf;
     global $lang;
     $ns = cleanID($ns);
@@ -455,26 +571,727 @@ function media_filelist($ns,$auth=null,$jump=''){
     // check auth our self if not given (needed for ajax calls)
     if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
 
-    echo '<h1 id="media__ns">:'.hsc($ns).'</h1>'.NL;
+    if (!$fullscreenview) echo '<h1 id="media__ns">:'.hsc($ns).'</h1>'.NL;
 
     if($auth < AUTH_READ){
         // FIXME: print permission warning here instead?
         echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
     }else{
-        media_uploadform($ns, $auth);
+        if (!$fullscreenview) media_uploadform($ns, $auth);
 
         $dir = utf8_encodeFN(str_replace(':','/',$ns));
         $data = array();
         search($data,$conf['mediadir'],'search_media',
-                array('showmsg'=>true,'depth'=>1),$dir);
+                array('showmsg'=>true,'depth'=>1),$dir,1,$sort);
 
         if(!count($data)){
             echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
-        }else foreach($data as $item){
-            media_printfile($item,$auth,$jump);
+        }else {
+            if ($fullscreenview) {
+                $view = $_REQUEST['view'];
+                if ($view == 'list') {
+                    echo '<ul class="mediamanager-list" id="mediamanager__file_list">';
+                } else {
+                    echo '<ul class="mediamanager-thumbs" id="mediamanager__file_list">';
+                }
+            }
+            foreach($data as $item){
+                if (!$fullscreenview) {
+                    media_printfile($item,$auth,$jump);
+                } else {
+                    media_printfile_thumbs($item,$auth,$jump);
+                }
+            }
+            if ($fullscreenview) echo '</ul>';
+        }
+    }
+    if (!$fullscreenview) media_searchform($ns);
+}
+
+/**
+ * Prints mediamanager tab
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ * @param string $link - tab href
+ * @param string $class - tab css class
+ * @param string $name - tab caption
+ * @param boolean $selected - is tab selected
+ */
+function media_tab($link, $class, $name, $selected=false) {
+    if ($selected) $class .= ' selected';
+    $tab = '<a href="'.$link.'" class="'.$class.'" >'.$name.'</a>';
+    echo $tab;
+}
+
+/**
+ * Prints tabs for files list actions
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ * @param string $selected_tab - opened tab
+ */
+function media_tabs_files($selected_tab = ''){
+    global $lang;
+
+    echo '<div class="mediamanager-tabs" id="mediamanager__tabs_files">';
+
+    media_tab(media_managerURL(array('tab_files' => 'files')),
+        'files', $lang['mediaselect'], ($selected_tab == 'files'));
+    media_tab(media_managerURL(array('tab_files' => 'upload')),
+        'upload', $lang['media_uploadtab'], ($selected_tab == 'upload'));
+    media_tab(media_managerURL(array('tab_files' => 'search')),
+        'search', $lang['media_searchtab'], ($selected_tab == 'search'));
+
+    echo '<div class="clearer"></div>';
+    echo '</div>';
+}
+
+/**
+ * Prints tabs for files details actions
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ * @param string $selected_tab - opened tab
+ */
+function media_tabs_details($image, $selected_tab = ''){
+    global $lang, $conf;
+
+    echo '<div class="mediamanager-tabs" id="mediamanager__tabs_details">';
+
+    media_tab(media_managerURL(array('tab_details' => 'view')),
+        'view', $lang['media_viewtab'], ($selected_tab == 'view'));
+
+    list($ext, $mime) = mimetype($image);
+    if ($mime == 'image/jpeg' && @file_exists(mediaFN($image))) {
+        media_tab(media_managerURL(array('tab_details' => 'edit')),
+            'edit', $lang['media_edittab'], ($selected_tab == 'edit'));
+    }
+    if ($conf['mediarevisions']) {
+        media_tab(media_managerURL(array('tab_details' => 'history')),
+            'history', $lang['media_historytab'], ($selected_tab == 'history'));
+    }
+
+    echo '<div class="clearer"></div>';
+    echo '</div>';
+}
+
+/**
+ * Prints options for the tab that displays a list of all files
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_tab_files_options($ns, $sort){
+    global $lang;
+
+    echo '<div class="background-container">';
+
+    echo '<strong class="namespace">';
+    echo $ns ? $ns : '['.$lang['mediaroot'].']';
+    echo '</strong>';
+
+    echo '<div id="mediamanager__tabs_list">';
+
+    echo '<a href="'.media_managerURL(array('view' => 'thumbs')).'" id="mediamanager__link_thumbs" >';
+    echo $lang['media_thumbsview'];
+    echo '</a>';
+
+    echo '<a href="'.media_managerURL(array('view' => 'list')).'" id="mediamanager__link_list" >';
+    echo $lang['media_listview'];
+    echo '</a>';
+
+    echo '</div>';
+
+    echo '<div id="mediamanager__sort">';
+    $form = new Doku_Form(array('action'=>media_managerURL(array(), '&'), 'id' => 'mediamanager__form_sort'));
+    $form->addElement(form_makeListboxField(
+                        'sort',
+                        array(
+                            'name' => $lang['media_sort_name'],
+                            'date' => $lang['media_sort_date']),
+                        $sort,
+                        $lang['media_sort']));
+    $form->addElement(form_makeButton('submit', '', $lang['btn_apply']));
+    $form->printForm();
+    echo '</div>';
+
+    echo '<div class="clearer"></div>';
+    echo '</div>';
+}
+
+/**
+ * Returns type of sorting for the list of files in media manager
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ * @return string - sort type
+ */
+function _media_get_sort_type() {
+    $sort = $_REQUEST['sort'];
+    if (!$sort && (strpos($_COOKIE['DOKU_PREFS'], 'sort') >= 0)) {
+        $parts = explode('#', $_COOKIE['DOKU_PREFS']);
+            for ($i = 0; $i < count($parts); $i+=2){
+                if ($parts[$i] == 'sort') $sort = $parts[$i+1];
+            }
+    }
+    return $sort;
+}
+
+/**
+ * Prints tab that displays a list of all files
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_tab_files($ns,$auth=null,$jump='') {
+    global $lang;
+    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
+
+    $sort = _media_get_sort_type();
+    media_tab_files_options($ns, $sort);
+
+    echo '<div class="scroll-container" >';
+    if($auth < AUTH_READ){
+        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
+    }else{
+        media_filelist($ns,$auth,$jump,true,$sort);
+    }
+    echo '</div>';
+}
+
+/**
+ * Prints tab that displays uploading form
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_tab_upload($ns,$auth=null,$jump='') {
+    global $lang;
+    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
+
+    echo '<div class="background-container">';
+    echo sprintf($lang['media_upload'], $ns ? $ns : '['.$lang['mediaroot'].']');
+    echo '</div>';
+
+    echo '<div class="scroll-container">';
+    if ($auth >= AUTH_UPLOAD) echo '<div class="upload">' . $lang['mediaupload'] . '</div>';
+    media_uploadform($ns, $auth, true);
+    echo '</div>';
+}
+
+/**
+ * Prints tab that displays search form
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_tab_search($ns,$auth=null) {
+    global $lang;
+
+    $do = $_REQUEST['mediado'];
+    $query = $_REQUEST['q'];
+    if (!$query) $query = '';
+
+    $sort = _media_get_sort_type();
+    media_tab_files_options($ns, $sort);
+
+    echo '<div class="scroll-container">';
+    media_searchform($ns, $query, true);
+    if ($do == 'searchlist') media_searchlist($query,$ns,$auth,true,$sort);
+    echo '</div>';
+}
+
+/**
+ * Prints tab that displays mediafile details
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_tab_view($image, $ns, $auth=null, $rev=false) {
+    global $lang, $conf;
+    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
+
+    echo '<div class="background-container">';
+    list($ext,$mime,$dl) = mimetype($image,false);
+    $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
+    $class = 'select mediafile mf_'.$class;
+    echo '<span class="'.$class.'" >'.$image.'</span>';
+    echo '</div>';
+
+    echo '<div class="scroll-container">';
+    if ($image && $auth >= AUTH_READ) {
+        $meta = new JpegMeta(mediaFN($image, $rev));
+        media_preview($image, $auth, $rev, $meta);
+        media_preview_buttons($image, $auth, $rev);
+        media_details($image, $auth, $rev, $meta);
+
+    } else {
+        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>';
+    }
+    echo '</div>';
+}
+
+/**
+ * Prints tab that displays form for editing mediafile metadata
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_tab_edit($image, $ns, $auth=null) {
+    global $lang;
+    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
+
+    echo '<div class="background-container">';
+    echo $lang['media_edit'];
+    echo '</div>';
+
+    echo '<div class="scroll-container">';
+    if ($image) {
+        list($ext, $mime) = mimetype($image);
+        if ($mime == 'image/jpeg') media_metaform($image,$auth);
+    }
+    echo '</div>';
+}
+
+/**
+ * Prints tab that displays mediafile revisions
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_tab_history($image, $ns, $auth=null) {
+    global $lang;
+    if(is_null($auth)) $auth = auth_quickaclcheck("$ns:*");
+    $do = $_REQUEST['mediado'];
+
+    echo '<div class="background-container">';
+    echo $lang['media_history'];
+    echo '</div>';
+
+    echo '<div class="scroll-container">';
+    if ($auth >= AUTH_READ && $image) {
+        if ($do == 'diff'){
+            media_diff($image, $ns, $auth);
+        } else {
+            $first = isset($_REQUEST['first']) ? intval($_REQUEST['first']) : 0;
+            html_revisions($first, $image);
+        }
+    } else {
+        echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
+    }
+    echo '</div>';
+}
+
+/**
+ * Prints mediafile details
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_preview($image, $auth, $rev=false, $meta=false) {
+    global $lang;
+
+    echo '<div class="mediamanager__preview">';
+
+    $size = media_image_preview_size($image, $rev, $meta);
+
+    if ($size) {
+        $more = array();
+        if ($rev) {
+            $more['rev'] = $rev;
+        } else {
+            $t = @filemtime(mediaFN($image));
+            $more['t'] = $t;
+        }
+
+        $more['w'] = $size[0];
+        $more['h'] = $size[1];
+        $src = ml($image, $more);
+        echo '<img src="'.$src.'" alt="'.$image.'" style="max-width: '.$size[0].'px;" />';
+    }
+
+    echo '</div>';
+}
+
+/**
+ * Prints mediafile action buttons
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_preview_buttons($image, $auth, $rev=false) {
+    global $lang, $conf;
+
+    echo '<div class="mediamanager__preview_buttons">';
+
+    $more = '';
+    if ($rev) {
+        $more = "rev=$rev";
+    } else {
+        $t = @filemtime(mediaFN($image));
+        $more = "t=$t";
+    }
+    $link = ml($image,$more,true,'&');
+
+    if (@file_exists(mediaFN($image, $rev))) {
+
+        // view original file button
+        $form = new Doku_Form(array('action'=>$link, 'target'=>'_blank'));
+        $form->addElement(form_makeButton('submit','',$lang['mediaview']));
+        $form->printForm();
+    }
+
+    if($auth >= AUTH_DELETE && !$rev && @file_exists(mediaFN($image))){
+
+        // delete button
+        $form = new Doku_Form(array('id' => 'mediamanager__btn_delete',
+            'action'=>media_managerURL(array('delete' => $image), '&')));
+        $form->addElement(form_makeButton('submit','',$lang['btn_delete']));
+        $form->printForm();
+
+    }
+
+    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
+    if($auth >= $auth_ow && !$rev){
+
+        // upload new version button
+        $form = new Doku_Form(array('id' => 'mediamanager__btn_update',
+            'action'=>media_managerURL(array('image' => $image, 'mediado' => 'update'), '&')));
+        $form->addElement(form_makeButton('submit','',$lang['media_update']));
+        $form->printForm();
+    }
+
+    if($auth >= AUTH_UPLOAD && $rev && $conf['mediarevisions'] && @file_exists(mediaFN($image, $rev))){
+
+        // restore button
+        $form = new Doku_Form(array('id' => 'mediamanager__btn_restore',
+            'action'=>media_managerURL(array('image' => $image), '&')));
+        $form->addHidden('mediado','restore');
+        $form->addHidden('rev',$rev);
+        $form->addElement(form_makeButton('submit','',$lang['media_restore']));
+        $form->printForm();
+    }
+
+    echo '</div>';
+}
+
+/**
+ * Returns image width and height for mediamanager preview panel
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ * @param string $image
+ * @param int $rev
+ * @param JpegMeta $meta
+ * @return array
+ */
+function media_image_preview_size($image, $rev, $meta, $size = 500) {
+    if (!preg_match("/\.(jpe?g|gif|png)$/", $image) || !file_exists(mediaFN($image, $rev))) return false;
+
+    $info = getimagesize(mediaFN($image, $rev));
+    $w = (int) $info[0];
+    $h = (int) $info[1];
+
+    if($meta && ($w > $size || $h > $size)){
+        $ratio = $meta->getResizeRatio($size, $size);
+        $w = floor($w * $ratio);
+        $h = floor($h * $ratio);
+    }
+    return array($w, $h);
+}
+
+/**
+ * Returns the requested EXIF/IPTC tag from the image meta
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ * @param array $tags
+ * @param JpegMeta $meta
+ * @param string $alt
+ * @return string
+ */
+function media_getTag($tags,$meta,$alt=''){
+    if($meta === false) return $alt;
+    $info = $meta->getField($tags);
+    if($info == false) return $alt;
+    return $info;
+}
+
+/**
+ * Returns mediafile tags
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ * @param JpegMeta $meta
+ * @return array
+ */
+function media_file_tags($meta) {
+    global $config_cascade;
+
+    // load the field descriptions
+    static $fields = null;
+    if(is_null($fields)){
+        $config_files = getConfigFiles('mediameta');
+        foreach ($config_files as $config_file) {
+            if(@file_exists($config_file)) include($config_file);
+        }
+    }
+
+    $tags = array();
+
+    foreach($fields as $key => $tag){
+        $t = array();
+        if (!empty($tag[0])) $t = array($tag[0]);
+        if(is_array($tag[3])) $t = array_merge($t,$tag[3]);
+        $value = media_getTag($t, $meta);
+        $tags[] = array('tag' => $tag, 'value' => $value);
+    }
+
+    return $tags;
+}
+
+/**
+ * Prints mediafile tags
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_details($image, $auth, $rev=false, $meta=false) {
+    global $lang;
+
+    if (!$meta) $meta = new JpegMeta(mediaFN($image, $rev));
+    $tags = media_file_tags($meta);
+
+    echo '<dl class="img_tags">';
+    foreach($tags as $tag){
+        if ($tag['value']) {
+            $value = cleanText($tag['value']);
+            echo '<dt>'.$lang[$tag['tag'][1]].':</dt><dd>';
+            if ($tag['tag'][2] == 'date') echo dformat($value);
+            else echo hsc($value);
+            echo '</dd>';
         }
     }
-    media_searchform($ns);
+    echo '</dl>';
+}
+
+/**
+ * Shows difference between two revisions of file
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_diff($image, $ns, $auth, $fromajax = false) {
+    global $lang;
+    global $conf;
+
+    if ($auth < AUTH_READ || !$image || !$conf['mediarevisions']) return '';
+
+    $rev1 = (int) $_REQUEST['rev'];
+
+    if(is_array($_REQUEST['rev2'])){
+        $rev1 = (int) $_REQUEST['rev2'][0];
+        $rev2 = (int) $_REQUEST['rev2'][1];
+
+        if(!$rev1){
+            $rev1 = $rev2;
+            unset($rev2);
+        }
+    }else{
+        $rev2 = (int) $_REQUEST['rev2'];
+    }
+
+    if ($rev1 && !file_exists(mediaFN($image, $rev1))) $rev1 = false;
+    if ($rev2 && !file_exists(mediaFN($image, $rev2))) $rev2 = false;
+
+    if($rev1 && $rev2){            // two specific revisions wanted
+        // make sure order is correct (older on the left)
+        if($rev1 < $rev2){
+            $l_rev = $rev1;
+            $r_rev = $rev2;
+        }else{
+            $l_rev = $rev2;
+            $r_rev = $rev1;
+        }
+    }elseif($rev1){                // single revision given, compare to current
+        $r_rev = '';
+        $l_rev = $rev1;
+    }else{                        // no revision was given, compare previous to current
+        $r_rev = '';
+        $revs = getRevisions($image, 0, 1, 8192, true);
+        if (file_exists(mediaFN($image, $revs[0]))) {
+            $l_rev = $revs[0];
+        } else {
+            $l_rev = '';
+        }
+    }
+
+    // prepare event data
+    $data[0] = $image;
+    $data[1] = $l_rev;
+    $data[2] = $r_rev;
+    $data[3] = $ns;
+    $data[4] = $auth;
+    $data[5] = $fromajax;
+
+    // trigger event
+    return trigger_event('MEDIA_DIFF', $data, '_media_file_diff', true);
+
+}
+
+function _media_file_diff($data) {
+    if(is_array($data) && count($data)===6) {
+        return media_file_diff($data[0], $data[1], $data[2], $data[3], $data[4], $data[5]);
+    } else {
+        return false;
+    }
+}
+
+/**
+ * Shows difference between two revisions of image
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_file_diff($image, $l_rev, $r_rev, $ns, $auth, $fromajax){
+    global $lang, $config_cascade;
+
+    $l_meta = new JpegMeta(mediaFN($image, $l_rev));
+    $r_meta = new JpegMeta(mediaFN($image, $r_rev));
+
+    $is_img = preg_match("/\.(jpe?g|gif|png)$/", $image);
+    if ($is_img) {
+        $l_size = media_image_preview_size($image, $l_rev, $l_meta);
+        $r_size = media_image_preview_size($image, $r_rev, $r_meta);
+        $is_img = ($l_size && $r_size && ($l_size[0] >= 30 || $r_size[0] >= 30));
+
+        $difftype = $_REQUEST['difftype'];
+
+        if (!$fromajax) {
+            $form = new Doku_Form(array('action'=>media_managerURL(array(), '&'),
+                'id' => 'mediamanager__form_diffview'));
+            $form->addElement('<input type="hidden" name="rev2[]" value="'.$l_rev.'" ></input>');
+            $form->addElement('<input type="hidden" name="rev2[]" value="'.$r_rev.'" ></input>');
+            $form->addHidden('mediado', 'diff');
+            $form->printForm();
+
+            echo '<div id="mediamanager__diff" >';
+        }
+
+        if ($difftype == 'opacity' || $difftype == 'portions') {
+            media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $difftype);
+            if (!$fromajax) echo '</div>';
+            return '';
+        }
+    }
+
+    echo '<div class="mediamanager-preview">';
+    echo '<ul id="mediamanager__diff_table">';
+
+    echo '<li>';
+    media_preview($image, $auth, $l_rev, $l_meta);
+    echo '</li>';
+
+    echo '<li>';
+    media_preview($image, $auth, $r_rev, $r_meta);
+    echo '</li>';
+
+    echo '<li>';
+    media_preview_buttons($image, $auth, $l_rev);
+    echo '</li>';
+
+    echo '<li>';
+    media_preview_buttons($image, $auth, $r_rev);
+    echo '</li>';
+
+    $l_tags = media_file_tags($l_meta);
+    $r_tags = media_file_tags($r_meta);
+    foreach ($l_tags as $key => $l_tag) {
+        if ($l_tag['value'] != $r_tags[$key]['value']) {
+            $r_tags[$key]['class'] = 'highlighted';
+            $l_tags[$key]['class'] = 'highlighted';
+        } else if (!$l_tag['value'] || !$r_tags[$key]['value']) {
+            unset($r_tags[$key]);
+            unset($l_tags[$key]);
+        }
+    }
+
+    foreach(array($l_tags,$r_tags) as $tags){
+        echo '<li><div>';
+
+        echo '<dl class="img_tags">';
+        foreach($tags as $tag){
+            $value = cleanText($tag['value']);
+            if (!$value) $value = '-';
+            echo '<dt>'.$lang[$tag['tag'][1]].':</dt>';
+            echo '<dd class="'.$tag['class'].'" >';
+            if ($tag['tag'][2] == 'date') echo dformat($value);
+            else echo hsc($value);
+            echo '</dd>';
+        }
+        echo '</dl>';
+
+        echo '</div></li>';
+    }
+
+    echo '</ul>';
+    echo '</div>';
+
+    if ($is_img && !$fromajax) echo '</div>';
+}
+
+/**
+ * Prints two images side by side
+ * and slider
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ * @param string $image
+ * @param int $l_rev
+ * @param int $r_rev
+ * @param array $l_size
+ * @param array $r_size
+ * @param string $type
+ */
+function media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $type) {
+    if ($l_size != $r_size) {
+        if ($r_size[0] > $l_size[0]) {
+            $l_size = $r_size;
+        }
+    }
+
+    echo '<div class="mediamanager-preview">';
+
+    $l_more = array('rev' => $l_rev, 'h' => $l_size[1], 'w' => $l_size[0]);
+    $r_more = array('rev' => $r_rev, 'h' => $l_size[1], 'w' => $l_size[0]);
+
+    $l_src = ml($image, $l_more);
+    $r_src = ml($image, $r_more);
+
+    // slider
+    echo '<div id="mediamanager__'.$type.'_slider" style="max-width: '.($l_size[0]-20).'px;" ></div>';
+
+    // two image's in div's
+    echo '<div id="mediamanager__diff_layout">';
+    echo '<div id="mediamanager__diff_'.$type.'_image1" style="max-width: '.$l_size[0].'px;">';
+    echo '<img src="'.$l_src.'" alt="" />';
+    echo '</div>';
+    echo '<div id="mediamanager__diff_'.$type.'_image2" style="max-width: '.$l_size[0].'px;">';
+    echo '<img src="'.$r_src.'" alt="" />';
+    echo '</div>';
+    echo '</div>';
+
+    echo '</div>';
+}
+
+/**
+ * Restores an old revision of a media file
+ *
+ * @param string $image
+ * @param int $rev
+ * @param int $auth
+ * @return string - file's id
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_restore($image, $rev, $auth){
+    global $conf;
+    if ($auth < AUTH_UPLOAD || !$conf['mediarevisions']) return false;
+    $removed = (!file_exists(mediaFN($image)) && file_exists(mediaMetaFN($image, '.changes')));
+    if (!$image || (!file_exists(mediaFN($image)) && !$removed)) return false;
+    if (!$rev || !file_exists(mediaFN($image, $rev))) return false;
+    list($iext,$imime,$dl) = mimetype($image);
+    $res = media_upload_finish(mediaFN($image, $rev),
+        mediaFN($image),
+        $image,
+        $imime,
+        true,
+        'copy');
+    if (is_array($res)) {
+        msg($res[0], $res[1]);
+        return false;
+    }
+    return $res;
 }
 
 /**
@@ -482,11 +1299,13 @@ function media_filelist($ns,$auth=null,$jump=''){
  *
  * @author Tobias Sarnowski <sarnowski@cosmocode.de>
  * @author Andreas Gohr <gohr@cosmocode.de>
+ * @author Kate Arzamastseva <pshns@ukr.net>
  * @triggers MEDIA_SEARCH
  */
-function media_searchlist($query,$ns,$auth=null){
+function media_searchlist($query,$ns,$auth=null,$fullscreen=false,$sort=''){
     global $conf;
     global $lang;
+
     $ns = cleanID($ns);
 
     if ($query) {
@@ -505,50 +1324,39 @@ function media_searchlist($query,$ns,$auth=null){
                     array('showmsg'=>false,'pattern'=>$pattern),
                     $dir);
         }
+
+        $data = array();
+        foreach ($evdata['data'] as $k => $v) {
+            $data[$k] = ($sort == 'date') ? $v['mtime'] : $v['id'];
+        }
+        array_multisort($data, SORT_DESC, SORT_NUMERIC, $evdata['data']);
+
         $evt->advise_after();
         unset($evt);
     }
 
-    echo '<h1 id="media__ns">'.sprintf($lang['searchmedia_in'],hsc($ns).':*').'</h1>'.NL;
-    media_searchform($ns,$query);
+    if (!$fullscreen) {
+        echo '<h1 id="media__ns">'.sprintf($lang['searchmedia_in'],hsc($ns).':*').'</h1>'.NL;
+        media_searchform($ns,$query);
+    }
 
     if(!count($evdata['data'])){
         echo '<div class="nothing">'.$lang['nothingfound'].'</div>'.NL;
-    }else foreach($evdata['data'] as $item){
-        media_printfile($item,$item['perm'],'',true);
-    }
-}
-
-/**
- * Print action links for a file depending on filetype
- * and available permissions
- */
-function media_fileactions($item,$auth){
-    global $lang;
-
-    // view button
-    $link = ml($item['id'],'',true);
-    echo ' <a href="'.$link.'" target="_blank"><img src="'.DOKU_BASE.'lib/images/magnifier.png" '.
-        'alt="'.$lang['mediaview'].'" title="'.$lang['mediaview'].'" class="btn" /></a>';
-
-    // no further actions if not writable
-    if(!$item['writable']) return;
-
-    // delete button
-    if($auth >= AUTH_DELETE){
-        echo ' <a href="'.DOKU_BASE.'lib/exe/mediamanager.php?delete='.rawurlencode($item['id']).
-            '&amp;sectok='.getSecurityToken().'" class="btn_media_delete" title="'.$item['id'].'">'.
-            '<img src="'.DOKU_BASE.'lib/images/trash.png" alt="'.$lang['btn_delete'].'" '.
-            'title="'.$lang['btn_delete'].'" class="btn" /></a>';
-    }
-
-    // edit button
-    if($auth >= AUTH_UPLOAD && $item['isimg'] && $item['meta']->getField('File.Mime') == 'image/jpeg'){
-        echo ' <a href="'.DOKU_BASE.'lib/exe/mediamanager.php?edit='.rawurlencode($item['id']).'">'.
-            '<img src="'.DOKU_BASE.'lib/images/pencil.png" alt="'.$lang['metaedit'].'" '.
-            'title="'.$lang['metaedit'].'" class="btn" /></a>';
+    }else {
+        if ($fullscreen) {
+            $view = $_REQUEST['view'];
+            if ($view == 'list') {
+                echo '<ul class="mediamanager-list" id="mediamanager__file_list">';
+            } else {
+                echo '<ul class="mediamanager-thumbs" id="mediamanager__file_list">';
+            }
+        }
+        foreach($evdata['data'] as $item){
+            if (!$fullscreen) media_printfile($item,$item['perm'],'',true);
+            else media_printfile_thumbs($item,$item['perm'],false,true);
+        }
+        if ($fullscreen) echo '</ul>';
     }
-
 }
 
 /**
@@ -599,7 +1407,12 @@ function media_printfile($item,$auth,$jump,$display_namespace=false){
         echo '<a name="h_:'.$item['id'].'" class="'.$class.'">'.hsc($item['id']).'</a><br/>';
     }
     echo '<span class="info">('.$info.')</span>'.NL;
-    media_fileactions($item,$auth);
+
+    // view button
+    $link = ml($item['id'],'',true);
+    echo ' <a href="'.$link.'" target="_blank"><img src="'.DOKU_BASE.'lib/images/magnifier.png" '.
+        'alt="'.$lang['mediaview'].'" title="'.$lang['mediaview'].'" class="btn" /></a>';
+
     echo '<div class="example" id="ex_'.str_replace(':','_',$item['id']).'">';
     echo $lang['mediausage'].' <code>{{:'.$item['id'].'}}</code>';
     echo '</div>';
@@ -608,27 +1421,113 @@ function media_printfile($item,$auth,$jump,$display_namespace=false){
     echo '</div>'.NL;
 }
 
+function media_printicon($filename){
+    list($ext,$mime,$dl) = mimetype(mediaFN($filename),false);
+
+    if (@file_exists(DOKU_INC.'lib/images/fileicons/'.$ext.'.png')) {
+        $icon = DOKU_BASE.'lib/images/fileicons/'.$ext.'.png';
+    } else {
+        $icon = DOKU_BASE.'lib/images/fileicons/file.png';
+    }
+
+    return '<img src="'.$icon.'" alt="'.$filename.'" class="icon" />';
+
+}
+
+/**
+ * Formats and prints one file in the list in the thumbnails view
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function media_printfile_thumbs($item,$auth,$jump=false,$display_namespace=false){
+    global $lang;
+    global $conf;
+
+    // Prepare filename
+    $file = utf8_decodeFN($item['file']);
+
+    // output
+    echo '<li><div>';
+
+    if($item['isimg']) {
+        media_printimgdetail($item, true);
+
+    } else {
+        echo '<a name="d_:'.$item['id'].'" class="image" title="'.$item['id'].'" href="'.
+            media_managerURL(array('image' => hsc($item['id']), 'ns' => getNS($item['id']),
+            'tab_details' => 'view')).'"><span>';
+        echo media_printicon($item['id']);
+        echo '</span></a>';
+    }
+    //echo '<input type=checkbox />';
+    if (!$display_namespace) {
+        $name = hsc($file);
+    } else {
+        $name = hsc($item['id']);
+    }
+    echo '<a href="'.media_managerURL(array('image' => hsc($item['id']), 'ns' => getNS($item['id']),
+        'tab_details' => 'view')).'" name="h_:'.$item['id'].'" class="name">'.$name.'</a>';
+
+    if($item['isimg']){
+        $size = '';
+        $size .= (int) $item['meta']->getField('File.Width');
+        $size .= '&#215;';
+        $size .= (int) $item['meta']->getField('File.Height');
+        echo '<span class="size">'.$size.'</span>';
+    } else {
+        echo '<span class="size">&nbsp;</span>';
+    }
+    $date = dformat($item['mtime']);
+    echo '<span class="date">'.$date.'</span>';
+    $filesize = filesize_h($item['size']);
+    echo '<span class="filesize">'.$filesize.'</span>';
+    echo '<div class="clearer"></div>';
+    echo '</div></li>'.NL;
+}
+
 /**
  * Prints a thumbnail and metainfos
  */
-function media_printimgdetail($item){
+function media_printimgdetail($item, $fullscreen=false){
     // prepare thumbnail
-    $w = (int) $item['meta']->getField('File.Width');
-    $h = (int) $item['meta']->getField('File.Height');
-    if($w>120 || $h>120){
-        $ratio = $item['meta']->getResizeRatio(120);
-        $w = floor($w * $ratio);
-        $h = floor($h * $ratio);
+    if (!$fullscreen) {
+        $size_array[] = 120;
+    } else {
+        $size_array = array(90, 40);
+    }
+    foreach ($size_array as $index => $size) {
+        $w = (int) $item['meta']->getField('File.Width');
+        $h = (int) $item['meta']->getField('File.Height');
+        if($w>$size || $h>$size){
+            if (!$fullscreen) {
+                $ratio = $item['meta']->getResizeRatio($size);
+            } else {
+                $ratio = $item['meta']->getResizeRatio($size,$size);
+            }
+            $w = floor($w * $ratio);
+            $h = floor($h * $ratio);
+        }
+        $src = ml($item['id'],array('w'=>$w,'h'=>$h,'t'=>$item['mtime']));
+        $p = array();
+        if (!$fullscreen) {
+            $p['width']  = $w;
+            $p['height'] = $h;
+        }
+        $p['alt']    = $item['id'];
+        $p['class']  = 'thumb';
+        $att = buildAttributes($p);
+
+        // output
+        if ($fullscreen) {
+            echo '<a name="'.($index ? 'd' : 'l').'_:'.$item['id'].'" class="image'.$index.'" title="'.$item['id'].'" href="'.
+                media_managerURL(array('image' => hsc($item['id']), 'ns' => getNS($item['id']), 'tab_details' => 'view')).'">';
+            echo '<span><img src="'.$src.'" '.$att.' /></span>';
+            echo '</a>';
+        }
     }
-    $src = ml($item['id'],array('w'=>$w,'h'=>$h));
-    $p = array();
-    $p['width']  = $w;
-    $p['height'] = $h;
-    $p['alt']    = $item['id'];
-    $p['class']  = 'thumb';
-    $att = buildAttributes($p);
 
-    // output
+    if ($fullscreen) return '';
+
     echo '<div class="detail">';
     echo '<div class="thumb">';
     echo '<a name="d_:'.$item['id'].'" class="select">';
@@ -655,91 +1554,119 @@ function media_printimgdetail($item){
     echo '</div>';
 }
 
+/**
+ * Build link based on the current, adding/rewriting
+ * parameters
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ * @param array $params
+ * @param string $amp - separator
+ * @return string - link
+ */
+function media_managerURL($params=false, $amp='&amp;', $abs=false, $params_array=false) {
+    global $conf;
+    global $ID;
+
+    $gets = array('do' => 'media');
+    $media_manager_params = array('tab_files', 'tab_details', 'image', 'ns', 'view');
+    foreach ($media_manager_params as $x) {
+        if (isset($_REQUEST[$x])) $gets[$x] = $_REQUEST[$x];
+    }
+
+    if ($params) {
+        foreach ($params as $k => $v) {
+            $gets[$k] = $v;
+        }
+    }
+    unset($gets['id']);
+    if ($gets['delete']) {
+        unset($gets['image']);
+        unset($gets['tab_details']);
+    }
+
+    if ($params_array) return $gets;
+
+    return wl($ID,$gets,$abs,$amp);
+}
+
 /**
  * Print the media upload form if permissions are correct
  *
  * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Kate Arzamastseva <pshns@ukr.net>
  */
-function media_uploadform($ns, $auth){
-    global $lang;
+function media_uploadform($ns, $auth, $fullscreen = false){
+    global $lang, $conf;
 
-    if($auth < AUTH_UPLOAD) return; //fixme print info on missing permissions?
+    if($auth < AUTH_UPLOAD) {
+        echo '<div class="nothing">'.$lang['media_perm_upload'].'</div>'.NL;
+        return;
+    }
+    $auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
+
+    $update = false;
+    $id = '';
+    if ($auth >= $auth_ow && $fullscreen && $_REQUEST['mediado'] == 'update') {
+        $update = true;
+        $id = cleanID($_REQUEST['image']);
+    }
 
     // The default HTML upload form
-    $form = new Doku_Form(array('id'      => 'dw__upload',
-                                'action'  => DOKU_BASE.'lib/exe/mediamanager.php',
-                                'enctype' => 'multipart/form-data'));
-    $form->addElement('<div class="upload">' . $lang['mediaupload'] . '</div>');
+    $params = array('id'      => 'dw__upload',
+                    'enctype' => 'multipart/form-data');
+    if (!$fullscreen) {
+        $params['action'] = DOKU_BASE.'lib/exe/mediamanager.php';
+    } else {
+        $params['action'] = media_managerURL(array('tab_files' => 'files',
+            'tab_details' => 'view'), '&');
+    }
+
+    $form = new Doku_Form($params);
+    if (!$fullscreen) echo '<div class="upload">' . $lang['mediaupload'] . '</div>';
     $form->addElement(formSecurityToken());
     $form->addHidden('ns', hsc($ns));
     $form->addElement(form_makeOpenTag('p'));
     $form->addElement(form_makeFileField('upload', $lang['txt_upload'].':', 'upload__file'));
     $form->addElement(form_makeCloseTag('p'));
     $form->addElement(form_makeOpenTag('p'));
-    $form->addElement(form_makeTextField('id', '', $lang['txt_filename'].':', 'upload__name'));
+    $form->addElement(form_makeTextField('mediaid', noNS($id), $lang['txt_filename'].':', 'upload__name'));
     $form->addElement(form_makeButton('submit', '', $lang['btn_upload']));
     $form->addElement(form_makeCloseTag('p'));
 
-    if($auth >= AUTH_DELETE){
+    if($auth >= $auth_ow){
         $form->addElement(form_makeOpenTag('p'));
-        $form->addElement(form_makeCheckboxField('ow', 1, $lang['txt_overwrt'], 'dw__ow', 'check'));
+        $attrs = array();
+        if ($update) $attrs['checked'] = 'checked';
+        $form->addElement(form_makeCheckboxField('ow', 1, $lang['txt_overwrt'], 'dw__ow', 'check', $attrs));
         $form->addElement(form_makeCloseTag('p'));
     }
-    html_form('upload', $form);
 
-    // prepare flashvars for multiupload
-    $opt = array(
-            'L_gridname'  => $lang['mu_gridname'] ,
-            'L_gridsize'  => $lang['mu_gridsize'] ,
-            'L_gridstat'  => $lang['mu_gridstat'] ,
-            'L_namespace' => $lang['mu_namespace'] ,
-            'L_overwrite' => $lang['txt_overwrt'],
-            'L_browse'    => $lang['mu_browse'],
-            'L_upload'    => $lang['btn_upload'],
-            'L_toobig'    => $lang['mu_toobig'],
-            'L_ready'     => $lang['mu_ready'],
-            'L_done'      => $lang['mu_done'],
-            'L_fail'      => $lang['mu_fail'],
-            'L_authfail'  => $lang['mu_authfail'],
-            'L_progress'  => $lang['mu_progress'],
-            'L_filetypes' => $lang['mu_filetypes'],
-            'L_info'      => $lang['mu_info'],
-            'L_lasterr'   => $lang['mu_lasterr'],
-
-            'O_ns'        => ":$ns",
-            'O_backend'   => 'mediamanager.php?'.session_name().'='.session_id(),
-            'O_maxsize'   => php_to_byte(ini_get('upload_max_filesize')),
-            'O_extensions'=> join('|',array_keys(getMimeTypes())),
-            'O_overwrite' => ($auth >= AUTH_DELETE),
-            'O_sectok'    => getSecurityToken(),
-            'O_authtok'   => auth_createToken(),
-            );
-    $var = buildURLparams($opt);
-    // output the flash uploader
-    ?>
-        <div id="dw__flashupload" style="display:none">
-        <div class="upload"><?php echo $lang['mu_intro']?></div>
-        <?php echo html_flashobject('multipleUpload.swf','500','190',null,$opt); ?>
-        </div>
-        <?php
+    echo '<div id="mediamanager__uploader">';
+    html_form('upload', $form);
+    echo '</div>';
 }
 
 /**
  * Print the search field form
  *
  * @author Tobias Sarnowski <sarnowski@cosmocode.de>
+ * @author Kate Arzamastseva <pshns@ukr.net>
  */
-function media_searchform($ns,$query=''){
+function media_searchform($ns,$query='',$fullscreen=false){
     global $lang;
 
     // The default HTML search form
-    $form = new Doku_Form(array('id' => 'dw__mediasearch', 'action' => DOKU_BASE.'lib/exe/mediamanager.php'));
-    $form->addElement('<div class="upload">' . $lang['mediasearch'] . '</div>');
+    $params = array('id' => 'dw__mediasearch');
+    if (!$fullscreen) $params['action'] = DOKU_BASE.'lib/exe/mediamanager.php';
+    else $params['action'] = media_managerURL(array(), '&');
+    $form = new Doku_Form($params);
+    if (!$fullscreen) $form->addElement('<div class="upload">' . $lang['mediasearch'] . '</div>');
     $form->addElement(formSecurityToken());
     $form->addHidden('ns', $ns);
-    $form->addHidden('do', 'searchlist');
+    if (!$fullscreen) $form->addHidden('do', 'searchlist');
+    else $form->addHidden('mediado', 'searchlist');
     $form->addElement(form_makeOpenTag('p'));
-    $form->addElement(form_makeTextField('q', $query,$lang['searchmedia'],'','',array('title'=>sprintf($lang['searchmedia_in'],hsc($ns).':*'))));
+    $form->addElement(form_makeTextField('q', $query,$lang['searchmedia'],'mediamanager__sort_textfield','',array('title'=>sprintf($lang['searchmedia_in'],hsc($ns).':*'))));
     $form->addElement(form_makeButton('submit', '', $lang['btn_search']));
     $form->addElement(form_makeCloseTag('p'));
     html_form('searchmedia', $form);
@@ -786,7 +1713,10 @@ function media_nstree_item($item){
     if(!$item['label']) $item['label'] = $label;
 
     $ret  = '';
+    if (!($_REQUEST['do'] == 'media'))
     $ret .= '<a href="'.DOKU_BASE.'lib/exe/mediamanager.php?ns='.idfilter($item['id']).'" class="idx_dir">';
+    else $ret .= '<a href="'.media_managerURL(array('ns' => idfilter($item['id']), 'tab_files' => 'files'))
+        .'" class="idx_dir">';
     $ret .= $item['label'];
     $ret .= '</a>';
     return $ret;
diff --git a/inc/pageutils.php b/inc/pageutils.php
index c9bf601359f565615376241bc498035cf30474fa..81dcb66e7f5ec4bddf3c4042a0884b64e759de0d 100644
--- a/inc/pageutils.php
+++ b/inc/pageutils.php
@@ -295,8 +295,6 @@ function wikiLockFN($id) {
 /**
  * returns the full path to the meta file specified by ID and extension
  *
- * The filename is URL encoded to protect Unicode chars
- *
  * @author Steven Danz <steven-danz@kc.rr.com>
  */
 function metaFN($id,$ext){
@@ -307,6 +305,19 @@ function metaFN($id,$ext){
     return $fn;
 }
 
+/**
+ * returns the full path to the media's meta file specified by ID and extension
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function mediaMetaFN($id,$ext){
+    global $conf;
+    $id = cleanID($id);
+    $id = str_replace(':','/',$id);
+    $fn = $conf['mediametadir'].'/'.utf8_encodeFN($id).$ext;
+    return $fn;
+}
+
 /**
  * returns an array of full paths to all metafiles of a given ID
  *
@@ -326,12 +337,19 @@ function metaFiles($id){
  * The filename is URL encoded to protect Unicode chars
  *
  * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Kate Arzamastseva <pshns@ukr.net>
  */
-function mediaFN($id){
+function mediaFN($id, $rev=''){
     global $conf;
     $id = cleanID($id);
     $id = str_replace(':','/',$id);
-    $fn = $conf['mediadir'].'/'.utf8_encodeFN($id);
+    if(empty($rev)){
+        $fn = $conf['mediadir'].'/'.utf8_encodeFN($id);
+    }else{
+    	$ext = mimetype($id);
+    	$name = substr($id,0, -1*strlen($ext[0])-1);
+        $fn = $conf['mediaolddir'].'/'.utf8_encodeFN($name .'.'.( (int) $rev ).'.'.$ext[0]);
+    }
     return $fn;
 }
 
diff --git a/inc/parser/xhtml.php b/inc/parser/xhtml.php
index 83359cd557ba1ac73ffc993728d90c911f3794e8..ea1756803a9223bc0bc0d002064762c5bfd63556 100644
--- a/inc/parser/xhtml.php
+++ b/inc/parser/xhtml.php
@@ -805,8 +805,10 @@ class Doku_Renderer_xhtml extends Doku_Renderer {
         if($hash) $link['url'] .= '#'.$hash;
 
         //markup non existing files
-        if (!$exists)
-          $link['class'] .= ' wikilink2';
+        if (!$exists) {
+            $link['class'] .= ' wikilink2';
+            $link['url'] = media_managerURL(array('tab_details' => 'view', 'image' => $src, 'ns' => getNS($src)), '&');
+        }
 
         //output formatted
         if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
diff --git a/inc/plugincontroller.class.php b/inc/plugincontroller.class.php
index ea5725d47938b8d09460bdb18b1288ba3e6e4490..734331c94a6500823e0bd431292795c9e09a9153 100644
--- a/inc/plugincontroller.class.php
+++ b/inc/plugincontroller.class.php
@@ -11,11 +11,16 @@ if(!defined('DOKU_PLUGIN'))  define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
 
 class Doku_Plugin_Controller {
 
-    var $list_enabled = array();
-    var $list_disabled = array();
     var $list_bytype = array();
+    var $tmp_plugins = array();
+    var $plugin_cascade = array('default'=>array(),'local'=>array(),'protected'=>array());
+    var $last_local_config_file = '';
 
-    function Doku_Plugin_Controller() {
+    /**
+     * Populates the master list of plugins
+     */
+    function __construct() {
+        $this->loadConfig();
         $this->_populateMasterList();
     }
 
@@ -37,7 +42,7 @@ class Doku_Plugin_Controller {
 
         // request the complete list
         if (!$type) {
-            return $all ? array_merge($this->list_enabled,$this->list_disabled) : $this->list_enabled;
+            return $all ? array_keys($this->tmp_plugins) : array_keys(array_filter($this->tmp_plugins));
         }
 
         if (!isset($this->list_bytype[$type]['enabled'])) {
@@ -61,9 +66,11 @@ class Doku_Plugin_Controller {
      * @param  $disabled bool   true to load even disabled plugins
      * @return objectreference  the plugin object or null on failure
      */
-    function &load($type,$name,$new=false,$disabled=false){
+    function load($type,$name,$new=false,$disabled=false){
+
         //we keep all loaded plugins available in global scope for reuse
         global $DOKU_PLUGINS;
+        global $lang;
 
         list($plugin,$component) = $this->_splitName($name);
 
@@ -85,12 +92,12 @@ class Doku_Plugin_Controller {
 
         //construct class and instantiate
         if (!class_exists($class, true)) {
+
             # the plugin might be in the wrong directory
             $dir = $this->get_directory($plugin);
             $inf = confToHash(DOKU_PLUGIN."$dir/plugin.info.txt");
             if($inf['base'] && $inf['base'] != $plugin){
-                msg("Plugin installed incorrectly. Rename plugin directory '".
-                    hsc($plugin)."' to '".hsc($inf['base'])."'.",-1);
+                msg(sprintf($lang['plugin_install_err'],hsc($plugin),hsc($inf['base'])),-1);
             }
             return null;
         }
@@ -100,30 +107,28 @@ class Doku_Plugin_Controller {
     }
 
     function isdisabled($plugin) {
-        return (array_search($plugin, $this->list_enabled) === false);
+        return empty($this->tmp_plugins[$plugin]);
     }
 
-    function enable($plugin) {
-        if (array_search($plugin, $this->list_disabled) !== false) {
-            return @unlink(DOKU_PLUGIN.$plugin.'/disabled');
-        }
-        return false;
+    function disable($plugin) {
+        if(array_key_exists($plugin,$this->plugin_cascade['protected'])) return false;
+        $this->tmp_plugins[$plugin] = 0;
+        return $this->saveList();
     }
 
-    function disable($plugin) {
-        if (array_search($plugin, $this->list_enabled) !== false) {
-            return @touch(DOKU_PLUGIN.$plugin.'/disabled');
-        }
-        return false;
+    function enable($plugin) {
+        if(array_key_exists($plugin,$this->plugin_cascade['protected'])) return false;
+        $this->tmp_plugins[$plugin] = 1;
+        return $this->saveList();
     }
 
     function get_directory($plugin) {
         return $plugin;
     }
 
-    function _populateMasterList() {
-        global $conf;
-        if ($dh = opendir(DOKU_PLUGIN)) {
+    protected function _populateMasterList() {
+        if ($dh = @opendir(DOKU_PLUGIN)) {
+            $all_plugins = array();
             while (false !== ($plugin = readdir($dh))) {
                 if ($plugin[0] == '.') continue;               // skip hidden entries
                 if (is_file(DOKU_PLUGIN.$plugin)) continue;    // skip files, we're only interested in directories
@@ -132,19 +137,119 @@ class Doku_Plugin_Controller {
                     // the plugin was disabled by rc2009-01-26
                     // disabling mechanism was changed back very soon again
                     // to keep everything simple we just skip the plugin completely
-                }elseif(@file_exists(DOKU_PLUGIN.$plugin.'/disabled') ||
-                        ($plugin === 'plugin' && isset($conf['pluginmanager']) &&
-                         !$conf['pluginmanager'])){
-                    $this->list_disabled[] = $plugin;
+                } elseif (@file_exists(DOKU_PLUGIN.$plugin.'/disabled')) {
+                    // treat this as a default disabled plugin(over-rideable by the plugin manager)
+                    // deprecated 2011-09-10 (usage of disabled files)
+                    if (empty($this->plugin_cascade['local'][$plugin])) {
+                        $all_plugins[$plugin] = 0;
+                    } else {
+                        $all_plugins[$plugin] = 1;
+                    }
+                    $this->plugin_cascade['default'][$plugin] = 0;
+
+                } elseif ((array_key_exists($plugin,$this->tmp_plugins) && $this->tmp_plugins[$plugin] == 0) ||
+                          ($plugin === 'plugin' && isset($conf['pluginmanager']) && !$conf['pluginmanager'])){
+                    $all_plugins[$plugin] = 0;
+
+                } elseif ((array_key_exists($plugin,$this->tmp_plugins) && $this->tmp_plugins[$plugin] == 1)) {
+                    $all_plugins[$plugin] = 1;
                 } else {
-                    $this->list_enabled[] = $plugin;
+                    $all_plugins[$plugin] = 1;
                 }
             }
+            $this->tmp_plugins = $all_plugins;
+            if (!file_exists($this->last_local_config_file)) {
+                $this->saveList(true);
+            }
+        }
+    }
+
+    protected function checkRequire($files) {
+        $plugins = array();
+        foreach($files as $file) {
+            if(file_exists($file)) {
+                @include_once($file);
+            }
+        }
+        return $plugins;
+    }
+
+    function getCascade() {
+        return $this->plugin_cascade;
+    }
+
+    /**
+     * Save the current list of plugins
+     */
+    function saveList($forceSave = false) {
+        global $conf;
+
+        if (empty($this->tmp_plugins)) return false;
+
+        // Rebuild list of local settings
+        $local_plugins = $this->rebuildLocal();
+        if($local_plugins != $this->plugin_cascade['local'] || $forceSave) {
+            $file = $this->last_local_config_file;
+            $out = "<?php\n/*\n * Local plugin enable/disable settings\n * Auto-generated through plugin/extension manager\n *\n".
+                   " * NOTE: Plugins will not be added to this file unless there is a need to override a default setting. Plugins are\n".
+                   " *       enabled by default, unless having a 'disabled' file in their plugin folder.\n */\n";
+            foreach ($local_plugins as $plugin => $value) {
+                $out .= "\$plugins['$plugin'] = $value;\n";
+            }
+            // backup current file (remove any existing backup)
+            if (@file_exists($file)) {
+                $backup = $file.'.bak';
+                if (@file_exists($backup)) @unlink($backup);
+                if (!@copy($file,$backup)) return false;
+                if ($conf['fperm']) chmod($backup, $conf['fperm']);
+            }
+            //check if can open for writing, else restore
+            return io_saveFile($file,$out);
+        }
+        return false;
+    }
+
+    /**
+     * Rebuild the set of local plugins
+     * @return array array of plugins to be saved in end($config_cascade['plugins']['local'])
+     */
+    function rebuildLocal() {
+        //assign to local variable to avoid overwriting
+        $backup = $this->tmp_plugins;
+        //Can't do anything about protected one so rule them out completely
+        $local_default = array_diff_key($backup,$this->plugin_cascade['protected']);
+        //Diff between local+default and default
+        //gives us the ones we need to check and save
+        $diffed_ones = array_diff_key($local_default,$this->plugin_cascade['default']);
+        //The ones which we are sure of (list of 0s not in default)
+        $sure_plugins = array_filter($diffed_ones,array($this,'negate'));
+        //the ones in need of diff
+        $conflicts = array_diff_key($local_default,$diffed_ones);
+        //The final list
+        return array_merge($sure_plugins,array_diff_assoc($conflicts,$this->plugin_cascade['default']));
+    }
+
+    /**
+     * Build the list of plugins and cascade
+     * 
+     */
+    function loadConfig() {
+        global $config_cascade;
+        foreach(array('default','protected') as $type) {
+            if(array_key_exists($type,$config_cascade['plugins']))
+                $this->plugin_cascade[$type] = $this->checkRequire($config_cascade['plugins'][$type]);
         }
+        $local = $config_cascade['plugins']['local'];
+        $this->last_local_config_file = array_pop($local);
+        $this->plugin_cascade['local'] = $this->checkRequire(array($this->last_local_config_file));
+        if(is_array($local)) {
+            $this->plugin_cascade['default'] = array_merge($this->plugin_cascade['default'],$this->checkRequire($local));
+        }
+        $this->tmp_plugins = array_merge($this->plugin_cascade['default'],$this->plugin_cascade['local'],$this->plugin_cascade['protected']);
     }
 
     function _getListByType($type, $enabled) {
-        $master_list = $enabled ? $this->list_enabled : $this->list_disabled;
+        $master_list = $enabled ? array_keys(array_filter($this->tmp_plugins)) : array_keys(array_filter($this->tmp_plugins,array($this,'negate')));
 
         $plugins = array();
         foreach ($master_list as $plugin) {
@@ -169,11 +274,13 @@ class Doku_Plugin_Controller {
     }
 
     function _splitName($name) {
-        if (array_search($name, $this->list_enabled + $this->list_disabled) === false) {
+        if (array_search($name, array_keys($this->tmp_plugins)) === false) {
             return explode('_',$name,2);
         }
 
         return array($name,'');
     }
-
+    function negate($input) {
+        return !(bool) $input;
+    }
 }
diff --git a/inc/pluginutils.php b/inc/pluginutils.php
index 85bcaee1e80b09dcfb234b718a65d1503e7a2a95..53cfedf82b6edeb7df2c2efdcfb28205df9f3507 100644
--- a/inc/pluginutils.php
+++ b/inc/pluginutils.php
@@ -16,7 +16,7 @@ function plugin_list($type='',$all=false) {
     global $plugin_controller;
     return $plugin_controller->getList($type,$all);
 }
-function &plugin_load($type,$name,$new=false,$disabled=false) {
+function plugin_load($type,$name,$new=false,$disabled=false) {
     global $plugin_controller;
     return $plugin_controller->load($type,$name,$new,$disabled);
 }
@@ -36,3 +36,7 @@ function plugin_directory($plugin) {
     global $plugin_controller;
     return $plugin_controller->get_directory($plugin);
 }
+function plugin_getcascade() {
+    global $plugin_controller;
+    return $plugin_controller->getCascade();
+}
diff --git a/inc/search.php b/inc/search.php
index 7b53edabe7d5abe753de7d9b21ff2ccc7ac34e40..a26ae48081467dc827ddf8d8e805ddf859170ae9 100644
--- a/inc/search.php
+++ b/inc/search.php
@@ -21,9 +21,10 @@ if(!defined('DOKU_INC')) die('meh.');
  * @param   int       $lvl  Recursion Level
  * @author  Andreas Gohr <andi@splitbrain.org>
  */
-function search(&$data,$base,$func,$opts,$dir='',$lvl=1){
+function search(&$data,$base,$func,$opts,$dir='',$lvl=1,$sort=false){
     $dirs   = array();
     $files  = array();
+    $filepaths = array();
 
     //read in directories and files
     $dh = @opendir($base.'/'.$dir);
@@ -35,9 +36,14 @@ function search(&$data,$base,$func,$opts,$dir='',$lvl=1){
             continue;
         }
         $files[] = $dir.'/'.$file;
+        $filepaths[] = $base.'/'.$dir.'/'.$file;
     }
     closedir($dh);
-    sort($files);
+    if ($sort == 'date') {
+        @array_multisort(array_map('filemtime', $filepaths), SORT_NUMERIC, SORT_DESC, $files);
+    } else {
+        sort($files);
+    }
     sort($dirs);
 
     //give directories to userfunction then recurse
@@ -78,8 +84,8 @@ function search_callback($func,&$data,$base,$file,$type,$lvl,$opts){
  * return values for files are ignored
  *
  * All functions should check the ACL for document READ rights
- * namespaces (directories) are NOT checked as this would break
- * the recursion (You can have an nonreadable dir over a readable
+ * namespaces (directories) are NOT checked (when sneaky_index is 0) as this
+ * would break the recursion (You can have an nonreadable dir over a readable
  * one deeper nested) also make sure to check the file type (for example
  * in case of lockfiles).
  */
@@ -101,45 +107,22 @@ function search_qsearch(&$data,$base,$file,$type,$lvl,$opts){
 /**
  * Build the browsable index of pages
  *
- * $opts['ns'] is the current namespace
+ * $opts['ns'] is the currently viewed namespace
  *
  * @author  Andreas Gohr <andi@splitbrain.org>
  */
 function search_index(&$data,$base,$file,$type,$lvl,$opts){
     global $conf;
-    $return = true;
-
-    $item = array();
-
-    if($type == 'd' && !preg_match('#^'.$file.'(/|$)#','/'.$opts['ns'])){
-        //add but don't recurse
-        $return = false;
-    }elseif($type == 'f' && ($opts['nofiles'] || substr($file,-4) != '.txt')){
-        //don't add
-        return false;
-    }
-
-    $id = pathID($file,($type == 'd'));
-
-    if($type=='d' && $conf['sneaky_index'] && auth_quickaclcheck($id.':') < AUTH_READ){
-        return false;
-    }
-
-    //check hidden
-    if(isHiddenPage($id)){
-        return false;
-    }
-
-    //check ACL
-    if($type=='f' && auth_quickaclcheck($id) < AUTH_READ){
-        return false;
-    }
-
-    $data[]=array( 'id'    => $id,
-            'type'  => $type,
-            'level' => $lvl,
-            'open'  => $return );
-    return $return;
+    $opts = array(
+        'pagesonly' => true,
+        'listdirs' => true,
+        'listfiles' => !$opts['nofiles'],
+        'sneakyacl' => $conf['sneaky_index'],
+        // Hacky, should rather use recmatch
+        'depth' => preg_match('#^'.$file.'(/|$)#','/'.$opts['ns']) ? 0 : -1
+    );
+
+    return search_universal($data, $base, $file, $type, $lvl, $opts);
 }
 
 /**
diff --git a/inc/template.php b/inc/template.php
index 5184929b8e8338f418fb3b24c57c985f283deb37..96e0668c251f66fee83e561bd86784e4e24c91e5 100644
--- a/inc/template.php
+++ b/inc/template.php
@@ -89,7 +89,8 @@ function tpl_content_core(){
                 $_REQUEST['first'] = $_REQUEST['first'][0];
             }
             $first = is_numeric($_REQUEST['first']) ? intval($_REQUEST['first']) : 0;
-            html_recent($first);
+            $show_changes = $_REQUEST['show_changes'];
+            html_recent($first, $show_changes);
             break;
         case 'index':
             html_index($IDX); #FIXME can this be pulled from globals? is it sanitized correctly?
@@ -122,6 +123,9 @@ function tpl_content_core(){
         case 'subscribe':
             tpl_subscribe();
             break;
+        case 'media':
+            tpl_media();
+            break;
         default:
             $evt = new Doku_Event('TPL_ACT_UNKNOWN',$ACT);
             if ($evt->advise_before())
@@ -626,6 +630,8 @@ function tpl_get_action($type) {
             // Superseded by subscribe/subscription
             return '';
             break;
+        case 'media':
+            break;
         default:
             return '[unknown %s type]';
             break;
@@ -1103,9 +1109,7 @@ function tpl_mediaContent($fromajax=false){
     $evt = new Doku_Event('MEDIAMANAGER_CONTENT_OUTPUT', $data);
     if ($evt->advise_before()) {
         $do = $data['do'];
-        if($do == 'metaform'){
-            media_metaform($IMG,$AUTH);
-        }elseif($do == 'filesinuse'){
+        if($do == 'filesinuse'){
             media_filesinuse($INUSE,$IMG);
         }elseif($do == 'filelist'){
             media_filelist($NS,$AUTH,$JUMPTO);
@@ -1121,6 +1125,93 @@ function tpl_mediaContent($fromajax=false){
 
 }
 
+/**
+ * Prints the central column in full-screen media manager
+ * Depending on the opened tab this may be a list of
+ * files in a namespace, upload form or search form
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function tpl_mediaFileList(){
+    global $AUTH;
+    global $NS;
+    global $JUMPTO;
+
+    $opened_tab = $_REQUEST['tab_files'];
+    if (!$opened_tab || !in_array($opened_tab, array('files', 'upload', 'search'))) $opened_tab = 'files';
+    if ($_REQUEST['mediado'] == 'update') $opened_tab = 'upload';
+
+    media_tabs_files($opened_tab);
+
+    if ($opened_tab == 'files') {
+        echo '<div id="mediamanager__files">';
+        media_tab_files($NS,$AUTH,$JUMPTO);
+        echo '</div>';
+
+    } elseif ($opened_tab == 'upload') {
+        echo '<div id="mediamanager__files">';
+        media_tab_upload($NS,$AUTH,$JUMPTO);
+        echo '</div>';
+
+    } elseif ($opened_tab == 'search') {
+        echo '<div id="mediamanager__files">';
+        media_tab_search($NS,$AUTH);
+        echo '</div>';
+    }
+
+}
+
+/**
+ * Prints the third column in full-screen media manager
+ * Depending on the opened tab this may be details of the
+ * selected file, the meta editing dialog or
+ * list of file revisions
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function tpl_mediaFileDetails($image, $rev){
+    global $AUTH, $NS, $conf, $DEL;
+
+    $removed = (!file_exists(mediaFN($image)) && file_exists(mediaMetaFN($image, '.changes')) && $conf['mediarevisions']);
+    if (!$image || (!file_exists(mediaFN($image)) && !$removed) || $DEL) return '';
+    if ($rev && !file_exists(mediaFN($image, $rev))) $rev = false;
+    if (isset($NS) && getNS($image) != $NS) return '';
+    $do = $_REQUEST['mediado'];
+
+    $opened_tab = $_REQUEST['tab_details'];
+
+    $tab_array = array('view');
+    list($ext, $mime) = mimetype($image);
+    if ($mime == 'image/jpeg') {
+        $tab_array[] = 'edit';
+    }
+    if ($conf['mediarevisions']) {
+        $tab_array[] = 'history';
+    }
+
+    if (!$opened_tab || !in_array($opened_tab, $tab_array)) $opened_tab = 'view';
+    if ($_REQUEST['edit']) $opened_tab = 'edit';
+    if ($do == 'restore') $opened_tab = 'view';
+
+    media_tabs_details($image, $opened_tab);
+
+    if ($opened_tab == 'view') {
+        echo '<div id="mediamanager__details">';
+        media_tab_view($image, $NS, $AUTH, $rev);
+        echo '</div>';
+
+    } elseif ($opened_tab == 'edit' && !$removed) {
+        echo '<div id="mediamanager__details">';
+        media_tab_edit($image, $NS, $AUTH);
+        echo '</div>';
+
+    } elseif ($opened_tab == 'history' && $conf['mediarevisions']) {
+        echo '<div id="mediamanager__details">';
+        media_tab_history($image,$NS,$AUTH);
+        echo '</div>';
+    }
+}
+
 /**
  * prints the namespace tree in the mediamanger popup
  *
@@ -1130,7 +1221,6 @@ function tpl_mediaContent($fromajax=false){
  */
 function tpl_mediaTree(){
     global $NS;
-
     ptln('<div id="media__tree">');
     media_nstree($NS);
     ptln('</div>');
@@ -1386,6 +1476,52 @@ function tpl_favicon($types=array('favicon')) {
     return $return;
 }
 
+/**
+ * Prints full-screen media manager
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+function tpl_media() {
+    //
+    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;
+
+    echo '<div id="mediamanager__page">';
+    echo '<h1>'.$lang['btn_media'].'</h1>';
+    echo '<div id="mediamanager__layout">';
+
+    echo '<div id="mediamanager__layout_namespaces" class="layout-resizable" >';
+    html_msgarea();
+    echo '<div class="mediamanager-tabs">';
+    echo '<a href="#" class="selected">'.hsc($lang['namespaces']).'</a>';
+    echo '<div class="clearer"></div>';
+    echo '</div>';
+    echo '<div class="background-container">';
+    echo hsc($lang['namespaces']);
+    echo '</div>';
+    echo '<div class="scroll-container">';
+    tpl_mediaTree();
+    echo '</div>';
+    echo '</div>';
+
+    echo '<div id="mediamanager__layout_list" class="layout-resizable" >';
+    tpl_mediaFileList();
+    echo '</div>';
+
+    echo '<div id="mediamanager__layout_detail" class="layout" >';
+    tpl_mediaFileDetails($image, $rev);
+    echo '</div>';
+
+    echo '<div class="clearer"></div>';
+    echo '</div>';
+    echo '</div>';
+}
 
 //Setup VIM: ex: et ts=4 :
 
diff --git a/lib/exe/ajax.php b/lib/exe/ajax.php
index 2bfa3680c8950ae0463bde45f993af14bcd2f74d..d4ef8dc116bc95831aa399c978ab2db2754b36e2 100644
--- a/lib/exe/ajax.php
+++ b/lib/exe/ajax.php
@@ -208,7 +208,100 @@ function ajax_medialist(){
     global $NS;
 
     $NS = $_POST['ns'];
-    tpl_mediaContent(true);
+    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, false, true);
+
+    $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);
+    }
+    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;
 }
 
 /**
diff --git a/lib/exe/css.php b/lib/exe/css.php
index 8f86f2433d463f4b5e9b484b7a6a14cd4d6042ea..81f47d8fa85c9d6d51fe8dbd730f65b605f5416e 100644
--- a/lib/exe/css.php
+++ b/lib/exe/css.php
@@ -189,7 +189,7 @@ function css_interwiki(){
 function css_filetypes(){
 
     // default style
-    echo 'a.mediafile {';
+    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;';
@@ -212,7 +212,7 @@ function css_filetypes(){
     }
     foreach($exts as $ext=>$type){
         $class = preg_replace('/[^_\-a-z0-9]+/','_',$ext);
-        echo "a.mf_$class {";
+        echo ".mf_$class {";
         echo '  background-image: url('.DOKU_BASE.'lib/images/fileicons/'.$ext.$type.')';
         echo '}';
     }
diff --git a/lib/exe/fetch.php b/lib/exe/fetch.php
index 3ad4f19371dcadddf5a8a854b59b33958c49f0fb..143d40f227c8230468421f5ff54b243413d2a2b9 100644
--- a/lib/exe/fetch.php
+++ b/lib/exe/fetch.php
@@ -20,6 +20,10 @@
   $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';
@@ -28,7 +32,7 @@
   }
 
   // check for permissions, preconditions and cache external files
-  list($STATUS, $STATUSMESSAGE) = checkFileStatus($MEDIA, $FILE);
+  list($STATUS, $STATUSMESSAGE) = checkFileStatus($MEDIA, $FILE, $REV);
 
   // prepare data for plugin events
   $data = array('media'           => $MEDIA,
@@ -147,7 +151,7 @@ function sendFile($file,$mime,$dl,$cache){
  * @param $file reference to the file variable
  * @returns array(STATUS, STATUSMESSAGE)
  */
-function checkFileStatus(&$media, &$file) {
+function checkFileStatus(&$media, &$file, $rev='') {
   global $MIME, $EXT, $CACHE;
 
   //media to local file
@@ -172,7 +176,7 @@ function checkFileStatus(&$media, &$file) {
     if(auth_quickaclcheck(getNS($media).':X') < AUTH_READ){
       return array( 403, 'Forbidden' );
     }
-    $file  = mediaFN($media);
+    $file  = mediaFN($media, $rev);
   }
 
   //check file existance
diff --git a/lib/exe/js.php b/lib/exe/js.php
index e96d45ee6607e56084f3558fa75ce590924cb2dd..33f8c695dc1c55116c6cdacf3320d030fe784e4f 100644
--- a/lib/exe/js.php
+++ b/lib/exe/js.php
@@ -43,6 +43,8 @@ function js_out(){
                 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',
diff --git a/lib/exe/mediamanager.php b/lib/exe/mediamanager.php
index 02fde5a8d4908c818d3cf211f668797c99783c3b..5f09fe1f8dab79d5b9dca060719fedd59712f058 100644
--- a/lib/exe/mediamanager.php
+++ b/lib/exe/mediamanager.php
@@ -35,7 +35,7 @@
     $AUTH = auth_quickaclcheck("$NS:*");
 
     // do not display the manager if user does not have read access
-    if($AUTH < AUTH_READ) {
+    if($AUTH < AUTH_READ && !$fullscreen) {
         header('HTTP/1.0 403 Forbidden');
         die($lang['accessdenied']);
     }
@@ -76,10 +76,20 @@
     }
 
     // handle meta saving
-    if($IMG && $_REQUEST['do']['save']){
+    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;
@@ -88,7 +98,7 @@
         }
         if ($res & DOKU_MEDIA_DELETED) {
             $msg = sprintf($lang['deletesucc'], noNS($DEL));
-            if ($res & DOKU_MEDIA_EMPTY_NS) {
+            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']);
@@ -102,9 +112,11 @@
             msg(sprintf($lang['deletefail'],noNS($DEL)),-1);
         }
     }
-
     // finished - start output
-    header('Content-Type: text/html; charset=utf-8');
-    include(template('mediamanager.php'));
+
+    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/images/icon-file.png b/lib/images/icon-file.png
new file mode 100644
index 0000000000000000000000000000000000000000..d350c8c3121240456de10bce8ba7d6cef8eb3266
Binary files /dev/null and b/lib/images/icon-file.png differ
diff --git a/lib/images/icon-list.png b/lib/images/icon-list.png
new file mode 100644
index 0000000000000000000000000000000000000000..ecfeed92d9c042ff0840a30084a092c0c13013c3
Binary files /dev/null and b/lib/images/icon-list.png differ
diff --git a/lib/images/icon-sort.png b/lib/images/icon-sort.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6403dd3c4bce08f7ba9487ad15fcfe7a3c04ff0
Binary files /dev/null and b/lib/images/icon-sort.png differ
diff --git a/lib/images/icon-thumb.png b/lib/images/icon-thumb.png
new file mode 100644
index 0000000000000000000000000000000000000000..ccc7a101dda21b9f9058ba933402dce3b8fdc167
Binary files /dev/null and b/lib/images/icon-thumb.png differ
diff --git a/lib/plugins/acl/admin.php b/lib/plugins/acl/admin.php
index 53f6db0cc99ebd0d0f427335e45bce23ff6117b9..a6b0624bce42a9e83da72e7bfdb9bceedb62c49a 100644
--- a/lib/plugins/acl/admin.php
+++ b/lib/plugins/acl/admin.php
@@ -30,20 +30,6 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
     var $usersgroups = array();
     var $specials = array();
 
-    /**
-     * return some info
-     */
-    function getInfo(){
-        return array(
-            'author' => 'Andreas Gohr',
-            'email'  => 'andi@splitbrain.org',
-            'date'   => '2011-04-16',
-            'name'   => 'ACL Manager',
-            'desc'   => 'Manage Page Access Control Lists',
-            'url'    => 'http://dokuwiki.org/plugin:acl',
-        );
-    }
-
     /**
      * return prompt for admin menu
      */
diff --git a/lib/plugins/acl/plugin.info.txt b/lib/plugins/acl/plugin.info.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f108a239068ae11190e952aef725ff40befcce28
--- /dev/null
+++ b/lib/plugins/acl/plugin.info.txt
@@ -0,0 +1,6 @@
+author Andreas Gohr
+email  andi@splitbrain.org
+date   2011-04-16
+name   ACL Manager
+desc   Manage Page Access Control Lists
+url    http://dokuwiki.org/plugin:acl
diff --git a/lib/plugins/acl/script.js b/lib/plugins/acl/script.js
index 4fc97aeb56cc7fb1b2d3424ec31c8e950ac6e2ba..2598bcef844f6dc6995718bdefa7e6c784d86c57 100644
--- a/lib/plugins/acl/script.js
+++ b/lib/plugins/acl/script.js
@@ -33,7 +33,7 @@ var dw_acl = {
                                show_sublist,
                                'html'
                            );
-                       },
+    },
 
                        toggle_display: function ($clicky, opening) {
                            $clicky.attr('src',
@@ -93,13 +93,13 @@ var dw_acl = {
 
         $link = jQuery(this);
 
-        // remove highlighting
-        jQuery('#acl__tree a.cur').removeClass('cur');
+            // remove highlighting
+            jQuery('#acl__tree a.cur').removeClass('cur');
 
-        // add new highlighting
+            // add new highlighting
         $link.addClass('cur');
 
-        // set new page to detail form
+            // set new page to detail form
         $frm = jQuery('#acl__detail form');
         if ($link.hasClass('wikilink1')) {
             $frm.find('input[name=ns]').val('');
@@ -107,7 +107,7 @@ var dw_acl = {
         } else if ($link.hasClass('idx_dir')) {
             $frm.find('input[name=ns]').val(dw_acl.parseatt($link[0].search).ns);
             $frm.find('input[name=id]').val('');
-        }
+            }
         dw_acl.loadinfo();
 
         return false;
diff --git a/lib/plugins/config/admin.php b/lib/plugins/config/admin.php
index e24f3b87b3c3d146aa900533cd4e07f2831198af..64906171d3340ceb85da8270ab02bb693a48dfb2 100644
--- a/lib/plugins/config/admin.php
+++ b/lib/plugins/config/admin.php
@@ -32,21 +32,6 @@ class admin_plugin_config extends DokuWiki_Admin_Plugin {
     var $_session_started = false;
     var $_localised_prompts = false;
 
-    /**
-     * return some info
-     */
-    function getInfo(){
-
-      return array(
-        'author' => 'Christopher Smith',
-        'email'  => 'chris@jalakai.co.uk',
-        'date'   => '2007-08-05',
-        'name'   => 'Configuration Manager',
-        'desc'   => "Manage Dokuwiki's Configuration Settings",
-        'url'    => 'http://dokuwiki.org/plugin:config',
-      );
-    }
-
     function getMenuSort() { return 100; }
 
     /**
diff --git a/lib/plugins/config/plugin.info.txt b/lib/plugins/config/plugin.info.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ace4889b6c4f8a147dfa617eea5a570eed4dc087
--- /dev/null
+++ b/lib/plugins/config/plugin.info.txt
@@ -0,0 +1,6 @@
+author Christopher Smith
+email  chris@jalakai.co.uk
+date   2007-08-05
+name   Configuration Manager
+desc   Manage Dokuwiki's Configuration Settings
+url    http://dokuwiki.org/plugin:config
diff --git a/lib/plugins/info/plugin.info.txt b/lib/plugins/info/plugin.info.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2432225f1f744564101bb38136f9a6961c8c0431
--- /dev/null
+++ b/lib/plugins/info/plugin.info.txt
@@ -0,0 +1,6 @@
+author Andreas Gohr
+email andi@splitbrain.org
+date 2008-09-12
+name Info Plugin
+desc Displays information about various DokuWiki internals
+url http://dokuwiki.org/plugin:info
diff --git a/lib/plugins/popularity/lang/uk/submitted.txt b/lib/plugins/popularity/lang/uk/submitted.txt
new file mode 100644
index 0000000000000000000000000000000000000000..90213858dcca718cb9eb2751de79ddca305b4457
--- /dev/null
+++ b/lib/plugins/popularity/lang/uk/submitted.txt
@@ -0,0 +1,2 @@
+====== Відгук популярності ======
+Дані були успішно відправлені.
\ No newline at end of file
diff --git a/lib/plugins/revert/admin.php b/lib/plugins/revert/admin.php
index e188e2488d363f636289daf3273bd2756d860366..2aaf1395f6f6bbac99fbf32fbad904f61bf0e526 100644
--- a/lib/plugins/revert/admin.php
+++ b/lib/plugins/revert/admin.php
@@ -20,20 +20,6 @@ class admin_plugin_revert extends DokuWiki_Admin_Plugin {
         $this->setupLocale();
     }
 
-    /**
-     * return some info
-     */
-    function getInfo(){
-        return array(
-            'author' => 'Andreas Gohr',
-            'email'  => 'andi@splitbrain.org',
-            'date'   => '2008-12-10',
-            'name'   => 'Revert Manager',
-            'desc'   => 'Allows you to mass revert recent edits',
-            'url'    => 'http://dokuwiki.org/plugin:revert',
-        );
-    }
-
     /**
      * access for managers
      */
diff --git a/lib/plugins/revert/plugin.info.txt b/lib/plugins/revert/plugin.info.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5bb6f34138303e9c62a980c16b69fec0e0cfb073
--- /dev/null
+++ b/lib/plugins/revert/plugin.info.txt
@@ -0,0 +1,6 @@
+author Andreas Gohr
+email  andi@splitbrain.org
+date   2008-12-10
+name   Revert Manager
+desc   Allows you to mass revert recent edits
+url    http://dokuwiki.org/plugin:revert
diff --git a/lib/plugins/usermanager/admin.php b/lib/plugins/usermanager/admin.php
index e40ee9b7e69a58aa6bd05c8e2d1f2b5edc64d8e2..8e90be0935e48d0c74ef2c9b79d15eed5192904f 100644
--- a/lib/plugins/usermanager/admin.php
+++ b/lib/plugins/usermanager/admin.php
@@ -51,20 +51,6 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin {
         }
     }
 
-    /**
-     * return some info
-     */
-    function getInfo(){
-
-        return array(
-            'author' => 'Chris Smith',
-            'email'  => 'chris@jalakai.co.uk',
-            'date'   => '2008-09-17',
-            'name'   => 'User Manager',
-            'desc'   => 'Manage users '.$this->disabled,
-            'url'    => 'http://dokuwiki.org/plugin:usermanager',
-        );
-    }
      /**
      * return prompt for admin menu
      */
diff --git a/lib/plugins/usermanager/plugin.info.txt b/lib/plugins/usermanager/plugin.info.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7ec5fafd52558c1a9dc2aeadd4f774501d0aa51d
--- /dev/null
+++ b/lib/plugins/usermanager/plugin.info.txt
@@ -0,0 +1,6 @@
+author Chris Smith
+email  chris@jalakai.co.uk
+date   2008-09-17
+name   User Manager
+desc   Manage users 
+url    http://dokuwiki.org/plugin:usermanager
diff --git a/lib/scripts/behaviour.js b/lib/scripts/behaviour.js
index afb210840af10d8352d68082451de04587a74d74..cfdc8915775633a0d77262df967208d960daff46 100644
--- a/lib/scripts/behaviour.js
+++ b/lib/scripts/behaviour.js
@@ -63,7 +63,9 @@ var dw_behaviour = {
     quickSelect: function(){
         jQuery('select.quickselect')
             .change(function(e){ e.target.form.submit(); })
-            .parents('form').find('input[type=submit]').hide();
+            .parents('form').find('input[type=submit]').each(function(){
+                if (!jQuery(this).hasClass('show')) jQuery(this).hide();
+            });
     },
 
     /**
diff --git a/lib/scripts/cookie.js b/lib/scripts/cookie.js
index f97882855ee5f8cb6f081ac9b98d7a37ceb2f7b7..4dd77beeaa5ee34bf3a616b4e69b27f9d60698aa 100644
--- a/lib/scripts/cookie.js
+++ b/lib/scripts/cookie.js
@@ -24,10 +24,10 @@ DokuCookie = {
         this.data[key] = val;
 
         //save the whole data array
-        jQuery.each(this.data, function (val, key) {
-            if (this.data.hasOwnProperty(key)) {
+        jQuery.each(this.data, function (key, val) {
+            if (DokuCookie.data.hasOwnProperty(key)) {
                 text += '#'+encodeURIComponent(key)+'#'+encodeURIComponent(val);
-            }
+        }
         });
         jQuery.cookie(this.name,text.substr(1), {expires: 365, path: DOKU_BASE});
     },
@@ -59,5 +59,5 @@ DokuCookie = {
                 this.data[decodeURIComponent(parts[i])] = decodeURIComponent(parts[i+1]);
             }
         }
-    }
+        }
 };
diff --git a/lib/scripts/fileuploader.js b/lib/scripts/fileuploader.js
new file mode 100644
index 0000000000000000000000000000000000000000..e75b8d3a52364ce34adf13c3c9ae69bb0efc11e4
--- /dev/null
+++ b/lib/scripts/fileuploader.js
@@ -0,0 +1,1247 @@
+/**
+ * http://github.com/valums/file-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop.
+ * © 2010 Andrew Valums ( andrew(at)valums.com )
+ *
+ * Licensed under GNU GPL 2 or later and GNU LGPL 2 or later, see license.txt.
+ */
+
+//
+// Helper functions
+//
+
+var qq = qq || {};
+
+/**
+ * Adds all missing properties from second obj to first obj
+ */
+qq.extend = function(first, second){
+    for (var prop in second){
+        first[prop] = second[prop];
+    }
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+    if (arr.indexOf) return arr.indexOf(elt, from);
+
+    from = from || 0;
+    var len = arr.length;
+
+    if (from < 0) from += len;
+
+    for (; from < len; from++){
+        if (from in arr && arr[from] === elt){
+            return from;
+        }
+    }
+    return -1;
+};
+
+qq.getUniqueId = (function(){
+    var id = 0;
+    return function(){ return id++; };
+})();
+
+//
+// Events
+
+qq.attach = function(element, type, fn){
+    if (element.addEventListener){
+        element.addEventListener(type, fn, false);
+    } else if (element.attachEvent){
+        element.attachEvent('on' + type, fn);
+    }
+};
+qq.detach = function(element, type, fn){
+    if (element.removeEventListener){
+        element.removeEventListener(type, fn, false);
+    } else if (element.attachEvent){
+        element.detachEvent('on' + type, fn);
+    }
+};
+
+qq.preventDefault = function(e){
+    if (e.preventDefault){
+        e.preventDefault();
+    } else{
+        e.returnValue = false;
+    }
+};
+
+//
+// Node manipulations
+
+/**
+ * Insert node a before node b.
+ */
+qq.insertBefore = function(a, b){
+    b.parentNode.insertBefore(a, b);
+};
+qq.remove = function(element){
+    element.parentNode.removeChild(element);
+};
+
+qq.contains = function(parent, descendant){
+    // compareposition returns false in this case
+    if (parent == descendant) return true;
+
+    if (parent.contains){
+        return parent.contains(descendant);
+    } else {
+        return !!(descendant.compareDocumentPosition(parent) & 8);
+    }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+    var div = document.createElement('div');
+    return function(html){
+        div.innerHTML = html;
+        var element = div.firstChild;
+        div.removeChild(element);
+        return element;
+    };
+})();
+
+//
+// Node properties and attributes
+
+/**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+qq.css = function(element, styles){
+    if (styles.opacity != null){
+        if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
+            styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+        }
+    }
+    qq.extend(element.style, styles);
+};
+qq.hasClass = function(element, name){
+    var re = new RegExp('(^| )' + name + '( |$)');
+    return re.test(element.className);
+};
+qq.addClass = function(element, name){
+    if (!qq.hasClass(element, name)){
+        element.className += ' ' + name;
+    }
+};
+qq.removeClass = function(element, name){
+    var re = new RegExp('(^| )' + name + '( |$)');
+    element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+};
+qq.setText = function(element, text){
+    element.innerText = text;
+    element.textContent = text;
+};
+
+//
+// Selecting elements
+
+qq.children = function(element){
+    var children = [],
+    child = element.firstChild;
+
+    while (child){
+        if (child.nodeType == 1){
+            children.push(child);
+        }
+        child = child.nextSibling;
+    }
+
+    return children;
+};
+
+qq.getByClass = function(element, className){
+    if (element.querySelectorAll){
+        return element.querySelectorAll('.' + className);
+    }
+
+    var result = [];
+    var candidates = element.getElementsByTagName("*");
+    var len = candidates.length;
+
+    for (var i = 0; i < len; i++){
+        if (qq.hasClass(candidates[i], className)){
+            result.push(candidates[i]);
+        }
+    }
+    return result;
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ *    `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ *    `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param  Object JSON-Object
+ * @param  String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+    var uristrings = [],
+        prefix = '&',
+        add = function(nextObj, i){
+            var nextTemp = temp
+                ? (/\[\]$/.test(temp)) // prevent double-encoding
+                   ? temp
+                   : temp+'['+i+']'
+                : i;
+            if ((nextTemp != 'undefined') && (i != 'undefined')) {
+                uristrings.push(
+                    (typeof nextObj === 'object')
+                        ? qq.obj2url(nextObj, nextTemp, true)
+                        : (Object.prototype.toString.call(nextObj) === '[object Function]')
+                            ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+                            : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+                );
+            }
+        };
+
+    if (!prefixDone && temp) {
+      prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+      uristrings.push(temp);
+      uristrings.push(qq.obj2url(obj));
+    } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
+        // we wont use a for-in-loop on an array (performance)
+        for (var i = 0, len = obj.length; i < len; ++i){
+            add(obj[i], i);
+        }
+    } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
+        // for anything else but a scalar, we will use for-in-loop
+        for (var i in obj){
+            add(obj[i], i);
+        }
+    } else {
+        uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+    }
+
+    return uristrings.join(prefix)
+                     .replace(/^&/, '')
+                     .replace(/%20/g, '+');
+};
+
+//
+//
+// Uploader Classes
+//
+//
+
+var qq = qq || {};
+
+/**
+ * Creates upload button, validates upload, but doesn't create file list or dd.
+ */
+qq.FileUploaderBasic = function(o){
+    this._options = {
+        // set to true to see the server response
+        debug: false,
+        action: '/server/upload',
+        params: {},
+        button: null,
+        multiple: true,
+        maxConnections: 3,
+        // validation
+        allowedExtensions: [],
+        sizeLimit: 0,
+        minSizeLimit: 0,
+        // events
+        // return false to cancel submit
+        onSubmit: function(id, fileName){},
+        onProgress: function(id, fileName, loaded, total){},
+        onComplete: function(id, fileName, responseJSON){},
+        onCancel: function(id, fileName){},
+        // messages
+        messages: {
+            typeError: "{file} has invalid extension. Only {extensions} are allowed.",
+            sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+            minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+            emptyError: "{file} is empty, please select files again without it.",
+            onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+        },
+        showMessage: function(message){
+            alert(message);
+        }
+    };
+    qq.extend(this._options, o);
+
+    // number of files being uploaded
+    this._filesInProgress = 0;
+    this._handler = this._createUploadHandler();
+
+    if (this._options.button){
+        this._button = this._createUploadButton(this._options.button);
+    }
+
+    this._preventLeaveInProgress();
+};
+
+qq.FileUploaderBasic.prototype = {
+    setParams: function(params){
+        this._options.params = params;
+    },
+    getInProgress: function(){
+        return this._filesInProgress;
+    },
+    _createUploadButton: function(element){
+        var self = this;
+
+        return new qq.UploadButton({
+            element: element,
+            multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
+            onChange: function(input){
+                self._onInputChange(input);
+            }
+        });
+    },
+    _createUploadHandler: function(){
+        var self = this,
+            handlerClass;
+
+        if(qq.UploadHandlerXhr.isSupported()){
+            handlerClass = 'UploadHandlerXhr';
+        } else {
+            handlerClass = 'UploadHandlerForm';
+        }
+
+        var handler = new qq[handlerClass]({
+            debug: this._options.debug,
+            action: this._options.action,
+            maxConnections: this._options.maxConnections,
+            onProgress: function(id, fileName, loaded, total){
+                self._onProgress(id, fileName, loaded, total);
+                self._options.onProgress(id, fileName, loaded, total);
+            },
+            onComplete: function(id, fileName, result){
+                self._onComplete(id, fileName, result);
+                self._options.onComplete(id, fileName, result);
+            },
+            onCancel: function(id, fileName){
+                self._onCancel(id, fileName);
+                self._options.onCancel(id, fileName);
+            }
+        });
+
+        return handler;
+    },
+    _preventLeaveInProgress: function(){
+        var self = this;
+
+        qq.attach(window, 'beforeunload', function(e){
+            if (!self._filesInProgress){return;}
+
+            var e = e || window.event;
+            // for ie, ff
+            e.returnValue = self._options.messages.onLeave;
+            // for webkit
+            return self._options.messages.onLeave;
+        });
+    },
+    _onSubmit: function(id, fileName){
+        this._filesInProgress++;
+    },
+    _onProgress: function(id, fileName, loaded, total){
+    },
+    _onComplete: function(id, fileName, result){
+        this._filesInProgress--;
+        if (result.error){
+            this._options.showMessage(result.error);
+        }
+    },
+    _onCancel: function(id, fileName){
+        this._filesInProgress--;
+    },
+    _onInputChange: function(input){
+        if (this._handler instanceof qq.UploadHandlerXhr){
+            this._uploadFileList(input.files);
+        } else {
+            if (this._validateFile(input)){
+                this._uploadFile(input);
+            }
+        }
+        this._button.reset();
+    },
+    _uploadFileList: function(files){
+        for (var i=0; i<files.length; i++){
+            if ( !this._validateFile(files[i])){
+                return;
+            }
+        }
+
+        for (var i=0; i<files.length; i++){
+            this._uploadFile(files[i]);
+        }
+    },
+    _uploadFile: function(fileContainer){
+        var id = this._handler.add(fileContainer);
+        var fileName = this._handler.getName(id);
+
+        if (this._options.onSubmit(id, fileName) !== false){
+            this._onSubmit(id, fileName);
+            this._handler.upload(id, this._options.params);
+        }
+    },
+    _validateFile: function(file){
+        var name, size;
+
+        if (file.value){
+            // it is a file input
+            // get input value and remove path to normalize
+            name = file.value.replace(/.*(\/|\\)/, "");
+        } else {
+            // fix missing properties in Safari
+            name = file.fileName != null ? file.fileName : file.name;
+            size = file.fileSize != null ? file.fileSize : file.size;
+        }
+
+        if (! this._isAllowedExtension(name)){
+            this._error('typeError', name);
+            return false;
+
+        } else if (size === 0){
+            this._error('emptyError', name);
+            return false;
+
+        } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){
+            this._error('sizeError', name);
+            return false;
+
+        } else if (size && size < this._options.minSizeLimit){
+            this._error('minSizeError', name);
+            return false;
+        }
+
+        return true;
+    },
+    _error: function(code, fileName){
+        var message = this._options.messages[code];
+        function r(name, replacement){ message = message.replace(name, replacement); }
+
+        r('{file}', this._formatFileName(fileName));
+        r('{extensions}', this._options.allowedExtensions.join(', '));
+        r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
+        r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
+
+        this._options.showMessage(message);
+    },
+    _formatFileName: function(name){
+        if (name.length > 33){
+            name = name.slice(0, 19) + '...' + name.slice(-13);
+        }
+        return name;
+    },
+    _isAllowedExtension: function(fileName){
+        var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
+        var allowed = this._options.allowedExtensions;
+
+        if (!allowed.length){return true;}
+
+        for (var i=0; i<allowed.length; i++){
+            if (allowed[i].toLowerCase() == ext){ return true;}
+        }
+
+        return false;
+    },
+    _formatSize: function(bytes){
+        var i = -1;
+        do {
+            bytes = bytes / 1024;
+            i++;
+        } while (bytes > 99);
+
+        return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
+    }
+};
+
+
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FileUploaderBasic
+ */
+qq.FileUploader = function(o){
+    // call parent constructor
+    qq.FileUploaderBasic.apply(this, arguments);
+
+    // additional options
+    qq.extend(this._options, {
+        element: null,
+        // if set, will be used instead of qq-upload-list in template
+        listElement: null,
+
+        template: '<div class="qq-uploader">' +
+                '<div class="qq-upload-drop-area"><span>Drop files here to upload</span></div>' +
+                '<div class="qq-upload-button">Upload a file</div>' +
+                '<ul class="qq-upload-list"></ul>' +
+             '</div>',
+
+        // template for one item in file list
+        fileTemplate: '<li>' +
+                '<span class="qq-upload-file"></span>' +
+                '<span class="qq-upload-spinner"></span>' +
+                '<span class="qq-upload-size"></span>' +
+                '<a class="qq-upload-cancel" href="#">Cancel</a>' +
+                '<span class="qq-upload-failed-text">Failed</span>' +
+            '</li>',
+
+        classes: {
+            // used to get elements from templates
+            button: 'qq-upload-button',
+            drop: 'qq-upload-drop-area',
+            dropActive: 'qq-upload-drop-area-active',
+            list: 'qq-upload-list',
+
+            file: 'qq-upload-file',
+            spinner: 'qq-upload-spinner',
+            size: 'qq-upload-size',
+            cancel: 'qq-upload-cancel',
+
+            // added to list item when upload completes
+            // used in css to hide progress spinner
+            success: 'qq-upload-success',
+            fail: 'qq-upload-fail'
+        }
+    });
+    // overwrite options with user supplied
+    qq.extend(this._options, o);
+
+    this._element = this._options.element;
+    this._element.innerHTML = this._options.template;
+    this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+    this._classes = this._options.classes;
+
+    this._button = this._createUploadButton(this._find(this._element, 'button'));
+
+    this._bindCancelEvent();
+    this._setupDragDrop();
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
+
+qq.extend(qq.FileUploader.prototype, {
+    /**
+     * Gets one of the elements listed in this._options.classes
+     **/
+    _find: function(parent, type){
+        var element = qq.getByClass(parent, this._options.classes[type])[0];
+        if (!element){
+            throw new Error('element not found ' + type);
+        }
+
+        return element;
+    },
+    _setupDragDrop: function(){
+        var self = this,
+            dropArea = this._find(this._element, 'drop');
+
+        var dz = new qq.UploadDropZone({
+            element: dropArea,
+            onEnter: function(e){
+                qq.addClass(dropArea, self._classes.dropActive);
+                e.stopPropagation();
+            },
+            onLeave: function(e){
+                e.stopPropagation();
+            },
+            onLeaveNotDescendants: function(e){
+                qq.removeClass(dropArea, self._classes.dropActive);
+            },
+            onDrop: function(e){
+                dropArea.style.display = 'none';
+                qq.removeClass(dropArea, self._classes.dropActive);
+                self._uploadFileList(e.dataTransfer.files);
+            }
+        });
+
+        dropArea.style.display = 'none';
+
+        qq.attach(document, 'dragenter', function(e){
+            if (!dz._isValidFileDrag(e)) return;
+
+            dropArea.style.display = 'block';
+        });
+        qq.attach(document, 'dragleave', function(e){
+            if (!dz._isValidFileDrag(e)) return;
+
+            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+            // only fire when leaving document out
+            if ( ! relatedTarget || relatedTarget.nodeName == "HTML"){
+                dropArea.style.display = 'none';
+            }
+        });
+    },
+    _onSubmit: function(id, fileName){
+        qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
+        this._addToList(id, fileName);
+    },
+    _onProgress: function(id, fileName, loaded, total){
+        qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+        var item = this._getItemByFileId(id);
+        var size = this._find(item, 'size');
+        size.style.display = 'inline';
+
+        var text;
+        if (loaded != total){
+            text = Math.round(loaded / total * 100) + '% from ' + this._formatSize(total);
+        } else {
+            text = this._formatSize(total);
+        }
+
+        qq.setText(size, text);
+    },
+    _onComplete: function(id, fileName, result){
+        qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+        // mark completed
+        var item = this._getItemByFileId(id);
+        qq.remove(this._find(item, 'cancel'));
+        qq.remove(this._find(item, 'spinner'));
+
+        if (result.success){
+            qq.addClass(item, this._classes.success);
+        } else {
+            qq.addClass(item, this._classes.fail);
+        }
+    },
+    _addToList: function(id, fileName){
+        var item = qq.toElement(this._options.fileTemplate);
+        item.qqFileId = id;
+
+        var fileElement = this._find(item, 'file');
+        qq.setText(fileElement, this._formatFileName(fileName));
+        this._find(item, 'size').style.display = 'none';
+
+        this._listElement.appendChild(item);
+    },
+    _getItemByFileId: function(id){
+        var item = this._listElement.firstChild;
+
+        // there can't be txt nodes in dynamically created list
+        // and we can  use nextSibling
+        while (item){
+            if (item.qqFileId == id) return item;
+            item = item.nextSibling;
+        }
+    },
+    /**
+     * delegate click event for cancel link
+     **/
+    _bindCancelEvent: function(){
+        var self = this,
+            list = this._listElement;
+
+        qq.attach(list, 'click', function(e){
+            e = e || window.event;
+            var target = e.target || e.srcElement;
+
+            if (qq.hasClass(target, self._classes.cancel)){
+                qq.preventDefault(e);
+
+                var item = target.parentNode;
+                self._handler.cancel(item.qqFileId);
+                qq.remove(item);
+            }
+        });
+    }
+});
+
+qq.UploadDropZone = function(o){
+    this._options = {
+        element: null,
+        onEnter: function(e){},
+        onLeave: function(e){},
+        // is not fired when leaving element by hovering descendants
+        onLeaveNotDescendants: function(e){},
+        onDrop: function(e){}
+    };
+    qq.extend(this._options, o);
+
+    this._element = this._options.element;
+
+    this._disableDropOutside();
+    this._attachEvents();
+};
+
+qq.UploadDropZone.prototype = {
+    _disableDropOutside: function(e){
+        // run only once for all instances
+        if (!qq.UploadDropZone.dropOutsideDisabled ){
+
+            qq.attach(document, 'dragover', function(e){
+                if (e.dataTransfer){
+                    e.dataTransfer.dropEffect = 'none';
+                    e.preventDefault();
+                }
+            });
+
+            qq.UploadDropZone.dropOutsideDisabled = true;
+        }
+    },
+    _attachEvents: function(){
+        var self = this;
+
+        qq.attach(self._element, 'dragover', function(e){
+            if (!self._isValidFileDrag(e)) return;
+
+            var effect = e.dataTransfer.effectAllowed;
+            if (effect == 'move' || effect == 'linkMove'){
+                e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+            } else {
+                e.dataTransfer.dropEffect = 'copy'; // for Chrome
+            }
+
+            e.stopPropagation();
+            e.preventDefault();
+        });
+
+        qq.attach(self._element, 'dragenter', function(e){
+            if (!self._isValidFileDrag(e)) return;
+
+            self._options.onEnter(e);
+        });
+
+        qq.attach(self._element, 'dragleave', function(e){
+            if (!self._isValidFileDrag(e)) return;
+
+            self._options.onLeave(e);
+
+            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+            // do not fire when moving a mouse over a descendant
+            if (qq.contains(this, relatedTarget)) return;
+
+            self._options.onLeaveNotDescendants(e);
+        });
+
+        qq.attach(self._element, 'drop', function(e){
+            if (!self._isValidFileDrag(e)) return;
+
+            e.preventDefault();
+            self._options.onDrop(e);
+        });
+    },
+    _isValidFileDrag: function(e){
+        var dt = e.dataTransfer,
+            // do not check dt.types.contains in webkit, because it crashes safari 4
+            isWebkit = navigator.userAgent.indexOf("AppleWebKit") > -1;
+
+        // dt.effectAllowed is none in Safari 5
+        // dt.types.contains check is for firefox
+        return dt && dt.effectAllowed != 'none' &&
+            (dt.files || (!isWebkit && dt.types.contains && dt.types.contains('Files')));
+
+    }
+};
+
+qq.UploadButton = function(o){
+    this._options = {
+        element: null,
+        // if set to true adds multiple attribute to file input
+        multiple: false,
+        // name attribute of file input
+        name: 'file',
+        onChange: function(input){},
+        hoverClass: 'qq-upload-button-hover',
+        focusClass: 'qq-upload-button-focus'
+    };
+
+    qq.extend(this._options, o);
+
+    this._element = this._options.element;
+
+    // make button suitable container for input
+    qq.css(this._element, {
+        position: 'relative',
+        overflow: 'hidden',
+        // Make sure browse button is in the right side
+        // in Internet Explorer
+        direction: 'ltr'
+    });
+
+    this._input = this._createInput();
+};
+
+qq.UploadButton.prototype = {
+    /* returns file input element */
+    getInput: function(){
+        return this._input;
+    },
+    /* cleans/recreates the file input */
+    reset: function(){
+        if (this._input.parentNode){
+            qq.remove(this._input);
+        }
+
+        qq.removeClass(this._element, this._options.focusClass);
+        this._input = this._createInput();
+    },
+    _createInput: function(){
+        var input = document.createElement("input");
+
+        if (this._options.multiple){
+            input.setAttribute("multiple", "multiple");
+        }
+
+        input.setAttribute("type", "file");
+        input.setAttribute("name", this._options.name);
+
+        qq.css(input, {
+            position: 'absolute',
+            // in Opera only 'browse' button
+            // is clickable and it is located at
+            // the right side of the input
+            right: 0,
+            top: 0,
+            fontFamily: 'Arial',
+            // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+            fontSize: '118px',
+            margin: 0,
+            padding: 0,
+            cursor: 'pointer',
+            opacity: 0
+        });
+
+        this._element.appendChild(input);
+
+        var self = this;
+        qq.attach(input, 'change', function(){
+            self._options.onChange(input);
+        });
+
+        qq.attach(input, 'mouseover', function(){
+            qq.addClass(self._element, self._options.hoverClass);
+        });
+        qq.attach(input, 'mouseout', function(){
+            qq.removeClass(self._element, self._options.hoverClass);
+        });
+        qq.attach(input, 'focus', function(){
+            qq.addClass(self._element, self._options.focusClass);
+        });
+        qq.attach(input, 'blur', function(){
+            qq.removeClass(self._element, self._options.focusClass);
+        });
+
+        // IE and Opera, unfortunately have 2 tab stops on file input
+        // which is unacceptable in our case, disable keyboard access
+        if (window.attachEvent){
+            // it is IE or Opera
+            input.setAttribute('tabIndex', "-1");
+        }
+
+        return input;
+    }
+};
+
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+qq.UploadHandlerAbstract = function(o){
+    this._options = {
+        debug: false,
+        action: '/upload.php',
+        // maximum number of concurrent uploads
+        maxConnections: 999,
+        onProgress: function(id, fileName, loaded, total){},
+        onComplete: function(id, fileName, response){},
+        onCancel: function(id, fileName){}
+    };
+    qq.extend(this._options, o);
+
+    this._queue = [];
+    // params for files in queue
+    this._params = [];
+};
+qq.UploadHandlerAbstract.prototype = {
+    log: function(str){
+        if (this._options.debug && window.console) console.log('[uploader] ' + str);
+    },
+    /**
+     * Adds file or file input to the queue
+     * @returns id
+     **/
+    add: function(file){},
+    /**
+     * Sends the file identified by id and additional query params to the server
+     */
+    upload: function(id, params){
+        var len = this._queue.push(id);
+
+        var copy = {};
+        qq.extend(copy, params);
+        this._params[id] = copy;
+
+        // if too many active uploads, wait...
+        if (len <= this._options.maxConnections){
+            this._upload(id, this._params[id]);
+        }
+    },
+    /**
+     * Cancels file upload by id
+     */
+    cancel: function(id){
+        this._cancel(id);
+        this._dequeue(id);
+    },
+    /**
+     * Cancells all uploads
+     */
+    cancelAll: function(){
+        for (var i=0; i<this._queue.length; i++){
+            this._cancel(this._queue[i]);
+        }
+        this._queue = [];
+    },
+    /**
+     * Returns name of the file identified by id
+     */
+    getName: function(id){},
+    /**
+     * Returns size of the file identified by id
+     */
+    getSize: function(id){},
+    /**
+     * Returns id of files being uploaded or
+     * waiting for their turn
+     */
+    getQueue: function(){
+        return this._queue;
+    },
+    /**
+     * Actual upload method
+     */
+    _upload: function(id){},
+    /**
+     * Actual cancel method
+     */
+    _cancel: function(id){},
+    /**
+     * Removes element from queue, starts upload of next
+     */
+    _dequeue: function(id){
+        var i = qq.indexOf(this._queue, id);
+        this._queue.splice(i, 1);
+
+        var max = this._options.maxConnections;
+
+        if (this._queue.length >= max && i < max){
+            var nextId = this._queue[max-1];
+            this._upload(nextId, this._params[nextId]);
+        }
+    }
+};
+
+/**
+ * Class for uploading files using form and iframe
+ * @inherits qq.UploadHandlerAbstract
+ */
+qq.UploadHandlerForm = function(o){
+    qq.UploadHandlerAbstract.apply(this, arguments);
+
+    this._inputs = {};
+};
+// @inherits qq.UploadHandlerAbstract
+qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
+
+qq.extend(qq.UploadHandlerForm.prototype, {
+    add: function(fileInput){
+        fileInput.setAttribute('name', 'qqfile');
+        var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
+
+        this._inputs[id] = fileInput;
+
+        // remove file input from DOM
+        if (fileInput.parentNode){
+            qq.remove(fileInput);
+        }
+
+        return id;
+    },
+    getName: function(id){
+        // get input value and remove path to normalize
+        return this._inputs[id].value.replace(/.*(\/|\\)/, "");
+    },
+    _cancel: function(id){
+        this._options.onCancel(id, this.getName(id));
+
+        delete this._inputs[id];
+
+        var iframe = document.getElementById(id);
+        if (iframe){
+            // to cancel request set src to something else
+            // we use src="javascript:false;" because it doesn't
+            // trigger ie6 prompt on https
+            iframe.setAttribute('src', 'javascript:false;');
+
+            qq.remove(iframe);
+        }
+    },
+    _upload: function(id, params){
+        var input = this._inputs[id];
+
+        if (!input){
+            throw new Error('file with passed id was not added, or already uploaded or cancelled');
+        }
+
+        var fileName = this.getName(id);
+
+        var iframe = this._createIframe(id);
+        var form = this._createForm(iframe, params);
+        form.appendChild(input);
+
+        var self = this;
+        this._attachLoadEvent(iframe, function(){
+            self.log('iframe loaded');
+
+            var response = self._getIframeContentJSON(iframe);
+
+            self._options.onComplete(id, fileName, response);
+            self._dequeue(id);
+
+            delete self._inputs[id];
+            // timeout added to fix busy state in FF3.6
+            setTimeout(function(){
+                qq.remove(iframe);
+            }, 1);
+        });
+
+        form.submit();
+        qq.remove(form);
+
+        return id;
+    },
+    _attachLoadEvent: function(iframe, callback){
+        qq.attach(iframe, 'load', function(){
+            // when we remove iframe from dom
+            // the request stops, but in IE load
+            // event fires
+            if (!iframe.parentNode){
+                return;
+            }
+
+            // fixing Opera 10.53
+            if (iframe.contentDocument &&
+                iframe.contentDocument.body &&
+                iframe.contentDocument.body.innerHTML == "false"){
+                // In Opera event is fired second time
+                // when body.innerHTML changed from false
+                // to server response approx. after 1 sec
+                // when we upload file with iframe
+                return;
+            }
+
+            callback();
+        });
+    },
+    /**
+     * Returns json object received by iframe from server.
+     */
+    _getIframeContentJSON: function(iframe){
+        // iframe.contentWindow.document - for IE<7
+        var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
+            response;
+
+        this.log("converting iframe's innerHTML to JSON");
+        this.log("innerHTML = " + doc.body.innerHTML);
+
+        try {
+            response = eval("(" + doc.body.innerHTML + ")");
+        } catch(err){
+            response = {};
+        }
+
+        return response;
+    },
+    /**
+     * Creates iframe with unique name
+     */
+    _createIframe: function(id){
+        // We can't use following code as the name attribute
+        // won't be properly registered in IE6, and new window
+        // on form submit will open
+        // var iframe = document.createElement('iframe');
+        // iframe.setAttribute('name', id);
+
+        var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
+        // src="javascript:false;" removes ie6 prompt on https
+
+        iframe.setAttribute('id', id);
+
+        iframe.style.display = 'none';
+        document.body.appendChild(iframe);
+
+        return iframe;
+    },
+    /**
+     * Creates form, that will be submitted to iframe
+     */
+    _createForm: function(iframe, params){
+        // We can't use the following code in IE6
+        // var form = document.createElement('form');
+        // form.setAttribute('method', 'post');
+        // form.setAttribute('enctype', 'multipart/form-data');
+        // Because in this case file won't be attached to request
+        var form = qq.toElement('<form method="post" enctype="multipart/form-data"></form>');
+
+        var queryString = qq.obj2url(params, this._options.action);
+
+        form.setAttribute('action', queryString);
+        form.setAttribute('target', iframe.name);
+        form.style.display = 'none';
+        document.body.appendChild(form);
+
+        return form;
+    }
+});
+
+/**
+ * Class for uploading files using xhr
+ * @inherits qq.UploadHandlerAbstract
+ */
+qq.UploadHandlerXhr = function(o){
+    qq.UploadHandlerAbstract.apply(this, arguments);
+
+    this._files = [];
+    this._xhrs = [];
+
+    // current loaded size in bytes for each file
+    this._loaded = [];
+};
+
+// static method
+qq.UploadHandlerXhr.isSupported = function(){
+    var input = document.createElement('input');
+    input.type = 'file';
+
+    return (
+        'multiple' in input &&
+        typeof File != "undefined" &&
+        typeof (new XMLHttpRequest()).upload != "undefined" );
+};
+
+// @inherits qq.UploadHandlerAbstract
+qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype);
+
+qq.extend(qq.UploadHandlerXhr.prototype, {
+    /**
+     * Adds file to the queue
+     * Returns id to use with upload, cancel
+     **/
+    add: function(file){
+        if (!(file instanceof File)){
+            throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
+        }
+
+        return this._files.push(file) - 1;
+    },
+    getName: function(id){
+        var file = this._files[id];
+        // fix missing name in Safari 4
+        return file.fileName != null ? file.fileName : file.name;
+    },
+    getSize: function(id){
+        var file = this._files[id];
+        return file.fileSize != null ? file.fileSize : file.size;
+    },
+    /**
+     * Returns uploaded bytes for file identified by id
+     */
+    getLoaded: function(id){
+        return this._loaded[id] || 0;
+    },
+    /**
+     * Sends the file identified by id and additional query params to the server
+     * @param {Object} params name-value string pairs
+     */
+    _upload: function(id, params){
+        var file = this._files[id],
+            name = this.getName(id),
+            size = this.getSize(id);
+
+        this._loaded[id] = 0;
+
+        var xhr = this._xhrs[id] = new XMLHttpRequest();
+        var self = this;
+
+        xhr.upload.onprogress = function(e){
+            if (e.lengthComputable){
+                self._loaded[id] = e.loaded;
+                self._options.onProgress(id, name, e.loaded, e.total);
+            }
+        };
+
+        xhr.onreadystatechange = function(){
+            if (xhr.readyState == 4){
+                self._onComplete(id, xhr);
+            }
+        };
+
+        // build query string
+        params = params || {};
+        params['qqfile'] = name;
+        var queryString = qq.obj2url(params, this._options.action);
+
+        xhr.open("POST", queryString, true);
+        xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+        xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
+        xhr.setRequestHeader("Content-Type", "application/octet-stream");
+        xhr.send(file);
+    },
+    _onComplete: function(id, xhr){
+        // the request was aborted/cancelled
+        if (!this._files[id]) return;
+
+        var name = this.getName(id);
+        var size = this.getSize(id);
+
+        this._options.onProgress(id, name, size, size);
+
+        if (xhr.status == 200){
+            this.log("xhr - server response received");
+            this.log("responseText = " + xhr.responseText);
+
+            var response;
+
+            try {
+                response = eval("(" + xhr.responseText + ")");
+            } catch(err){
+                response = {};
+            }
+
+            this._options.onComplete(id, name, response);
+
+        } else {
+            this._options.onComplete(id, name, {});
+        }
+
+        this._files[id] = null;
+        this._xhrs[id] = null;
+        this._dequeue(id);
+    },
+    _cancel: function(id){
+        this._options.onCancel(id, this.getName(id));
+
+        this._files[id] = null;
+
+        if (this._xhrs[id]){
+            this._xhrs[id].abort();
+            this._xhrs[id] = null;
+        }
+    }
+});
\ No newline at end of file
diff --git a/lib/scripts/fileuploaderextended.js b/lib/scripts/fileuploaderextended.js
new file mode 100644
index 0000000000000000000000000000000000000000..ed631a9eaa04328fc8be1b3a446e6830546e6b75
--- /dev/null
+++ b/lib/scripts/fileuploaderextended.js
@@ -0,0 +1,339 @@
+qq.extend(qq.FileUploader.prototype, {
+    _createUploadHandler: function(){
+        var self = this,
+            handlerClass;
+
+        if(qq.UploadHandlerXhr.isSupported()){
+            handlerClass = 'UploadHandlerXhr';
+            //handlerClass = 'UploadHandlerForm';
+        } else {
+            handlerClass = 'UploadHandlerForm';
+        }
+
+        var handler = new qq[handlerClass]({
+            debug: this._options.debug,
+            action: this._options.action,
+            maxConnections: this._options.maxConnections,
+            onProgress: function(id, fileName, loaded, total){
+                self._onProgress(id, fileName, loaded, total);
+                self._options.onProgress(id, fileName, loaded, total);
+            },
+            onComplete: function(id, fileName, result){
+                self._onComplete(id, fileName, result);
+                self._options.onComplete(id, fileName, result);
+            },
+            onCancel: function(id, fileName){
+                self._onCancel(id, fileName);
+                self._options.onCancel(id, fileName);
+            },
+            onUpload: function(){
+                self._onUpload();
+            }
+        });
+
+        return handler;
+    },
+
+    _onUpload: function(){
+        this._handler.uploadAll(this._options.params);
+    },
+
+    _uploadFile: function(fileContainer){
+        var id = this._handler.add(fileContainer);
+        var fileName = this._handler.getName(id);
+
+        if (this._options.onSubmit(id, fileName) !== false){
+            this._onSubmit(id, fileName);
+        }
+    },
+
+    _addToList: function(id, fileName){
+        var item = qq.toElement(this._options.fileTemplate);
+        item.qqFileId = id;
+
+        var fileElement = this._find(item, 'file');
+        qq.setText(fileElement, fileName);
+        this._find(item, 'size').style.display = 'none';
+
+        var nameElement = this._find(item, 'nameInput');
+        fileName = fileName.toLowerCase();
+        fileName = fileName.replace(/([^a-z0-9_\.\-]+)/g, '_');
+        nameElement.value = fileName;
+        nameElement.id = 'mediamanager__upload_item'+id;
+
+        this._listElement.appendChild(item);
+    }
+
+});
+
+qq.FileUploaderExtended = function(o){
+    // call parent constructor
+    qq.FileUploaderBasic.apply(this, arguments);
+
+    qq.extend(this._options, {
+        element: null,
+        // if set, will be used instead of qq-upload-list in template
+        listElement: null,
+
+        template: '<div class="qq-uploader">' +
+                '<div class="qq-upload-drop-area"><span>' + LANG.media_drop + '</span></div>' +
+                '<div class="qq-upload-button">' + LANG.media_select + '</div>' +
+                '<div class="qq-upload-list"></div>' +
+                '<div><input class="button" type="submit" value="' + LANG.media_upload_btn + '" id="mediamanager__upload_button">' +
+                '<label class="check"><input class="dw__ow" type="checkbox" value="1" name="ow"><span>' + LANG.media_overwrt + '</span></label>' +
+                '</div>' +
+             '</div>',
+
+        // template for one item in file list
+        fileTemplate: '<div class="li">' +
+                '<span class="qq-upload-file qq-upload-file-hidden"></span>' +
+                '<input class="qq-upload-name-input edit" type="text">' +
+                '<span class="qq-upload-spinner-hidden"></span>' +
+                '<span class="qq-upload-size"></span>' +
+                '<a class="qq-upload-cancel" href="#">' + LANG.media_cancel + '</a>' +
+                '<span class="qq-upload-failed-text">Failed</span>' +
+            '</div>',
+
+        classes: {
+            // used to get elements from templates
+            button: 'qq-upload-button',
+            drop: 'qq-upload-drop-area',
+            dropActive: 'qq-upload-drop-area-active',
+            list: 'qq-upload-list',
+            nameInput: 'qq-upload-name-input',
+            file: 'qq-upload-file',
+
+            spinner: 'qq-upload-spinner',
+            size: 'qq-upload-size',
+            cancel: 'qq-upload-cancel',
+
+            // added to list item when upload completes
+            // used in css to hide progress spinner
+            success: 'qq-upload-success',
+            fail: 'qq-upload-fail',
+            failedText : 'qq-upload-failed-text'
+        }
+    });
+
+    qq.extend(this._options, o);
+
+    this._element = this._options.element;
+    this._element.innerHTML = this._options.template;
+    this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+    this._classes = this._options.classes;
+
+    this._button = this._createUploadButton(this._find(this._element, 'button'));
+
+    this._bindCancelEvent();
+    this._bindUploadEvent();
+    this._setupDragDrop();
+};
+
+qq.extend(qq.FileUploaderExtended.prototype, qq.FileUploader.prototype);
+
+qq.extend(qq.FileUploaderExtended.prototype, {
+    _bindUploadEvent: function(){
+        var self = this,
+            list = this._listElement;
+
+        qq.attach(document.getElementById('mediamanager__upload_button'), 'click', function(e){
+            e = e || window.event;
+            var target = e.target || e.srcElement;
+            qq.preventDefault(e);
+            self._handler._options.onUpload();
+
+            jQuery(".qq-upload-name-input").each(function (i) {
+                jQuery(this).attr('disabled', 'disabled');
+            });
+        });
+    },
+
+    _onComplete: function(id, fileName, result){
+        this._filesInProgress--;
+
+        // mark completed
+        var item = this._getItemByFileId(id);
+        qq.remove(this._find(item, 'cancel'));
+        qq.remove(this._find(item, 'spinner'));
+
+        var nameInput = this._find(item, 'nameInput');
+        var fileElement = this._find(item, 'file');
+        qq.setText(fileElement, nameInput.value);
+        qq.removeClass(fileElement, 'qq-upload-file-hidden');
+        qq.remove(nameInput);
+        jQuery('.qq-upload-button, #mediamanager__upload_button').remove();
+        jQuery('.dw__ow').parent().hide();
+        jQuery('.qq-upload-drop-area').remove();
+
+        if (result.success){
+            qq.addClass(item, this._classes.success);
+            $link = '<a href="' + result.link + '" name="h_:' + result.id + '" class="select">' + nameInput.value + '</a>';
+            jQuery(fileElement).html($link);
+
+        } else {
+            qq.addClass(item, this._classes.fail);
+            var fail = this._find(item, 'failedText');
+            if (result.error) qq.setText(fail, result.error);
+        }
+
+        if (document.getElementById('media__content') && !document.getElementById('mediamanager__done_form')) {
+            var action = document.location.href;
+            var i = action.indexOf('?');
+            if (i) action = action.substr(0, i);
+            var button = '<form method="post" action="' + action + '" id="mediamanager__done_form"><div>';
+            button += '<input type="hidden" value="' + result.ns + '" name="ns">';
+            button += '<input class="button" type="submit" value="' + LANG.media_done_btn + '"></div></form>';
+            jQuery('#mediamanager__uploader').append(button);
+        }
+    }
+
+});
+
+qq.extend(qq.UploadHandlerForm.prototype, {
+    uploadAll: function(params){
+        this._uploadAll(params);
+    },
+
+    getName: function(id){
+        var file = this._inputs[id];
+        var name = document.getElementById('mediamanager__upload_item'+id);
+        if (name != null) {
+            return name.value;
+        } else {
+            if (file != null) {
+                // get input value and remove path to normalize
+                return file.value.replace(/.*(\/|\\)/, "");
+            } else {
+                return null;
+            }
+        }
+    },
+
+    _uploadAll: function(params){
+         jQuery(".qq-upload-spinner-hidden").each(function (i) {
+            jQuery(this).addClass('qq-upload-spinner');
+        });
+        for (key in this._inputs) {
+            this.upload(key, params);
+        }
+
+    },
+
+    _upload: function(id, params){
+        var input = this._inputs[id];
+
+        if (!input){
+            throw new Error('file with passed id was not added, or already uploaded or cancelled');
+        }
+
+        var fileName = this.getName(id);
+
+        var iframe = this._createIframe(id);
+        var form = this._createForm(iframe, params);
+        form.appendChild(input);
+
+        var nameInput = qq.toElement('<input name="mediaid" value="' + fileName + '" type="text">');
+        form.appendChild(nameInput);
+
+	    var checked = jQuery('.dw__ow').attr('checked');
+        var owCheckbox = jQuery('.dw__ow').clone();
+    	owCheckbox.attr('checked', checked);
+        jQuery(form).append(owCheckbox);
+
+        var self = this;
+        this._attachLoadEvent(iframe, function(){
+            self.log('iframe loaded');
+
+            var response = self._getIframeContentJSON(iframe);
+
+            self._options.onComplete(id, fileName, response);
+            self._dequeue(id);
+
+            delete self._inputs[id];
+            // timeout added to fix busy state in FF3.6
+            setTimeout(function(){
+                qq.remove(iframe);
+            }, 1);
+        });
+
+        form.submit();
+        qq.remove(form);
+
+        return id;
+    }
+});
+
+qq.extend(qq.UploadHandlerXhr.prototype, {
+    uploadAll: function(params){
+        this._uploadAll(params);
+    },
+
+    getName: function(id){
+        var file = this._files[id];
+        var name = document.getElementById('mediamanager__upload_item'+id);
+        if (name != null) {
+            return name.value;
+        } else {
+            if (file != null) {
+                // fix missing name in Safari 4
+                return file.fileName != null ? file.fileName : file.name;
+            } else {
+                return null;
+            }
+        }
+    },
+
+    getSize: function(id){
+        var file = this._files[id];
+        if (file == null) return null;
+        return file.fileSize != null ? file.fileSize : file.size;
+    },
+
+    _upload: function(id, params){
+        var file = this._files[id],
+            name = this.getName(id),
+            size = this.getSize(id);
+        if (name == null || size == null) return;
+
+        this._loaded[id] = 0;
+
+        var xhr = this._xhrs[id] = new XMLHttpRequest();
+        var self = this;
+
+        xhr.upload.onprogress = function(e){
+            if (e.lengthComputable){
+                self._loaded[id] = e.loaded;
+                self._options.onProgress(id, name, e.loaded, e.total);
+            }
+        };
+
+        xhr.onreadystatechange = function(){
+            if (xhr.readyState == 4){
+                self._onComplete(id, xhr);
+            }
+        };
+
+        // build query string
+        params = params || {};
+        params['qqfile'] = name;
+        params['ow'] = jQuery('.dw__ow').attr('checked');
+        var queryString = qq.obj2url(params, this._options.action);
+
+        xhr.open("POST", queryString, true);
+        xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+        xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
+        xhr.setRequestHeader("Content-Type", "application/octet-stream");
+        xhr.send(file);
+    },
+
+    _uploadAll: function(params){
+        jQuery(".qq-upload-spinner-hidden").each(function (i) {
+            jQuery(this).addClass('qq-upload-spinner');
+        });
+        for (key in this._files) {
+            this.upload(key, params);
+        }
+
+    }
+});
diff --git a/lib/scripts/media.js b/lib/scripts/media.js
index 1402ad4bf569e7217de0f840bda5555d30faed55..f4064efd5a77edcc3789d04013924c4d3cc96930 100644
--- a/lib/scripts/media.js
+++ b/lib/scripts/media.js
@@ -22,6 +22,12 @@ var dw_mediamanager = {
     size: false,
     forbidden_opts: {},
 
+    // File list view type
+    view: false,
+
+    layout_width: 0,
+    layout_height: 0,
+
     init: function () {
         var $content, $tree;
         $content = jQuery('#media__content');
@@ -38,7 +44,8 @@ var dw_mediamanager = {
                 .delegate('a.select', 'click', dw_mediamanager.select)
                 // Attach deletion confirmation dialog to the delete buttons
                 .delegate('#media__content a.btn_media_delete', 'click',
-                          dw_mediamanager.confirmattach);
+                          dw_mediamanager.confirmattach)
+                .delegate('#mediamanager__done_form', 'submit', dw_mediamanager.list);
 
         $tree.dw_tree({toggle_selector: 'img',
                        load_data: function (show_sublist, $clicky) {
@@ -59,6 +66,43 @@ var dw_mediamanager = {
                                         (opening ? 'minus' : 'plus') + '.gif');
                        }});
         $tree.delegate('a', 'click', dw_mediamanager.list);
+
+        dw_mediamanager.set_filelist_view(dw_mediamanager.view, false);
+        jQuery('#mediamanager__form_sort').find('input[type=submit]').hide();
+        dw_mediamanager.image_diff();
+        dw_mediamanager.init_ajax_uploader();
+
+        // changing opened tab in the file list panel
+        jQuery('#mediamanager__layout_list').delegate('#mediamanager__tabs_files a', 'click', dw_mediamanager.list)
+            // changing type of the file list view
+            .delegate('#mediamanager__tabs_list a', 'click', dw_mediamanager.list_view)
+            // loading file details
+            .delegate('#mediamanager__file_list a', 'click', dw_mediamanager.details)
+            // search form
+            .delegate('#dw__mediasearch', 'submit', dw_mediamanager.list)
+            // "upload as" field autofill
+            .delegate('#upload__file', 'change', dw_mediamanager.suggest)
+            // sort type selection
+            .delegate('#mediamanager__form_sort select', 'change', dw_mediamanager.list)
+            // uploaded images
+            .delegate('.qq-upload-file a', 'click', dw_mediamanager.details);
+
+        // changing opened tab in the file details panel
+        jQuery('#mediamanager__layout_detail').delegate('#mediamanager__tabs_details a', 'click', dw_mediamanager.details)
+            // "update new version" button
+            .delegate('#mediamanager__btn_update', 'submit', dw_mediamanager.list)
+            // revisions form
+            .delegate('#page__revisions', 'submit', dw_mediamanager.details)
+            .delegate('#page__revisions a', 'click', dw_mediamanager.details)
+            // meta edit form
+            .delegate('#mediamanager__save_meta', 'submit', dw_mediamanager.details)
+            // delete button
+            .delegate('#mediamanager__btn_delete', 'submit', dw_mediamanager.details)
+            // "restore this version" button
+            .delegate('#mediamanager__btn_restore', 'submit', dw_mediamanager.details)
+            // less/more recent buttons in media revisions form
+            .delegate('.btn_newer, .btn_older', 'submit', dw_mediamanager.details);
+
     },
 
     /**
@@ -201,6 +245,9 @@ var dw_mediamanager = {
 
         $file = jQuery(this);
         $name = jQuery('#upload__name');
+
+        if ($name.val() != '') return;
+
         if(!$file.length || !$name.length) {
             return;
         }
@@ -218,33 +265,431 @@ var dw_mediamanager = {
      * @author Pierre Spring <pierre.spring@caillou.ch>
      */
     list: function (event) {
-        var $link, $content;
+        var $link, $content, params;
+        $link = jQuery(this);
 
         event.preventDefault();
 
         jQuery('div.success, div.info, div.error, div.notify').remove();
 
-        $link = jQuery(this);
-        $content = jQuery('#media__content');
-        $content.html('<img src="' + DOKU_BASE + 'lib/images/loading.gif" alt="..." class="load" />');
+        if (document.getElementById('media__content')) {
+            //popup
+            $content = jQuery('#media__content');
+        } else {
+            //fullscreen media manager
+            $content = jQuery('#mediamanager__layout_list');
+
+            if ($link.hasClass('idx_dir')) {
+                //changing namespace
+                jQuery('#mediamanager__layout_detail').empty();
+                jQuery('#media__tree .selected').each(function(){
+                    jQuery(this).removeClass('selected');
+                });
+                $link.addClass('selected');
+            }
+        }
+
+        params = '';
+
+        if ($link[0].search) {
+            params = $link[0].search.substr(1)+'&call=medialist';
+        } else if ($link[0].action) {
+            params = dw_mediamanager.form_params($link)+'&call=medialist';
+        } else if ($link.parents('form')) {
+            params = dw_mediamanager.form_params($link.parents('form'))+'&call=medialist';
+
+            if ($link.parents('form')[0].id == 'mediamanager__form_sort') {
+                DokuCookie.setValue('sort', $link[0].value);
+                params += '&q=' + jQuery('#mediamanager__sort_textfield').val();
+                params += '&mediado=searchlist';
+            }
+        }
 
         // fetch the subtree
+        dw_mediamanager.update_content($content, params);
+
+        if (document.getElementById('media__content')) {
+            //popup
+            $content = jQuery('#media__content');
+            $content.html('<img src="' + DOKU_BASE + 'lib/images/loading.gif" alt="..." class="load" />');
+        } else {
+            //fullscreen media manager
+            jQuery('.scroll-container', $content).html('<img src="' + DOKU_BASE + 'lib/images/loading.gif" alt="..." class="load" />');
+        }
+    },
+
+     /**
+     * Returns form parameters
+     *
+     * @author Kate Arzamastseva <pshns@ukr.net>
+     */
+    form_params: function ($form) {
+        if (!$form.length) return;
+        var elements = $form.serialize();
+        var action = '';
+        var i = $form[0].action.indexOf('?');
+        if (i >= 0) action = $form[0].action.substr(i+1);
+        return elements+'&'+action;
+    },
+
+     /**
+     * Changes view of media files list
+     *
+     * @author Kate Arzamastseva <pshns@ukr.net>
+     */
+    list_view: function (event) {
+        var $link, $content;
+        $link = jQuery(this);
+
+        event.preventDefault();
+
+        $content = jQuery('#mediamanager__file_list');
+
+        if ($link[0].id == 'mediamanager__link_thumbs') {
+            dw_mediamanager.set_filelist_view('thumbs', true);
+
+        } else if ($link[0].id == 'mediamanager__link_list') {
+            dw_mediamanager.set_filelist_view('list', true);
+        }
+    },
+
+    set_filelist_view: function (type, cookies) {
+        var $content = jQuery('#mediamanager__file_list');
+        if (!type) type = DokuCookie.getValue('view');
+
+        if (type == 'thumbs') {
+            $content.removeClass('mediamanager-list');
+            $content.addClass('mediamanager-thumbs');
+            if (cookies) DokuCookie.setValue('view', 'thumbs');
+            dw_mediamanager.view = 'thumbs';
+
+        } else if (type == 'list') {
+            $content.removeClass('mediamanager-thumbs');
+            $content.addClass('mediamanager-list');
+            if (cookies) DokuCookie.setValue('view', 'list');
+            dw_mediamanager.view = 'list';
+        }
+    },
+
+     /**
+     * Lists the content of the right column (image details) using AJAX
+     *
+     * @author Kate Arzamastseva <pshns@ukr.net>
+     */
+    details: function (event) {
+        var $link, $content, params, update_list;
+        $link = jQuery(this);
+
+        event.preventDefault();
+
+        jQuery('div.success, div.info, div.error, div.notify').remove();
+
+        if ($link[0].id == 'mediamanager__btn_delete' && !confirm(LANG['del_confirm'])) return false;
+        if ($link[0].id == 'mediamanager__btn_restore' && !confirm(LANG['restore_confirm'])) return false;
+
+        $content = jQuery('#mediamanager__layout_detail');
+        params = '';
+
+        if ($link[0].search) {
+            params = $link[0].search.substr(1)+'&call=mediadetails';
+        } else if ($link[0].action) {
+            params = dw_mediamanager.form_params($link)+'&call=mediadetails';
+        } else if ($link.parents('form')) {
+            params = dw_mediamanager.form_params($link.parents('form'))+'&call=mediadetails';
+        }
+
+        update_list = ($link[0].id == 'mediamanager__btn_delete' || $link[0].id == 'mediamanager__btn_restore');
+        dw_mediamanager.update_content($content, params, update_list);
+
+        if (jQuery('.scroll-container', $content).length) {
+            jQuery('.scroll-container', $content).html('<img src="' + DOKU_BASE + 'lib/images/loading.gif" alt="..." class="load" />');
+        } else {
+            jQuery($content).html('<img src="' + DOKU_BASE + 'lib/images/loading.gif" alt="..." class="load" />');
+        }
+    },
+
+    update_content: function ($content, params, update_list) {
         jQuery.post(
             DOKU_BASE + 'lib/exe/ajax.php',
-            $link[0].search.substr(1)+'&call=medialist',
+            params,
             function (data) {
+                jQuery('.ui-resizable').each(function(){
+                    jQuery(this).resizable('destroy');
+                });
+
                 $content.html(data);
+
                 dw_mediamanager.prepare_content($content);
                 dw_mediamanager.updatehide();
+
+                dw_mediamanager.update_resizable();
+                dw_behaviour.revisionBoxHandler();
+                jQuery('#mediamanager__form_sort').find('input[type=submit]').hide();
+                dw_mediamanager.set_filelist_view(dw_mediamanager.view, false);
+                dw_mediamanager.image_diff();
+                dw_mediamanager.init_ajax_uploader();
+
+                if (update_list) {
+                    var $link1, $content1, params1;
+                    $link1 = jQuery('a.files');
+                    params1 = $link1[0].search.substr(1)+'&call=medialist';
+                    $content1 = jQuery('#mediamanager__layout_list');
+                    dw_mediamanager.update_content($content1, params1);
+                    jQuery('.scroll-container', $content1).html('<img src="' + DOKU_BASE + 'lib/images/loading.gif" alt="..." class="load" />');
+                }
             },
             'html'
         );
     },
 
+    window_resize: function () {
+        if (jQuery('#mediamanager__layout').width() == dw_mediamanager.layout_width) {
+            return;
+        }
+
+        dw_mediamanager.layout_width = jQuery('#mediamanager__layout').width();
+        $r = jQuery("#mediamanager__layout .layout-resizable, #mediamanager__layout .layout");
+
+        var w = 0, wSum = 0, mCount = 0, mArray = [];
+        $r.each(function() {
+            w = jQuery(this).width();
+            if (w == parseFloat(jQuery(this).css("min-width"))) {
+                wSum += w;
+            } else {
+                mArray[mCount] = jQuery(this);
+                mCount++;
+            }
+        });
+
+        if (mCount > 0) {
+            var width = (0.95 * jQuery('#mediamanager__layout').width() - wSum - 30);
+            wSum = 0;
+            for(var i = 0; i < mArray.length; i++) {
+                wSum += mArray[i].width();
+            }
+            for(var i = 0; i < mArray.length; i++) {
+                w = mArray[i].width();
+                w = 100*w / wSum;
+                mArray[i].width(width*w/100);
+            }
+        }
+
+        $r.each(function() {
+            w = jQuery(this).width();
+            w = (100 * w / jQuery('#mediamanager__layout').width());
+            w += "%";
+            jQuery(this).width(w);
+        });
+
+        var windowHeight = jQuery(window).height();
+        var height = windowHeight - 300;
+        if (layout_height < height) {
+            layout_height = height;
+            jQuery('#mediamanager__layout .scroll-container').each(function (i) {
+                jQuery(this).height(height);
+            });
+            $resizable.each(function() {
+                jQuery(this).height(height+100);
+            });
+        }
+
+        dw_mediamanager.opacity_slider();
+        dw_mediamanager.portions_slider();
+    },
+
+    /**
+     * Updates mediamanager layout
+     *
+     * @author Kate Arzamastseva <pshns@ukr.net>
+     */
+    update_resizable: function () {
+        $resizable = jQuery("#mediamanager__layout .layout-resizable");
+
+        $resizable.resizable({ handles: 'e' ,
+            resize: function(event, ui){
+                var w = 0;
+                $resizable.each(function() {
+                    w += jQuery(this).width();
+                });
+                wSum = w + parseFloat(jQuery('#mediamanager__layout_detail').css("min-width"));
+
+                // max width of resizable column
+                var maxWidth = 0.95 * jQuery('#mediamanager__layout').width() - wSum + jQuery(this).width() - 30;
+                $resizable.resizable( "option", "maxWidth", maxWidth );
+
+                // percentage width of the first two columns
+                var wLeft = ( 100*(w+30) / jQuery('#mediamanager__layout').width() );
+
+                // width of the third column
+                var wRight = 95-wLeft;
+                wRight += "%";
+                jQuery('#mediamanager__layout_detail').width(wRight);
+
+                $resizable.each(function() {
+                    w = jQuery(this).width();
+                    w = (100 * w / jQuery('#mediamanager__layout').width());
+                    w += "%";
+                    jQuery(this).width(w);
+                });
+
+                dw_mediamanager.opacity_slider();
+                dw_mediamanager.portions_slider();
+            }
+        });
+
+        var windowHeight = jQuery(window).height();
+        var height = windowHeight - 300;
+        layout_height = height;
+        jQuery('#mediamanager__layout .scroll-container').each(function (i) {
+            jQuery(this).height(height);
+        });
+        $resizable.each(function() {
+            jQuery(this).height(height+100);
+        });
+    },
+
+     /**
+     * Prints 'select' for image difference representation type
+     *
+     * @author Kate Arzamastseva <pshns@ukr.net>
+     */
+    image_diff: function () {
+        if (jQuery('#mediamanager__difftype').length) return;
+
+        $form = jQuery('#mediamanager__form_diffview');
+        if (!$form.length) return;
+
+        $label = jQuery(document.createElement('label'));
+        $label.append('<span>'+LANG.media_diff+'</span>');
+        $select = jQuery(document.createElement('select'))
+         .attr('id', 'mediamanager__difftype')
+         .attr('name', 'difftype')
+         .change(dw_mediamanager.change_diff_type);
+        $select.append(new Option(LANG.media_diff_both, "both"));
+        $select.append(new Option(LANG.media_diff_opacity, "opacity"));
+        $select.append(new Option(LANG.media_diff_portions, "portions"));
+        $label.append($select);
+        $form.append($label);
+
+    	// for IE
+    	var select = document.getElementById('mediamanager__difftype');
+    	select.options[0].text = LANG.media_diff_both;
+    	select.options[1].text = LANG.media_diff_opacity;
+    	select.options[2].text = LANG.media_diff_portions;
+    },
+
+    /**
+     * Handles selection of image difference representation type
+     *
+     * @author Kate Arzamastseva <pshns@ukr.net>
+     */
+    change_diff_type: function () {
+        $select = jQuery('#mediamanager__difftype');
+        $content = jQuery('#mediamanager__diff');
+
+        params = dw_mediamanager.form_params($select.parents('form'))+'&call=mediadiff';
+        jQuery.post(
+            DOKU_BASE + 'lib/exe/ajax.php',
+            params,
+            function (data) {
+                $content.html(data);
+                dw_mediamanager.portions_slider();
+                dw_mediamanager.opacity_slider();
+            },
+            'html'
+        );
+    },
+
+    /**
+     * Sets options for opacity diff slider
+     *
+     * @author Kate Arzamastseva <pshns@ukr.net>
+     */
+    opacity_slider: function () {
+        var $slider = jQuery( "#mediamanager__opacity_slider" );
+        if (!$slider.length) return;
+
+        var $image = jQuery('#mediamanager__diff_opacity_image1 img');
+        if (!$image.length) return;
+        $slider.width($image.width()-20);
+
+        $slider.slider();
+        $slider.slider("option", "min", 0);
+        $slider.slider("option", "max", 0.999);
+        $slider.slider("option", "step", 0.001);
+        $slider.slider("option", "value", 0.5);
+        $slider.bind("slide", function(event, ui) {
+            jQuery('#mediamanager__diff_opacity_image2').css({ opacity: $slider.slider("option", "value")});
+        });
+    },
+
+     /**
+     * Sets options for red line diff slider
+     *
+     * @author Kate Arzamastseva <pshns@ukr.net>
+     */
+    portions_slider: function () {
+        var $image1 = jQuery('#mediamanager__diff_portions_image1 img');
+        var $image2 = jQuery('#mediamanager__diff_portions_image2 img');
+        if (!$image1.length || !$image2.length) return;
+
+        var $div = jQuery("#mediamanager__diff_layout");
+        if (!$div.length) return;
+
+        $div.width('100%');
+        $image2.parent().width('97%');
+        $image1.width('100%');
+        $image2.width('100%');
+
+        if ($image1.width() < $div.width()) {
+            $div.width($image1.width());
+        }
+
+        $image2.parent().width('50%');
+        $image2.width($image1.width());
+        $image1.width($image1.width());
+
+        var $slider = jQuery("#mediamanager__portions_slider");
+        if (!$slider.length) return;
+        $slider.width($image1.width()-20);
+
+        $slider.slider();
+        $slider.slider("option", "min", 0);
+        $slider.slider("option", "max", 97);
+        $slider.slider("option", "step", 1);
+        $slider.slider("option", "value", 50);
+        $slider.bind("slide", function(event, ui) {
+            jQuery('#mediamanager__diff_portions_image2').css({ width: $slider.slider("option", "value")+'%'});
+        });
+    },
+
+    params_toarray: function (str) {
+        var vars = [], hash;
+        var hashes = str.split('&');
+        for(var i = 0; i < hashes.length; i++) {
+            hash = hashes[i].split('=');
+            vars[hash[0]] = hash[1];
+        }
+        return vars;
+    },
+
+    init_ajax_uploader: function () {
+        if (!jQuery('#mediamanager__uploader').length) return;
+        if (jQuery('.qq-upload-list').length) return;
+
+        var params = dw_mediamanager.form_params(jQuery('#dw__upload'))+'&call=mediaupload';
+        params = dw_mediamanager.params_toarray(params);
+
+        var uploader = new qq.FileUploaderExtended({
+            element: document.getElementById('mediamanager__uploader'),
+            action: DOKU_BASE + 'lib/exe/ajax.php',
+            params: params
+        });
+    },
+
     prepare_content: function ($content) {
         // hide syntax example
         $content.find('div.example:visible').hide();
-        dw_mediamanager.initFlashUpload();
     },
 
     /**
@@ -521,4 +966,10 @@ function hasFlash(version){
     return ver >= version;
 }
 
+jQuery(document).ready(function() {
+    dw_mediamanager.update_resizable();
+    dw_mediamanager.layout_width = jQuery("#mediamanager__layout").width();
+    jQuery(window).resize(dw_mediamanager.window_resize);
+});
+
 jQuery(dw_mediamanager.init);
diff --git a/lib/tpl/default/_fileuploader.css b/lib/tpl/default/_fileuploader.css
new file mode 100644
index 0000000000000000000000000000000000000000..9e60393a0573e0b4ece85c00633cfe45726a86b4
--- /dev/null
+++ b/lib/tpl/default/_fileuploader.css
@@ -0,0 +1,106 @@
+.qq-uploader {
+    position: relative;
+    width: 100%;
+}
+
+.qq-upload-button {
+    display: inline-block;
+    border: 1px solid __border__;
+    color: __text__;
+    background-color: __background__;
+    text-decoration: none;
+    font-size: 100%;
+    cursor: pointer;
+    margin: 1px;
+    margin-bottom: 5px;
+    padding: 0.125em 0.4em;
+    background:  __background__ url(images/buttonshadow.png) repeat-x bottom;
+}
+
+* html .qq-upload-button,
+*+html .qq-upload-button {
+    display: inline;
+}
+
+.qq-upload-button-focus {
+    outline: 1px dotted black;
+}
+
+.qq-upload-drop-area {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    min-height: 70px;
+    z-index: 2;
+    background: __background_neu__;
+    text-align: center;
+}
+
+.qq-upload-drop-area span {
+    display:block;
+    position:absolute;
+    top: 50%;
+    width:100%;
+    margin-top:-8px;
+    font-size: 120%;
+}
+
+.qq-upload-drop-area-active {
+    background: __background_alt__;
+}
+
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-failed-text {
+    margin-right: 7px;
+}
+
+.qq-upload-spinner {
+    display: inline-block;
+    background: url("../../images/throbber.gif");
+    width: 15px;
+    height: 15px;
+    vertical-align: text-bottom;
+}
+
+.qq-upload-size,.qq-upload-cancel {
+    font-size: 85%;
+}
+
+.qq-upload-failed-text {
+    display:none;
+}
+
+.qq-upload-fail .qq-upload-failed-text {
+    display:inline;
+}
+
+.qq-upload-file {
+    display: block;
+    font-weight: bold;
+}
+
+.qq-upload-file-hidden {
+    display:none;
+}
+
+.qq-upload-cancel {
+    padding-left: 10px;
+}
+
+.qq-uploader .dw__ow {
+    margin-left: 10px;
+    margin-right: 3px;
+}
+
+.qq-uploader .check {
+    line-height: 18px;
+}
+
+.qq-uploader #mediamanager__upload_button {
+    margin-bottom: 10px;
+}
+
+.qq-uploader div.li {
+    margin-bottom: 5px;
+}
\ No newline at end of file
diff --git a/lib/tpl/default/design.css b/lib/tpl/default/design.css
index 1fdf2bfacf1d38dbefbfa23c63642edbe5228ce8..a94f814aae2286703b418afd84db966906a6730a 100644
--- a/lib/tpl/default/design.css
+++ b/lib/tpl/default/design.css
@@ -246,6 +246,12 @@ div.dokuwiki div.pagenav-next {
     width: 49%
 }
 
+/* ----------- type of recent changes select -------- */
+
+div.dokuwiki form#dw__recent select {
+    margin-bottom: 10px;
+}
+
 /* --------------- Links ------------------ */
 
 div.dokuwiki a:link,
diff --git a/lib/tpl/default/detail.php b/lib/tpl/default/detail.php
index 4f42b116e4beed01395cb5d4839ab01398b0c562..503c0ef71deb75355608e45ea2ab84cfbbd68fac 100644
--- a/lib/tpl/default/detail.php
+++ b/lib/tpl/default/detail.php
@@ -49,33 +49,33 @@ if (!defined('DOKU_INC')) die();
       </p>
 
       <p>&larr; <?php echo $lang['img_backto']?> <?php tpl_pagelink($ID)?></p>
+      <?php
+            $imgNS = getNS($IMG);
+            $authNS = auth_quickaclcheck("$imgNS:*");
+            if ($authNS >= AUTH_UPLOAD) {
+                echo '<p><a href="'.media_managerURL(array('ns' => $imgNS, 'image' => $IMG)).'">'.$lang['img_manager'].'</a></p>';
+            }
+      ?>
 
       <dl class="img_tags">
         <?php
-          $t = tpl_img_getTag('Date.EarliestTime');
-          if($t) print '<dt>'.$lang['img_date'].':</dt><dd>'.dformat($t).'</dd>';
-
-          $t = tpl_img_getTag('File.Name');
-          if($t) print '<dt>'.$lang['img_fname'].':</dt><dd>'.hsc($t).'</dd>';
-
-          $t = tpl_img_getTag(array('Iptc.Byline','Exif.TIFFArtist','Exif.Artist','Iptc.Credit'));
-          if($t) print '<dt>'.$lang['img_artist'].':</dt><dd>'.hsc($t).'</dd>';
-
-          $t = tpl_img_getTag(array('Iptc.CopyrightNotice','Exif.TIFFCopyright','Exif.Copyright'));
-          if($t) print '<dt>'.$lang['img_copyr'].':</dt><dd>'.hsc($t).'</dd>';
-
-          $t = tpl_img_getTag('File.Format');
-          if($t) print '<dt>'.$lang['img_format'].':</dt><dd>'.hsc($t).'</dd>';
-
-          $t = tpl_img_getTag('File.NiceSize');
-          if($t) print '<dt>'.$lang['img_fsize'].':</dt><dd>'.hsc($t).'</dd>';
-
-          $t = tpl_img_getTag('Simple.Camera');
-          if($t) print '<dt>'.$lang['img_camera'].':</dt><dd>'.hsc($t).'</dd>';
-
-          $t = tpl_img_getTag(array('IPTC.Keywords','IPTC.Category','xmp.dc:subject'));
-          if($t) print '<dt>'.$lang['img_keywords'].':</dt><dd>'.hsc($t).'</dd>';
-
+            $config_files = getConfigFiles('mediameta');
+            foreach ($config_files as $config_file) {
+                if(@file_exists($config_file)) include($config_file);
+            }
+
+            foreach($fields as $key => $tag){
+                $t = array();
+                if (!empty($tag[0])) $t = array($tag[0]);
+                if(is_array($tag[3])) $t = array_merge($t,$tag[3]);
+                $value = tpl_img_getTag($t);
+                if ($value) {
+                    echo '<dt>'.$lang[$tag[1]].':</dt><dd>';
+                    if ($tag[2] == 'date') echo dformat($value);
+                    else echo hsc($value);
+                    echo '</dd>';
+                }
+            }
         ?>
       </dl>
       <?php //Comment in for Debug// dbg(tpl_img_getTag('Simple.Raw'));?>
diff --git a/lib/tpl/default/images/resizecol.png b/lib/tpl/default/images/resizecol.png
new file mode 100644
index 0000000000000000000000000000000000000000..79fea0770a6e18e366cea4c4f60654e272e73c8b
Binary files /dev/null and b/lib/tpl/default/images/resizecol.png differ
diff --git a/lib/tpl/default/main.php b/lib/tpl/default/main.php
index 26e84fde61ddf2eebce75adbd80b19bc981e8243..3e85c58f2833e91cebaba3bfd2ebae2bb6b301b7 100644
--- a/lib/tpl/default/main.php
+++ b/lib/tpl/default/main.php
@@ -116,6 +116,7 @@ if (!defined('DOKU_INC')) die();
       </div>
       <div class="bar-right" id="bar__bottomright">
         <?php tpl_button('subscribe')?>
+        <?php tpl_button('media')?>
         <?php tpl_button('admin')?>
         <?php tpl_button('profile')?>
         <?php tpl_button('login')?>
diff --git a/lib/tpl/default/media.css b/lib/tpl/default/media.css
index 7b558a80bba922f46d4c13295daa1f41c6a9099b..b325a1ef6e20e212dd781d350f90ac172d168799 100644
--- a/lib/tpl/default/media.css
+++ b/lib/tpl/default/media.css
@@ -151,6 +151,11 @@ it's dirty, so any "real" fixes are welcome */
     border-bottom: solid 1px __border__;
     padding: 0 0.5em 1em 0.5em;
 }
+
+#media__content form#dw__upload {
+    border-bottom: 0;
+}
+
 #media__content form#dw__upload fieldset {
     padding: 0;
     margin: 0;
diff --git a/lib/tpl/default/mediamanager.css b/lib/tpl/default/mediamanager.css
new file mode 100644
index 0000000000000000000000000000000000000000..ea69dbeb9f9e5f1997ee9b732a7367b312e42bc3
--- /dev/null
+++ b/lib/tpl/default/mediamanager.css
@@ -0,0 +1,487 @@
+/* Layout */
+
+#mediamanager__page {
+    width: 100%;
+    overflow-x: auto;
+}
+
+#mediamanager__page h1 {
+    margin: 0 0 .5em;
+}
+
+#mediamanager__layout {
+    min-width: 840px;
+}
+
+#mediamanager__layout .layout,
+#mediamanager__layout .layout-resizable {
+    float: left;
+}
+
+#mediamanager__layout .layout-resizable {
+    margin-right: 10px;
+    float: left;
+}
+
+#mediamanager__layout .scroll-container {
+    overflow-y: auto;
+    overflow-x: hidden;
+    padding: 0;
+    margin: 0;
+    text-align: left;
+    position: relative;
+}
+
+#mediamanager__layout .background-container {
+    background-color: __background_alt__;
+    margin-bottom: 10px;
+    padding: 10px 10px 8px;
+    text-align: left;
+    min-height: 20px;
+}
+
+#mediamanager__layout .background-container .icon {
+    margin-right: 5px;
+}
+
+#mediamanager__layout_namespaces {
+    width: 15%;
+    min-width: 125px;
+}
+
+#mediamanager__layout_list {
+    width: 45%;
+    min-width: 375px;
+}
+
+#mediamanager__layout_detail {
+    width: 35%;
+    min-width: 290px;
+}
+
+#mediamanager__page .ui-resizable-e {
+    width: 16px;
+    right: -13px;
+    /* icon from Crystal Clear icon set (LGPL) */
+    background: transparent url(images/resizecol.png) 0 25px no-repeat;
+}
+
+#mediamanager__page .ui-resizable-e:hover {
+    width: 6px;
+    right: -8px;
+    background-image: none;
+    background-color: __background_alt__;
+}
+
+/* Namespaces tree */
+
+#mediamanager__page ul.idx {
+    margin-left: .2em;
+}
+
+#mediamanager__page ul.idx ul {
+    margin-left: 1em;
+}
+
+.idx .selected {
+    background-color: __highlight__;
+    font-weight: bold;
+}
+
+/* Tabs */
+
+.mediamanager-tabs a {
+    font-weight: bold;
+    display: block;
+    float: left;
+    padding: 10px;
+    padding-bottom: 5px;
+    padding-top: 5px;
+    margin-right: 2px;
+    -moz-border-radius-topright: 10px;
+    -webkit-border-top-right-radius: 10px;
+    -moz-border-radius-topleft: 10px;
+    -webkit-border-top-left-radius: 10px;
+    border-top-right-radius: 10px;
+    border-top-left-radius: 10px;
+}
+
+.mediamanager-tabs .selected {
+    background-color: __background_alt__;
+}
+
+.mediamanager-tabs a:hover {
+    background-color: __background_alt__;
+    opacity: 0.5;
+}
+
+/* Title links */
+
+#mediamanager__files .namespace {
+    float: left;
+    font-weight: normal;
+}
+
+#mediamanager__tabs_list {
+    float: left;
+}
+
+#mediamanager__link_thumbs,
+#mediamanager__link_list {
+    padding-left: 30px;
+    display: inline-block;
+    width: 0;
+    overflow: hidden;
+}
+
+#mediamanager__link_thumbs {
+    background: url('../../images/icon-thumb.png') 0 -4px no-repeat;
+    margin-left: 10px;
+}
+
+#mediamanager__link_list {
+    background: url('../../images/icon-list.png') 0 -4px no-repeat;
+}
+
+#mediamanager__link_thumbs:hover,
+#mediamanager__link_list:hover {
+    width: auto;
+    margin-right: 10px;
+}
+
+#mediamanager__sort {
+    background: url('../../images/icon-sort.png') 0 -4px no-repeat;
+    padding-left: 30px;
+    display: block;
+    float: right;
+}
+
+* html #mediamanager__sort,
+*+html #mediamanager__sort {
+    position: relative;
+    margin-top: -18px;
+}
+
+/* File list */
+
+#mediamanager__file_list {
+    padding: 0;
+    margin: 0 !important;
+}
+
+#mediamanager__file_list li:hover {
+    background-color: __background_alt__;
+}
+
+/* Files thumbs view */
+
+.mediamanager-thumbs li {
+    width: 100px;
+    min-height: 130px;
+    display: inline-block;
+    display: -moz-inline-stack;
+    /* the right margin should visually be 10px, but because of its inline-block nature the whitespace inbetween is about 4px more */
+    margin: 0 6px 10px 0;
+    background-color: __background_neu__;
+    color: __text__;
+    padding: 5px;
+    vertical-align: top;
+    text-align: center;
+    zoom: 1;
+    position: relative;
+    line-height: 1.2;
+}
+
+* html .mediamanager-thumbs li {
+    display: inline;
+}
+
+*+html .mediamanager-thumbs li {
+    display: inline;
+}
+
+.mediamanager-thumbs li .image,
+.mediamanager-thumbs li .image0 {
+    width: 100%;
+    height: 90px;
+    display: block;
+    overflow: hidden;
+}
+
+.mediamanager-thumbs li .image1 {
+    display: none;
+}
+
+.mediamanager-thumbs li .image span,
+.mediamanager-thumbs li .image0 span {
+    vertical-align: middle;
+    display: table-cell;
+    width: 100px;
+    height: 90px;
+}
+
+.mediamanager-thumbs li .name,
+.mediamanager-thumbs li .size,
+.mediamanager-thumbs li .filesize,
+.mediamanager-thumbs li .date {
+    display: block;
+    overflow: hidden;
+    width: 90px;
+    white-space: nowrap;
+}
+
+.mediamanager-thumbs li .name {
+    padding: 5px 0;
+    font-weight: bold;
+}
+
+.mediamanager-thumbs li .date {
+    font-style: italic;
+    white-space: normal;
+}
+
+.mediamanager-thumbs li input[type=checkbox] {
+    display: none;
+    float: left;
+    margin: 3px;
+}
+
+.mediamanager-thumbs li:hover input[type=checkbox],
+.mediamanager-thumbs li input[type=checkbox]:checked {
+    display: block;
+}
+
+/* Files list view */
+
+.mediamanager-list li {
+    list-style: none;
+    display: block;
+    position: relative;
+    max-height: 50px;
+    margin: 0;
+    margin-bottom: 3px;
+    background-color: __background__;
+    color: __text__;
+}
+
+.mediamanager-list li:nth-child(2n+1) {
+    background-color: __background_neu__;
+}
+
+.mediamanager-list li .image,
+.mediamanager-list li .image1 {
+    width: 10%;
+    display: block;
+    overflow: hidden;
+    float: left;
+    height: 40px;
+    text-align: center;
+}
+
+.mediamanager-list li .image0 {
+    display: none;
+}
+
+.mediamanager-list li .name,
+.mediamanager-list li .size,
+.mediamanager-list li .filesize,
+.mediamanager-list li .date {
+    overflow: hidden;
+    float: left;
+    margin-left: 1%;
+    white-space: nowrap;
+}
+
+.mediamanager-list li .name {
+    width: 30%;
+    font-weight: bold;
+}
+
+.mediamanager-list li .size,
+.mediamanager-list li .filesize {
+    width: 15%;
+}
+
+.mediamanager-list li .date {
+    width: 20%;
+}
+
+.mediamanager-list li .date {
+    font-style: italic;
+    white-space: normal;
+}
+
+.mediamanager-list .icon {
+    max-width: 16px;
+    max-height: 16px;
+}
+
+.mediamanager-list li .image span,
+.mediamanager-list li .image1 span {
+    vertical-align: middle;
+    text-align: center;
+    display: table-cell;
+    width: 100px;
+    height: 40px;
+}
+
+.mediamanager-list li input[type=checkbox] {
+    display: none;
+    float: left;
+    margin: 3px;
+}
+
+.mediamanager-list li:hover input[type=checkbox],
+.mediamanager-list li input[type=checkbox]:checked {
+    display: block;
+}
+
+/* Upload panel */
+
+#mediamanager__layout div.upload {
+    padding-bottom: 0.5em;
+}
+
+#media__content #mediamanager__uploader {
+    border-bottom: 1px solid __border__;
+    padding-bottom: 0.5em;
+}
+
+/* File preview */
+
+.mediamanager__preview,
+.mediamanager__preview_buttons {
+    text-align: center;
+    margin-bottom: 5px;
+}
+
+.mediamanager__preview img {
+    width: 99%;
+}
+
+/* Meta data edit form */
+
+#mediamanager__details div.metafield {
+    margin-bottom: 5px;
+}
+
+#mediamanager__details label {
+    display: block;
+}
+
+#mediamanager__details form.meta input {
+    width: 50%;
+}
+
+#mediamanager__details form.meta input.button {
+    width: auto;
+}
+
+#mediamanager__details form.meta textarea.edit {
+    height: 6em;
+    width: 95%;
+    min-width: 95%;
+    max-width: 95%;
+}
+
+/* Revisions form */
+
+#mediamanager__details #page__revisions ul {
+    margin-left: 10px;
+    list-style-type: none;
+}
+
+#mediamanager__details #page__revisions ul li div.li div {
+    font-size: 90%;
+    color: __text_neu__;
+    padding-left: 18px;
+}
+
+#mediamanager__details #page__revisions ul li div.li input {
+    position: relative;
+    top: 1px;
+}
+
+/* File diff */
+
+#mediamanager__diff {
+    margin-top: 10px;
+}
+
+#mediamanager__diff_table {
+    padding: 0;
+    margin: 0;
+    margin-top: 10px;
+}
+
+#mediamanager__diff_table li {
+    width: 48%;
+    display: inline-block;
+    margin: 0;
+    margin-bottom: 10px;
+    padding: 2px;
+    vertical-align: top;
+    zoom: 1;
+    color: __text__;
+}
+
+* html #mediamanager__diff_table li {
+    display: inline;
+}
+
+*+html #mediamanager__diff_table li {
+    display: inline;
+}
+
+/* For IE7 */
+*:first-child+html #mediamanager__diff_table li {
+    width: 45%;
+}
+
+/* Image diff */
+
+#mediamanager__layout dl.img_tags dd.highlighted{
+    background-color: __highlight__;
+}
+
+#mediamanager__form_diffview {
+    margin-bottom: 10px;
+}
+
+#mediamanager__diff_layout {
+    position: relative;
+}
+
+#mediamanager__diff_layout div {
+    position: absolute;
+    top: 0;
+    left: 0;
+}
+
+#mediamanager__diff_opacity_image1,
+#mediamanager__diff_portions_image1 {
+    width: 97%;
+}
+
+#mediamanager__diff_layout div img {
+    width: 100%;
+}
+
+#mediamanager__diff_opacity_image2 {
+    width: 97%;
+    -moz-opacity: 0.5;
+    -khtml-opacity: 0.5;
+    opacity: 0.5;
+}
+
+#mediamanager__diff_portions_image2 {
+    width: 97%;
+    border-right: 1px solid red;
+    overflow: hidden;
+}
+
+#mediamanager__opacity_slider,
+#mediamanager__portions_slider {
+    margin: 10px;
+    width: 95%;
+}
diff --git a/lib/tpl/default/style.ini b/lib/tpl/default/style.ini
index c5b2c31a530a2f77afe9e4b55aec1eb9c0e520e1..05914012e23a151037e95791747b29c1af5d34dc 100644
--- a/lib/tpl/default/style.ini
+++ b/lib/tpl/default/style.ini
@@ -15,6 +15,8 @@ _mediaoptions.css = screen
 _admin.css        = screen
 _linkwiz.css      = screen
 _subscription.css = screen
+mediamanager.css  = screen
+_fileuploader.css = screen
 
 rtl.css        = rtl
 print.css      = print