summaryrefslogtreecommitdiff
path: root/platform/www/lib
diff options
context:
space:
mode:
Diffstat (limited to 'platform/www/lib')
-rw-r--r--platform/www/lib/exe/ajax.php25
-rw-r--r--platform/www/lib/exe/css.php676
-rw-r--r--platform/www/lib/exe/detail.php42
-rw-r--r--platform/www/lib/exe/fetch.php106
-rw-r--r--platform/www/lib/exe/index.html11
-rw-r--r--platform/www/lib/exe/indexer.php5
-rw-r--r--platform/www/lib/exe/jquery.php44
-rw-r--r--platform/www/lib/exe/js.php490
-rw-r--r--platform/www/lib/exe/manifest.php14
-rw-r--r--platform/www/lib/exe/mediamanager.php129
-rw-r--r--platform/www/lib/exe/opensearch.php38
-rw-r--r--platform/www/lib/exe/taskrunner.php16
-rw-r--r--platform/www/lib/exe/xmlrpc.php15
-rw-r--r--platform/www/lib/images/README6
-rw-r--r--platform/www/lib/images/_deprecated.txt2
-rw-r--r--platform/www/lib/images/admin/README4
-rw-r--r--platform/www/lib/images/admin/acl.pngbin0 -> 1065 bytes
-rw-r--r--platform/www/lib/images/admin/config.pngbin0 -> 1484 bytes
-rw-r--r--platform/www/lib/images/admin/plugin.pngbin0 -> 1115 bytes
-rw-r--r--platform/www/lib/images/admin/popularity.pngbin0 -> 1172 bytes
-rw-r--r--platform/www/lib/images/admin/revert.pngbin0 -> 1295 bytes
-rw-r--r--platform/www/lib/images/admin/styling.pngbin0 -> 970 bytes
-rw-r--r--platform/www/lib/images/admin/usermanager.pngbin0 -> 1460 bytes
-rw-r--r--platform/www/lib/images/blank.gifbin0 -> 42 bytes
-rw-r--r--platform/www/lib/images/bullet.pngbin0 -> 101 bytes
-rw-r--r--platform/www/lib/images/closed-rtl.pngbin0 -> 111 bytes
-rw-r--r--platform/www/lib/images/closed.pngbin0 -> 110 bytes
-rw-r--r--platform/www/lib/images/diff.pngbin0 -> 190 bytes
-rw-r--r--platform/www/lib/images/email.pngbin0 -> 370 bytes
-rw-r--r--platform/www/lib/images/error.pngbin0 -> 637 bytes
-rw-r--r--platform/www/lib/images/external-link.pngbin0 -> 431 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/7z.pngbin0 -> 911 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/asm.pngbin0 -> 955 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/bash.pngbin0 -> 966 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/bz2.pngbin0 -> 920 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/c.pngbin0 -> 929 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/cc.pngbin0 -> 933 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/conf.pngbin0 -> 666 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/cpp.pngbin0 -> 943 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/cs.pngbin0 -> 944 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/csh.pngbin0 -> 952 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/css.pngbin0 -> 952 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/csv.pngbin0 -> 663 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/deb.pngbin0 -> 914 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/diff.pngbin0 -> 942 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/doc.pngbin0 -> 956 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/docx.pngbin0 -> 970 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/file.pngbin0 -> 543 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/gif.pngbin0 -> 873 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/gz.pngbin0 -> 914 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/h.pngbin0 -> 884 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/hpp.pngbin0 -> 942 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/htm.pngbin0 -> 945 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/html.pngbin0 -> 945 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/ico.pngbin0 -> 865 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/java.pngbin0 -> 961 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/jpeg.pngbin0 -> 877 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/jpg.pngbin0 -> 877 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/js.pngbin0 -> 937 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/json.pngbin0 -> 966 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/lua.pngbin0 -> 941 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/mp3.pngbin0 -> 896 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/mp4.pngbin0 -> 1116 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/odc.pngbin0 -> 946 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/odf.pngbin0 -> 951 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/odg.pngbin0 -> 949 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/odi.pngbin0 -> 944 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/odp.pngbin0 -> 949 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/ods.pngbin0 -> 955 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/odt.pngbin0 -> 949 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/ogg.pngbin0 -> 885 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/ogv.pngbin0 -> 1106 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/pas.pngbin0 -> 945 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/pdf.pngbin0 -> 1003 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/php.pngbin0 -> 952 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/pl.pngbin0 -> 936 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/png.pngbin0 -> 877 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/ppt.pngbin0 -> 850 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/pptx.pngbin0 -> 866 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/ps.pngbin0 -> 996 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/py.pngbin0 -> 942 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/rar.pngbin0 -> 914 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/rb.pngbin0 -> 936 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/rpm.pngbin0 -> 920 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/rtf.pngbin0 -> 738 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/sh.pngbin0 -> 941 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/sql.pngbin0 -> 664 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/svg.pngbin0 -> 980 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/swf.pngbin0 -> 1173 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/sxc.pngbin0 -> 964 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/sxd.pngbin0 -> 965 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/sxi.pngbin0 -> 962 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/sxw.pngbin0 -> 968 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/tar.pngbin0 -> 914 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/tgz.pngbin0 -> 919 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/txt.pngbin0 -> 661 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/wav.pngbin0 -> 888 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/webm.pngbin0 -> 1210 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/xls.pngbin0 -> 1124 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/xlsx.pngbin0 -> 1131 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/xml.pngbin0 -> 560 bytes
-rw-r--r--platform/www/lib/images/fileicons/32x32/zip.pngbin0 -> 914 bytes
-rw-r--r--platform/www/lib/images/fileicons/7z.pngbin0 -> 375 bytes
-rw-r--r--platform/www/lib/images/fileicons/README2
-rw-r--r--platform/www/lib/images/fileicons/asm.pngbin0 -> 379 bytes
-rw-r--r--platform/www/lib/images/fileicons/bash.pngbin0 -> 378 bytes
-rw-r--r--platform/www/lib/images/fileicons/bz2.pngbin0 -> 378 bytes
-rw-r--r--platform/www/lib/images/fileicons/c.pngbin0 -> 369 bytes
-rw-r--r--platform/www/lib/images/fileicons/cc.pngbin0 -> 369 bytes
-rw-r--r--platform/www/lib/images/fileicons/conf.pngbin0 -> 370 bytes
-rw-r--r--platform/www/lib/images/fileicons/cpp.pngbin0 -> 377 bytes
-rw-r--r--platform/www/lib/images/fileicons/cs.pngbin0 -> 374 bytes
-rw-r--r--platform/www/lib/images/fileicons/csh.pngbin0 -> 378 bytes
-rw-r--r--platform/www/lib/images/fileicons/css.pngbin0 -> 374 bytes
-rw-r--r--platform/www/lib/images/fileicons/csv.pngbin0 -> 371 bytes
-rw-r--r--platform/www/lib/images/fileicons/deb.pngbin0 -> 376 bytes
-rw-r--r--platform/www/lib/images/fileicons/diff.pngbin0 -> 376 bytes
-rw-r--r--platform/www/lib/images/fileicons/doc.pngbin0 -> 372 bytes
-rw-r--r--platform/www/lib/images/fileicons/docx.pngbin0 -> 375 bytes
-rw-r--r--platform/www/lib/images/fileicons/file.pngbin0 -> 249 bytes
-rw-r--r--platform/www/lib/images/fileicons/gif.pngbin0 -> 374 bytes
-rw-r--r--platform/www/lib/images/fileicons/gz.pngbin0 -> 374 bytes
-rw-r--r--platform/www/lib/images/fileicons/h.pngbin0 -> 368 bytes
-rw-r--r--platform/www/lib/images/fileicons/hpp.pngbin0 -> 376 bytes
-rw-r--r--platform/www/lib/images/fileicons/htm.pngbin0 -> 375 bytes
-rw-r--r--platform/www/lib/images/fileicons/html.pngbin0 -> 375 bytes
-rw-r--r--platform/www/lib/images/fileicons/ico.pngbin0 -> 372 bytes
-rw-r--r--platform/www/lib/images/fileicons/index.php59
-rw-r--r--platform/www/lib/images/fileicons/java.pngbin0 -> 376 bytes
-rw-r--r--platform/www/lib/images/fileicons/jpeg.pngbin0 -> 376 bytes
-rw-r--r--platform/www/lib/images/fileicons/jpg.pngbin0 -> 376 bytes
-rw-r--r--platform/www/lib/images/fileicons/js.pngbin0 -> 374 bytes
-rw-r--r--platform/www/lib/images/fileicons/json.pngbin0 -> 379 bytes
-rw-r--r--platform/www/lib/images/fileicons/lua.pngbin0 -> 374 bytes
-rw-r--r--platform/www/lib/images/fileicons/mp3.pngbin0 -> 378 bytes
-rw-r--r--platform/www/lib/images/fileicons/mp4.pngbin0 -> 377 bytes
-rw-r--r--platform/www/lib/images/fileicons/odc.pngbin0 -> 369 bytes
-rw-r--r--platform/www/lib/images/fileicons/odf.pngbin0 -> 373 bytes
-rw-r--r--platform/www/lib/images/fileicons/odg.pngbin0 -> 370 bytes
-rw-r--r--platform/www/lib/images/fileicons/odi.pngbin0 -> 371 bytes
-rw-r--r--platform/www/lib/images/fileicons/odp.pngbin0 -> 374 bytes
-rw-r--r--platform/www/lib/images/fileicons/ods.pngbin0 -> 373 bytes
-rw-r--r--platform/www/lib/images/fileicons/odt.pngbin0 -> 372 bytes
-rw-r--r--platform/www/lib/images/fileicons/ogg.pngbin0 -> 373 bytes
-rw-r--r--platform/www/lib/images/fileicons/ogv.pngbin0 -> 376 bytes
-rw-r--r--platform/www/lib/images/fileicons/pas.pngbin0 -> 380 bytes
-rw-r--r--platform/www/lib/images/fileicons/pdf.pngbin0 -> 377 bytes
-rw-r--r--platform/www/lib/images/fileicons/php.pngbin0 -> 376 bytes
-rw-r--r--platform/www/lib/images/fileicons/pl.pngbin0 -> 372 bytes
-rw-r--r--platform/www/lib/images/fileicons/png.pngbin0 -> 375 bytes
-rw-r--r--platform/www/lib/images/fileicons/ppt.pngbin0 -> 375 bytes
-rw-r--r--platform/www/lib/images/fileicons/pptx.pngbin0 -> 375 bytes
-rw-r--r--platform/www/lib/images/fileicons/ps.pngbin0 -> 377 bytes
-rw-r--r--platform/www/lib/images/fileicons/py.pngbin0 -> 374 bytes
-rw-r--r--platform/www/lib/images/fileicons/rar.pngbin0 -> 377 bytes
-rw-r--r--platform/www/lib/images/fileicons/rb.pngbin0 -> 375 bytes
-rw-r--r--platform/www/lib/images/fileicons/rpm.pngbin0 -> 374 bytes
-rw-r--r--platform/www/lib/images/fileicons/rtf.pngbin0 -> 376 bytes
-rw-r--r--platform/www/lib/images/fileicons/sh.pngbin0 -> 375 bytes
-rw-r--r--platform/www/lib/images/fileicons/sql.pngbin0 -> 373 bytes
-rw-r--r--platform/www/lib/images/fileicons/svg.pngbin0 -> 437 bytes
-rw-r--r--platform/www/lib/images/fileicons/swf.pngbin0 -> 379 bytes
-rw-r--r--platform/www/lib/images/fileicons/sxc.pngbin0 -> 377 bytes
-rw-r--r--platform/www/lib/images/fileicons/sxd.pngbin0 -> 377 bytes
-rw-r--r--platform/www/lib/images/fileicons/sxi.pngbin0 -> 377 bytes
-rw-r--r--platform/www/lib/images/fileicons/sxw.pngbin0 -> 376 bytes
-rw-r--r--platform/www/lib/images/fileicons/tar.pngbin0 -> 377 bytes
-rw-r--r--platform/www/lib/images/fileicons/tgz.pngbin0 -> 377 bytes
-rw-r--r--platform/www/lib/images/fileicons/txt.pngbin0 -> 371 bytes
-rw-r--r--platform/www/lib/images/fileicons/wav.pngbin0 -> 375 bytes
-rw-r--r--platform/www/lib/images/fileicons/webm.pngbin0 -> 378 bytes
-rw-r--r--platform/www/lib/images/fileicons/xls.pngbin0 -> 378 bytes
-rw-r--r--platform/www/lib/images/fileicons/xlsx.pngbin0 -> 379 bytes
-rw-r--r--platform/www/lib/images/fileicons/xml.pngbin0 -> 376 bytes
-rw-r--r--platform/www/lib/images/fileicons/zip.pngbin0 -> 377 bytes
-rw-r--r--platform/www/lib/images/history.pngbin0 -> 149 bytes
-rw-r--r--platform/www/lib/images/icon-list.pngbin0 -> 584 bytes
-rw-r--r--platform/www/lib/images/icon-sort.pngbin0 -> 211 bytes
-rw-r--r--platform/www/lib/images/index.html11
-rw-r--r--platform/www/lib/images/info.pngbin0 -> 721 bytes
-rw-r--r--platform/www/lib/images/interwiki.pngbin0 -> 442 bytes
-rw-r--r--platform/www/lib/images/interwiki/amazon.de.gifbin0 -> 132 bytes
-rw-r--r--platform/www/lib/images/interwiki/amazon.gifbin0 -> 132 bytes
-rw-r--r--platform/www/lib/images/interwiki/amazon.uk.gifbin0 -> 132 bytes
-rw-r--r--platform/www/lib/images/interwiki/callto.gifbin0 -> 177 bytes
-rw-r--r--platform/www/lib/images/interwiki/doku.gifbin0 -> 188 bytes
-rw-r--r--platform/www/lib/images/interwiki/google.gifbin0 -> 170 bytes
-rw-r--r--platform/www/lib/images/interwiki/paypal.gifbin0 -> 139 bytes
-rw-r--r--platform/www/lib/images/interwiki/phpfn.gifbin0 -> 164 bytes
-rw-r--r--platform/www/lib/images/interwiki/skype.gifbin0 -> 142 bytes
-rw-r--r--platform/www/lib/images/interwiki/tel.gifbin0 -> 177 bytes
-rw-r--r--platform/www/lib/images/interwiki/user.pngbin0 -> 684 bytes
-rw-r--r--platform/www/lib/images/interwiki/wp.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/images/interwiki/wpde.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/images/interwiki/wpes.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/images/interwiki/wpfr.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/images/interwiki/wpjp.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/images/interwiki/wpmeta.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/images/interwiki/wppl.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/images/larger.gifbin0 -> 87 bytes
-rw-r--r--platform/www/lib/images/license/badge/cc-by-nc-nd.pngbin0 -> 1456 bytes
-rw-r--r--platform/www/lib/images/license/badge/cc-by-nc-sa.pngbin0 -> 1567 bytes
-rw-r--r--platform/www/lib/images/license/badge/cc-by-nc.pngbin0 -> 1401 bytes
-rw-r--r--platform/www/lib/images/license/badge/cc-by-nd.pngbin0 -> 1261 bytes
-rw-r--r--platform/www/lib/images/license/badge/cc-by-sa.pngbin0 -> 1407 bytes
-rw-r--r--platform/www/lib/images/license/badge/cc-by.pngbin0 -> 1186 bytes
-rw-r--r--platform/www/lib/images/license/badge/cc-zero.pngbin0 -> 1182 bytes
-rw-r--r--platform/www/lib/images/license/badge/cc.pngbin0 -> 846 bytes
-rw-r--r--platform/www/lib/images/license/badge/gnufdl.pngbin0 -> 1649 bytes
-rw-r--r--platform/www/lib/images/license/badge/publicdomain.pngbin0 -> 1326 bytes
-rw-r--r--platform/www/lib/images/license/button/cc-by-nc-nd.pngbin0 -> 391 bytes
-rw-r--r--platform/www/lib/images/license/button/cc-by-nc-sa.pngbin0 -> 396 bytes
-rw-r--r--platform/www/lib/images/license/button/cc-by-nc.pngbin0 -> 381 bytes
-rw-r--r--platform/www/lib/images/license/button/cc-by-nd.pngbin0 -> 382 bytes
-rw-r--r--platform/www/lib/images/license/button/cc-by-sa.pngbin0 -> 379 bytes
-rw-r--r--platform/www/lib/images/license/button/cc-by.pngbin0 -> 364 bytes
-rw-r--r--platform/www/lib/images/license/button/cc-zero.pngbin0 -> 381 bytes
-rw-r--r--platform/www/lib/images/license/button/cc.pngbin0 -> 391 bytes
-rw-r--r--platform/www/lib/images/license/button/gnufdl.pngbin0 -> 497 bytes
-rw-r--r--platform/www/lib/images/license/button/publicdomain.pngbin0 -> 364 bytes
-rw-r--r--platform/www/lib/images/magnifier.pngbin0 -> 565 bytes
-rw-r--r--platform/www/lib/images/media_align_center.pngbin0 -> 249 bytes
-rw-r--r--platform/www/lib/images/media_align_left.pngbin0 -> 247 bytes
-rw-r--r--platform/www/lib/images/media_align_noalign.pngbin0 -> 218 bytes
-rw-r--r--platform/www/lib/images/media_align_right.pngbin0 -> 250 bytes
-rw-r--r--platform/www/lib/images/media_link_direct.pngbin0 -> 714 bytes
-rw-r--r--platform/www/lib/images/media_link_displaylnk.pngbin0 -> 304 bytes
-rw-r--r--platform/www/lib/images/media_link_lnk.pngbin0 -> 578 bytes
-rw-r--r--platform/www/lib/images/media_link_nolnk.pngbin0 -> 452 bytes
-rw-r--r--platform/www/lib/images/media_size_large.pngbin0 -> 100 bytes
-rw-r--r--platform/www/lib/images/media_size_medium.pngbin0 -> 226 bytes
-rw-r--r--platform/www/lib/images/media_size_original.pngbin0 -> 210 bytes
-rw-r--r--platform/www/lib/images/media_size_small.pngbin0 -> 206 bytes
-rw-r--r--platform/www/lib/images/mediamanager.pngbin0 -> 455 bytes
-rw-r--r--platform/www/lib/images/menu/00-default_checkbox-blank-circle-outline.svg1
-rw-r--r--platform/www/lib/images/menu/01-edit_pencil.svg1
-rw-r--r--platform/www/lib/images/menu/02-create_pencil.svg1
-rw-r--r--platform/www/lib/images/menu/03-draft_android-studio.svg1
-rw-r--r--platform/www/lib/images/menu/04-show_file-document.svg1
-rw-r--r--platform/www/lib/images/menu/05-source_file-xml.svg1
-rw-r--r--platform/www/lib/images/menu/06-revert_replay.svg1
-rw-r--r--platform/www/lib/images/menu/07-revisions_history.svg1
-rw-r--r--platform/www/lib/images/menu/08-backlink_link-variant.svg1
-rw-r--r--platform/www/lib/images/menu/09-subscribe_email-outline.svg1
-rw-r--r--platform/www/lib/images/menu/10-top_arrow-up.svg1
-rw-r--r--platform/www/lib/images/menu/11-mediamanager_folder-image.svg1
-rw-r--r--platform/www/lib/images/menu/12-back_arrow-left.svg1
-rw-r--r--platform/www/lib/images/menu/account-card-details.svg1
-rw-r--r--platform/www/lib/images/menu/account-plus.svg1
-rw-r--r--platform/www/lib/images/menu/calendar-clock.svg1
-rw-r--r--platform/www/lib/images/menu/file-tree.svg1
-rw-r--r--platform/www/lib/images/menu/folder-multiple-image.svg1
-rw-r--r--platform/www/lib/images/menu/lock-reset.svg1
-rw-r--r--platform/www/lib/images/menu/login.svg1
-rw-r--r--platform/www/lib/images/menu/logout.svg1
-rw-r--r--platform/www/lib/images/menu/settings.svg1
-rw-r--r--platform/www/lib/images/minus.gifbin0 -> 85 bytes
-rw-r--r--platform/www/lib/images/notify.pngbin0 -> 735 bytes
-rw-r--r--platform/www/lib/images/ns.pngbin0 -> 799 bytes
-rw-r--r--platform/www/lib/images/open.pngbin0 -> 107 bytes
-rw-r--r--platform/www/lib/images/page.pngbin0 -> 582 bytes
-rw-r--r--platform/www/lib/images/plus.gifbin0 -> 88 bytes
-rw-r--r--platform/www/lib/images/resizecol.pngbin0 -> 148 bytes
-rw-r--r--platform/www/lib/images/smaller.gifbin0 -> 86 bytes
-rw-r--r--platform/www/lib/images/smileys/delete.gifbin0 -> 448 bytes
-rw-r--r--platform/www/lib/images/smileys/facepalm.gifbin0 -> 185 bytes
-rw-r--r--platform/www/lib/images/smileys/fixme.gifbin0 -> 450 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_arrow.gifbin0 -> 170 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_biggrin.gifbin0 -> 172 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_confused.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_cool.gifbin0 -> 172 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_cry.gifbin0 -> 424 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_doubt.gifbin0 -> 178 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_doubt2.gifbin0 -> 180 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_eek.gifbin0 -> 170 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_evil.gifbin0 -> 188 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_exclaim.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_frown.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_fun.gifbin0 -> 179 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_idea.gifbin0 -> 176 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_kaddi.gifbin0 -> 179 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_lol.gifbin0 -> 344 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_mrgreen.gifbin0 -> 168 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_neutral.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_question.gifbin0 -> 182 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_razz.gifbin0 -> 176 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_redface.gifbin0 -> 669 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_rolleyes.gifbin0 -> 471 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_sad.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_silenced.gifbin0 -> 177 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_smile.gifbin0 -> 174 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_smile2.gifbin0 -> 174 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_surprised.gifbin0 -> 174 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_twisted.gifbin0 -> 180 bytes
-rw-r--r--platform/www/lib/images/smileys/icon_wink.gifbin0 -> 170 bytes
-rw-r--r--platform/www/lib/images/smileys/index.php53
-rw-r--r--platform/www/lib/images/success.pngbin0 -> 725 bytes
-rw-r--r--platform/www/lib/images/throbber.gifbin0 -> 746 bytes
-rw-r--r--platform/www/lib/images/toolbar/bold.pngbin0 -> 249 bytes
-rw-r--r--platform/www/lib/images/toolbar/chars.pngbin0 -> 493 bytes
-rw-r--r--platform/www/lib/images/toolbar/h.pngbin0 -> 257 bytes
-rw-r--r--platform/www/lib/images/toolbar/h1.pngbin0 -> 287 bytes
-rw-r--r--platform/www/lib/images/toolbar/h2.pngbin0 -> 319 bytes
-rw-r--r--platform/www/lib/images/toolbar/h3.pngbin0 -> 320 bytes
-rw-r--r--platform/www/lib/images/toolbar/h4.pngbin0 -> 310 bytes
-rw-r--r--platform/www/lib/images/toolbar/h5.pngbin0 -> 318 bytes
-rw-r--r--platform/www/lib/images/toolbar/hequal.pngbin0 -> 306 bytes
-rw-r--r--platform/www/lib/images/toolbar/hminus.pngbin0 -> 403 bytes
-rw-r--r--platform/www/lib/images/toolbar/hplus.pngbin0 -> 391 bytes
-rw-r--r--platform/www/lib/images/toolbar/hr.pngbin0 -> 251 bytes
-rw-r--r--platform/www/lib/images/toolbar/image.pngbin0 -> 539 bytes
-rw-r--r--platform/www/lib/images/toolbar/italic.pngbin0 -> 239 bytes
-rw-r--r--platform/www/lib/images/toolbar/link.pngbin0 -> 402 bytes
-rw-r--r--platform/www/lib/images/toolbar/linkextern.pngbin0 -> 902 bytes
-rw-r--r--platform/www/lib/images/toolbar/mono.pngbin0 -> 293 bytes
-rw-r--r--platform/www/lib/images/toolbar/ol.pngbin0 -> 302 bytes
-rw-r--r--platform/www/lib/images/toolbar/sig.pngbin0 -> 471 bytes
-rw-r--r--platform/www/lib/images/toolbar/smiley.pngbin0 -> 680 bytes
-rw-r--r--platform/www/lib/images/toolbar/strike.pngbin0 -> 316 bytes
-rw-r--r--platform/www/lib/images/toolbar/ul.pngbin0 -> 288 bytes
-rw-r--r--platform/www/lib/images/toolbar/underline.pngbin0 -> 297 bytes
-rw-r--r--platform/www/lib/images/trash.pngbin0 -> 423 bytes
-rw-r--r--platform/www/lib/images/unc.pngbin0 -> 290 bytes
-rw-r--r--platform/www/lib/images/up.pngbin0 -> 248 bytes
-rw-r--r--platform/www/lib/images/wrap.gifbin0 -> 86 bytes
-rw-r--r--platform/www/lib/index.html11
-rw-r--r--platform/www/lib/plugins/acl/action.php86
-rw-r--r--platform/www/lib/plugins/acl/admin.php858
-rw-r--r--platform/www/lib/plugins/acl/admin.svg1
-rw-r--r--platform/www/lib/plugins/acl/lang/en/help.txt9
-rw-r--r--platform/www/lib/plugins/acl/lang/en/lang.php46
-rw-r--r--platform/www/lib/plugins/acl/pix/group.pngbin0 -> 699 bytes
-rw-r--r--platform/www/lib/plugins/acl/pix/ns.pngbin0 -> 799 bytes
-rw-r--r--platform/www/lib/plugins/acl/pix/page.pngbin0 -> 582 bytes
-rw-r--r--platform/www/lib/plugins/acl/pix/user.pngbin0 -> 650 bytes
-rw-r--r--platform/www/lib/plugins/acl/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/acl/remote.php102
-rw-r--r--platform/www/lib/plugins/acl/script.js121
-rw-r--r--platform/www/lib/plugins/acl/style.css135
-rw-r--r--platform/www/lib/plugins/action.php2
-rw-r--r--platform/www/lib/plugins/admin.php2
-rw-r--r--platform/www/lib/plugins/auth.php2
-rw-r--r--platform/www/lib/plugins/authad/action.php90
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/adLDAP.php949
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/classes/adLDAPComputers.php153
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/classes/adLDAPContacts.php294
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/classes/adLDAPExchange.php390
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/classes/adLDAPFolders.php179
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/classes/adLDAPGroups.php631
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/classes/adLDAPUsers.php682
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/classes/adLDAPUtils.php268
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/collections/adLDAPCollection.php137
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/collections/adLDAPComputerCollection.php46
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/collections/adLDAPContactCollection.php46
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/collections/adLDAPGroupCollection.php46
-rw-r--r--platform/www/lib/plugins/authad/adLDAP/collections/adLDAPUserCollection.php46
-rw-r--r--platform/www/lib/plugins/authad/auth.php786
-rw-r--r--platform/www/lib/plugins/authad/conf/default.php18
-rw-r--r--platform/www/lib/plugins/authad/conf/metadata.php18
-rw-r--r--platform/www/lib/plugins/authad/lang/en/lang.php15
-rw-r--r--platform/www/lib/plugins/authad/lang/en/settings.php18
-rw-r--r--platform/www/lib/plugins/authad/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/authldap/auth.php698
-rw-r--r--platform/www/lib/plugins/authldap/conf/default.php23
-rw-r--r--platform/www/lib/plugins/authldap/conf/metadata.php22
-rw-r--r--platform/www/lib/plugins/authldap/lang/en/lang.php11
-rw-r--r--platform/www/lib/plugins/authldap/lang/en/settings.php30
-rw-r--r--platform/www/lib/plugins/authldap/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/authpdo/README27
-rw-r--r--platform/www/lib/plugins/authpdo/auth.php826
-rw-r--r--platform/www/lib/plugins/authpdo/conf/default.php118
-rw-r--r--platform/www/lib/plugins/authpdo/conf/metadata.php25
-rw-r--r--platform/www/lib/plugins/authpdo/lang/en/lang.php12
-rw-r--r--platform/www/lib/plugins/authpdo/lang/en/settings.php25
-rw-r--r--platform/www/lib/plugins/authpdo/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/authplain/auth.php494
-rw-r--r--platform/www/lib/plugins/authplain/lang/en/lang.php9
-rw-r--r--platform/www/lib/plugins/authplain/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/cli.php2
-rw-r--r--platform/www/lib/plugins/config/admin.php282
-rw-r--r--platform/www/lib/plugins/config/admin.svg1
-rw-r--r--platform/www/lib/plugins/config/core/ConfigParser.php90
-rw-r--r--platform/www/lib/plugins/config/core/Configuration.php219
-rw-r--r--platform/www/lib/plugins/config/core/Loader.php269
-rw-r--r--platform/www/lib/plugins/config/core/Setting/Setting.php294
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingArray.php105
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingAuthtype.php60
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingCompression.php21
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingDirchoice.php33
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingDisableactions.php23
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingEmail.php58
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingFieldset.php17
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingHidden.php10
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingImConvert.php28
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingLicense.php23
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingMulticheckbox.php163
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingMultichoice.php71
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingNoClass.php12
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingNoDefault.php13
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingNoKnownClass.php11
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingNumeric.php42
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingNumericopt.php25
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingOnoff.php57
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingPassword.php39
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingRegex.php34
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingRenderer.php56
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingSavedir.php26
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingSepchar.php18
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingString.php32
-rw-r--r--platform/www/lib/plugins/config/core/Setting/SettingUndefined.php40
-rw-r--r--platform/www/lib/plugins/config/core/Writer.php116
-rw-r--r--platform/www/lib/plugins/config/images/danger.pngbin0 -> 637 bytes
-rw-r--r--platform/www/lib/plugins/config/images/security.pngbin0 -> 682 bytes
-rw-r--r--platform/www/lib/plugins/config/images/warning.pngbin0 -> 606 bytes
-rw-r--r--platform/www/lib/plugins/config/lang/en/intro.txt7
-rw-r--r--platform/www/lib/plugins/config/lang/en/lang.php277
-rw-r--r--platform/www/lib/plugins/config/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/config/settings/config.metadata.php245
-rw-r--r--platform/www/lib/plugins/config/style.css167
-rw-r--r--platform/www/lib/plugins/extension/action.php82
-rw-r--r--platform/www/lib/plugins/extension/admin.php185
-rw-r--r--platform/www/lib/plugins/extension/admin.svg1
-rw-r--r--platform/www/lib/plugins/extension/all.less37
-rw-r--r--platform/www/lib/plugins/extension/cli.php372
-rw-r--r--platform/www/lib/plugins/extension/helper/extension.php1298
-rw-r--r--platform/www/lib/plugins/extension/helper/gui.php237
-rw-r--r--platform/www/lib/plugins/extension/helper/list.php674
-rw-r--r--platform/www/lib/plugins/extension/helper/repository.php203
-rw-r--r--platform/www/lib/plugins/extension/images/bug.gifbin0 -> 194 bytes
-rw-r--r--platform/www/lib/plugins/extension/images/disabled.pngbin0 -> 1163 bytes
-rw-r--r--platform/www/lib/plugins/extension/images/donate.pngbin0 -> 677 bytes
-rw-r--r--platform/www/lib/plugins/extension/images/down.pngbin0 -> 197 bytes
-rw-r--r--platform/www/lib/plugins/extension/images/enabled.pngbin0 -> 1172 bytes
-rw-r--r--platform/www/lib/plugins/extension/images/icons.xcfbin0 -> 67195 bytes
-rw-r--r--platform/www/lib/plugins/extension/images/license.txt4
-rw-r--r--platform/www/lib/plugins/extension/images/overlay.pngbin0 -> 68 bytes
-rw-r--r--platform/www/lib/plugins/extension/images/plugin.pngbin0 -> 4054 bytes
-rw-r--r--platform/www/lib/plugins/extension/images/tag.pngbin0 -> 341 bytes
-rw-r--r--platform/www/lib/plugins/extension/images/template.pngbin0 -> 5206 bytes
-rw-r--r--platform/www/lib/plugins/extension/images/up.pngbin0 -> 197 bytes
-rw-r--r--platform/www/lib/plugins/extension/images/warning.pngbin0 -> 606 bytes
-rw-r--r--platform/www/lib/plugins/extension/lang/en/intro_install.txt1
-rw-r--r--platform/www/lib/plugins/extension/lang/en/intro_plugins.txt1
-rw-r--r--platform/www/lib/plugins/extension/lang/en/intro_search.txt1
-rw-r--r--platform/www/lib/plugins/extension/lang/en/intro_templates.txt1
-rw-r--r--platform/www/lib/plugins/extension/lang/en/lang.php110
-rw-r--r--platform/www/lib/plugins/extension/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/extension/script.js145
-rw-r--r--platform/www/lib/plugins/extension/style.less386
-rw-r--r--platform/www/lib/plugins/farmer/.github/auto-comment.yml9
-rw-r--r--platform/www/lib/plugins/farmer/.travis.yml15
-rw-r--r--platform/www/lib/plugins/farmer/3rdparty/PHPIco.php248
-rw-r--r--platform/www/lib/plugins/farmer/3rdparty/RingIcon.php186
-rw-r--r--platform/www/lib/plugins/farmer/DokuWikiFarmCore.php375
-rw-r--r--platform/www/lib/plugins/farmer/README27
-rw-r--r--platform/www/lib/plugins/farmer/_animal/conf/acl.auth.php21
-rw-r--r--platform/www/lib/plugins/farmer/_animal/conf/local.php6
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/_dummy0
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/attic/_dummy0
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/cache/_dummy0
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/index/_dummy0
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/locks/_dummy0
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/log/_dummy0
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/media/wiki/dokuwiki-128.pngbin0 -> 33615 bytes
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/media_attic/_dummy0
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/media_meta/_dummy0
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/meta/_dummy0
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/pages/wiki/dokuwiki.txt64
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/pages/wiki/syntax.txt486
-rw-r--r--platform/www/lib/plugins/farmer/_animal/data/tmp/_dummy0
-rw-r--r--platform/www/lib/plugins/farmer/_test/core.test.php40
-rw-r--r--platform/www/lib/plugins/farmer/_test/general.test.php36
-rw-r--r--platform/www/lib/plugins/farmer/_test/getUserLine.test.php86
-rw-r--r--platform/www/lib/plugins/farmer/_test/helper.test.php64
-rw-r--r--platform/www/lib/plugins/farmer/action/ajax.php267
-rw-r--r--platform/www/lib/plugins/farmer/action/disable.php56
-rw-r--r--platform/www/lib/plugins/farmer/action/startup.php90
-rw-r--r--platform/www/lib/plugins/farmer/admin.php115
-rw-r--r--platform/www/lib/plugins/farmer/admin.svg1
-rw-r--r--platform/www/lib/plugins/farmer/admin/config.php126
-rw-r--r--platform/www/lib/plugins/farmer/admin/delete.php95
-rw-r--r--platform/www/lib/plugins/farmer/admin/info.php116
-rw-r--r--platform/www/lib/plugins/farmer/admin/new.php338
-rw-r--r--platform/www/lib/plugins/farmer/admin/plugins.php104
-rw-r--r--platform/www/lib/plugins/farmer/admin/setup.php151
-rw-r--r--platform/www/lib/plugins/farmer/all.less13
-rw-r--r--platform/www/lib/plugins/farmer/conf/default.php9
-rw-r--r--platform/www/lib/plugins/farmer/conf/metadata.php9
-rw-r--r--platform/www/lib/plugins/farmer/css/chosen-sprite.pngbin0 -> 538 bytes
-rw-r--r--platform/www/lib/plugins/farmer/css/chosen.less450
-rw-r--r--platform/www/lib/plugins/farmer/deleted.files11
-rw-r--r--platform/www/lib/plugins/farmer/helper.php334
-rw-r--r--platform/www/lib/plugins/farmer/includes/config.php13
-rw-r--r--platform/www/lib/plugins/farmer/includes/plugins.php12
-rw-r--r--platform/www/lib/plugins/farmer/includes/template.php27
-rw-r--r--platform/www/lib/plugins/farmer/lang/de/lang.php118
-rw-r--r--platform/www/lib/plugins/farmer/lang/de/notfound_404.txt3
-rw-r--r--platform/www/lib/plugins/farmer/lang/de/notfound_list.txt3
-rw-r--r--platform/www/lib/plugins/farmer/lang/de/settings.php13
-rw-r--r--platform/www/lib/plugins/farmer/lang/de/tab_config.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/de/tab_delete.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/de/tab_info.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/de/tab_new.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/de/tab_plugins.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/de/tab_setup.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/lang.php128
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/notfound_404.txt3
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/notfound_list.txt3
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/settings.php13
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/tab_config.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/tab_config_help.txt26
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/tab_delete.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/tab_info.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/tab_new.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/tab_new_help.txt31
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/tab_plugins.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/tab_plugins_help.txt16
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/tab_setup.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/en/tab_setup_help.txt37
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/lang.php109
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/notfound_404.txt3
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/notfound_list.txt4
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/settings.php9
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/tab_config.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/tab_config_help.txt37
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/tab_delete.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/tab_info.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/tab_new.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/tab_new_help.txt43
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/tab_plugins.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/tab_plugins_help.txt22
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/tab_setup.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/fr/tab_setup_help.txt52
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/lang.php106
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/notfound_404.txt3
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/notfound_list.txt3
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/settings.php9
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/tab_config.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/tab_config_help.txt25
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/tab_delete.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/tab_info.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/tab_new.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/tab_new_help.txt38
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/tab_plugins.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/tab_plugins_help.txt17
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/tab_setup.txt1
-rw-r--r--platform/www/lib/plugins/farmer/lang/ja/tab_setup_help.txt38
-rw-r--r--platform/www/lib/plugins/farmer/lang/nl/lang.php20
-rw-r--r--platform/www/lib/plugins/farmer/lang/pl/lang.php102
-rw-r--r--platform/www/lib/plugins/farmer/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/farmer/script.js2
-rw-r--r--platform/www/lib/plugins/farmer/script/jquery.chosen.js1257
-rw-r--r--platform/www/lib/plugins/farmer/script/plugins.js149
-rw-r--r--platform/www/lib/plugins/farmer/style.less104
m---------platform/www/lib/plugins/fastwiki0
-rw-r--r--platform/www/lib/plugins/index.html11
-rw-r--r--platform/www/lib/plugins/info/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/info/syntax.php302
-rw-r--r--platform/www/lib/plugins/markdowku/LICENSE27
-rw-r--r--platform/www/lib/plugins/markdowku/README.md2
-rw-r--r--platform/www/lib/plugins/markdowku/manager.dat2
-rw-r--r--platform/www/lib/plugins/markdowku/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/anchorsinline.php46
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/anchorsreference.php62
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/autolinks.php40
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/blockquotes.php115
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/boldasterisk.php45
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/codeblocks.php63
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/codespans1.php35
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/codespans2.php34
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/codespans3.php34
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/codespans4.php34
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/codespans5.php34
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/escapespecialchars.php94
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/githubcodeblocks.php48
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/headeratx.php57
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/headersetext.php56
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/hr.php47
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/imagesinline.php43
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/imagesreference.php58
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/italicasterisk.php49
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/italicunderline.php56
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/linebreak.php32
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/olists.php98
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/references.php41
-rw-r--r--platform/www/lib/plugins/markdowku/syntax/ulists.php99
-rw-r--r--platform/www/lib/plugins/master.zipbin0 -> 139966 bytes
-rw-r--r--platform/www/lib/plugins/popularity/action.php66
-rw-r--r--platform/www/lib/plugins/popularity/admin.php157
-rw-r--r--platform/www/lib/plugins/popularity/admin.svg1
-rw-r--r--platform/www/lib/plugins/popularity/helper.php292
-rw-r--r--platform/www/lib/plugins/popularity/lang/en/intro.txt11
-rw-r--r--platform/www/lib/plugins/popularity/lang/en/lang.php9
-rw-r--r--platform/www/lib/plugins/popularity/lang/en/submitted.txt3
-rw-r--r--platform/www/lib/plugins/popularity/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/remote.php2
-rw-r--r--platform/www/lib/plugins/revert/admin.php193
-rw-r--r--platform/www/lib/plugins/revert/admin.svg1
-rw-r--r--platform/www/lib/plugins/revert/lang/en/intro.txt3
-rw-r--r--platform/www/lib/plugins/revert/lang/en/lang.php23
-rw-r--r--platform/www/lib/plugins/revert/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/safefnrecode/action.php68
-rw-r--r--platform/www/lib/plugins/safefnrecode/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/styling/README27
-rw-r--r--platform/www/lib/plugins/styling/action.php51
-rw-r--r--platform/www/lib/plugins/styling/admin.php224
-rw-r--r--platform/www/lib/plugins/styling/admin.svg1
-rw-r--r--platform/www/lib/plugins/styling/lang/en/intro.txt2
-rw-r--r--platform/www/lib/plugins/styling/lang/en/lang.php35
-rw-r--r--platform/www/lib/plugins/styling/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/styling/popup.php31
-rw-r--r--platform/www/lib/plugins/styling/script.js92
-rw-r--r--platform/www/lib/plugins/styling/style.less13
-rw-r--r--platform/www/lib/plugins/syntax.php2
-rw-r--r--platform/www/lib/plugins/textinsert/README55
-rw-r--r--platform/www/lib/plugins/textinsert/admin.php292
-rw-r--r--platform/www/lib/plugins/textinsert/conf/default.php5
-rw-r--r--platform/www/lib/plugins/textinsert/conf/metadata.php3
-rw-r--r--platform/www/lib/plugins/textinsert/lang/de/lang.php22
-rw-r--r--platform/www/lib/plugins/textinsert/lang/de/settings.php8
-rw-r--r--platform/www/lib/plugins/textinsert/lang/en/intro.txt16
-rw-r--r--platform/www/lib/plugins/textinsert/lang/en/lang.php31
l---------platform/www/lib/plugins/textinsert/lang/en/macros.php1
-rw-r--r--platform/www/lib/plugins/textinsert/lang/en/settings.php3
-rw-r--r--platform/www/lib/plugins/textinsert/lang/es/intro.txt16
-rw-r--r--platform/www/lib/plugins/textinsert/lang/es/lang.php31
l---------platform/www/lib/plugins/textinsert/lang/es/macros.php1
-rw-r--r--platform/www/lib/plugins/textinsert/lang/es/settings.php3
-rw-r--r--platform/www/lib/plugins/textinsert/lang/fr/intro.txt10
-rw-r--r--platform/www/lib/plugins/textinsert/lang/fr/lang.php25
-rw-r--r--platform/www/lib/plugins/textinsert/lang/fr/settings.php10
-rw-r--r--platform/www/lib/plugins/textinsert/lang/ja/intro.txt16
-rw-r--r--platform/www/lib/plugins/textinsert/lang/ja/lang.php23
-rw-r--r--platform/www/lib/plugins/textinsert/lang/ja/settings.php9
-rw-r--r--platform/www/lib/plugins/textinsert/lang/nl/intro.txt11
-rw-r--r--platform/www/lib/plugins/textinsert/lang/nl/lang.php24
-rw-r--r--platform/www/lib/plugins/textinsert/lang/nl/settings.php8
-rw-r--r--platform/www/lib/plugins/textinsert/lang/pt-br/intro.txt13
-rw-r--r--platform/www/lib/plugins/textinsert/lang/pt-br/lang.php23
-rw-r--r--platform/www/lib/plugins/textinsert/lang/pt-br/settings.php9
-rw-r--r--platform/www/lib/plugins/textinsert/lang/ru/intro.txt10
-rw-r--r--platform/www/lib/plugins/textinsert/lang/ru/lang.php25
-rw-r--r--platform/www/lib/plugins/textinsert/lang/ru/settings.php9
-rw-r--r--platform/www/lib/plugins/textinsert/lang/zh/intro.txt11
-rw-r--r--platform/www/lib/plugins/textinsert/lang/zh/lang.php23
-rw-r--r--platform/www/lib/plugins/textinsert/manager.dat2
-rw-r--r--platform/www/lib/plugins/textinsert/plugin.info.txt9
-rw-r--r--platform/www/lib/plugins/textinsert/syntax.php261
-rw-r--r--platform/www/lib/plugins/textinsert/version2
-rw-r--r--platform/www/lib/plugins/translation/.travis.yml13
-rw-r--r--platform/www/lib/plugins/translation/README25
-rw-r--r--platform/www/lib/plugins/translation/_test/basic.test.php113
-rw-r--r--platform/www/lib/plugins/translation/_test/general.test.php61
-rw-r--r--platform/www/lib/plugins/translation/action.php302
-rw-r--r--platform/www/lib/plugins/translation/admin.php101
-rw-r--r--platform/www/lib/plugins/translation/admin.svg1
-rw-r--r--platform/www/lib/plugins/translation/conf/default.php19
-rw-r--r--platform/www/lib/plugins/translation/conf/metadata.php21
-rw-r--r--platform/www/lib/plugins/translation/flags/af.gifbin0 -> 369 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/ar.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/da.gifbin0 -> 374 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/de.gifbin0 -> 362 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/el.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/en.gifbin0 -> 260 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/es.gifbin0 -> 360 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/et.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/fa.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/fr.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/ga.gifbin0 -> 371 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/he.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/hu.gifbin0 -> 357 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/it.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/ja.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/ko.gifbin0 -> 385 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ad.gifbin0 -> 371 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ae.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ag.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ai.gifbin0 -> 369 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/al.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/am.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/an.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ao.gifbin0 -> 244 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ar.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/as.gifbin0 -> 365 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/at.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/au.gifbin0 -> 378 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/aw.gifbin0 -> 365 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ax.gifbin0 -> 376 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/az.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ba.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bb.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bd.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/be.gifbin0 -> 359 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bf.gifbin0 -> 358 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bg.gifbin0 -> 360 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bh.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bi.gifbin0 -> 374 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bj.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/blankflag.gifbin0 -> 42 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bm.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bn.gifbin0 -> 373 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bo.gifbin0 -> 359 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bs.gifbin0 -> 351 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bt.gifbin0 -> 377 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bv.gifbin0 -> 376 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bw.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/by.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/bz.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ca.gifbin0 -> 376 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/catalonia.gifbin0 -> 238 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cc.gifbin0 -> 371 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cd.gifbin0 -> 243 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cf.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cg.gifbin0 -> 359 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ch.gifbin0 -> 332 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ci.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ck.gifbin0 -> 362 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cl.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cm.gifbin0 -> 369 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/co.gifbin0 -> 353 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cr.gifbin0 -> 359 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cs.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cu.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cv.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cx.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cy.gifbin0 -> 365 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/cz.gifbin0 -> 362 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/dj.gifbin0 -> 369 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/dm.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/do.gifbin0 -> 362 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/dz.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ec.gifbin0 -> 362 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/eg.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/eh.gifbin0 -> 359 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/england.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/er.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/et.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/europeanunion.gifbin0 -> 171 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/fam.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/fi.gifbin0 -> 371 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/fj.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/fk.gifbin0 -> 372 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/fm.gifbin0 -> 377 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/fo.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ga.gifbin0 -> 359 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gd.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ge.gifbin0 -> 379 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gf.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gh.gifbin0 -> 358 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gi.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gl.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gm.gifbin0 -> 362 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gn.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gp.gifbin0 -> 357 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gq.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gs.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gt.gifbin0 -> 374 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gu.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gw.gifbin0 -> 358 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/gy.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/hk.gifbin0 -> 373 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/hm.gifbin0 -> 378 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/hn.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/hr.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ht.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/id.gifbin0 -> 362 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/in.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/io.gifbin0 -> 373 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/iq.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/is.gifbin0 -> 373 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ja.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/jm.gifbin0 -> 365 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/jo.gifbin0 -> 360 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ke.gifbin0 -> 360 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/kg.gifbin0 -> 373 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/kh.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ki.gifbin0 -> 371 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/km.gifbin0 -> 358 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/kn.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ko.gifbin0 -> 385 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/kp.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/kw.gifbin0 -> 362 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ky.gifbin0 -> 373 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/kz.gifbin0 -> 374 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/la.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/lb.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/lc.gifbin0 -> 259 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/li.gifbin0 -> 359 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/lk.gifbin0 -> 377 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/lr.gifbin0 -> 360 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ls.gifbin0 -> 369 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/lt.gifbin0 -> 362 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/lu.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/lv.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ly.gifbin0 -> 362 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ma.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mc.gifbin0 -> 359 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/md.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/me.gifbin0 -> 238 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mg.gifbin0 -> 372 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mh.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mk.gifbin0 -> 382 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ml.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mm.gifbin0 -> 365 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mn.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mo.gifbin0 -> 378 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mp.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mq.gifbin0 -> 379 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mr.gifbin0 -> 377 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ms.gifbin0 -> 371 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mt.gifbin0 -> 369 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mu.gifbin0 -> 358 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mv.gifbin0 -> 372 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mw.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mx.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/my.gifbin0 -> 375 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/mz.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/na.gifbin0 -> 371 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/nc.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ne.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/nf.gifbin0 -> 375 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ng.gifbin0 -> 371 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ni.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/np.gifbin0 -> 302 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/nr.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/nu.gifbin0 -> 369 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/nz.gifbin0 -> 369 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/om.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/pa.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/pe.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/pf.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/pg.gifbin0 -> 360 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ph.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/pk.gifbin0 -> 377 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/pl.gifbin0 -> 360 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/pm.gifbin0 -> 374 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/pn.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/pr.gifbin0 -> 369 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ps.gifbin0 -> 358 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/pw.gifbin0 -> 374 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/py.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/qa.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/re.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/rs.gifbin0 -> 238 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/rw.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sb.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sc.gifbin0 -> 357 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/scotland.gifbin0 -> 378 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sd.gifbin0 -> 355 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sg.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sh.gifbin0 -> 371 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/si.gifbin0 -> 362 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sj.gifbin0 -> 376 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sk.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sl.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sm.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sn.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/so.gifbin0 -> 376 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sr.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/st.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sv.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sy.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/sz.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/tc.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/td.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/tf.gifbin0 -> 365 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/tg.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/tj.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/tk.gifbin0 -> 372 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/tl.gifbin0 -> 360 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/tm.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/tn.gifbin0 -> 375 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/to.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/tt.gifbin0 -> 377 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/tv.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/tw.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/tz.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ua.gifbin0 -> 360 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ug.gifbin0 -> 359 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/um.gifbin0 -> 371 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/us.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/uy.gifbin0 -> 373 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/uz.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/va.gifbin0 -> 369 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/vc.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ve.gifbin0 -> 364 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/vg.gifbin0 -> 368 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/vi.gifbin0 -> 376 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/vu.gifbin0 -> 365 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/wales.gifbin0 -> 372 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/wf.gifbin0 -> 377 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ws.gifbin0 -> 365 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/ye.gifbin0 -> 356 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/yt.gifbin0 -> 382 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/za.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/zm.gifbin0 -> 358 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/more/zw.gifbin0 -> 365 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/nl.gifbin0 -> 360 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/no.gifbin0 -> 376 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/pt-br.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/pt.gifbin0 -> 369 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/ro.gifbin0 -> 363 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/ru.gifbin0 -> 361 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/sv.gifbin0 -> 367 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/th.gifbin0 -> 360 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/tr.gifbin0 -> 371 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/vi.gifbin0 -> 370 bytes
-rw-r--r--platform/www/lib/plugins/translation/flags/zh.gifbin0 -> 366 bytes
-rw-r--r--platform/www/lib/plugins/translation/helper.php446
-rw-r--r--platform/www/lib/plugins/translation/lang/be/lang.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/be/settings.php23
-rw-r--r--platform/www/lib/plugins/translation/lang/be/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/bn/lang.php10
-rw-r--r--platform/www/lib/plugins/translation/lang/bn/settings.php10
-rw-r--r--platform/www/lib/plugins/translation/lang/bn/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/ca/lang.php16
-rw-r--r--platform/www/lib/plugins/translation/lang/ca/settings.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/ca/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/cs/lang.php16
-rw-r--r--platform/www/lib/plugins/translation/lang/cs/settings.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/cs/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/cy/lang.php11
-rw-r--r--platform/www/lib/plugins/translation/lang/cy/settings.php18
-rw-r--r--platform/www/lib/plugins/translation/lang/cy/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/da/lang.php12
-rw-r--r--platform/www/lib/plugins/translation/lang/da/settings.php20
-rw-r--r--platform/www/lib/plugins/translation/lang/da/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/de-informal/lang.php16
-rw-r--r--platform/www/lib/plugins/translation/lang/de-informal/settings.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/de-informal/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/de/lang.php16
-rw-r--r--platform/www/lib/plugins/translation/lang/de/settings.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/de/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/en/lang.php11
-rw-r--r--platform/www/lib/plugins/translation/lang/en/settings.php20
-rw-r--r--platform/www/lib/plugins/translation/lang/en/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/eo/lang.php11
-rw-r--r--platform/www/lib/plugins/translation/lang/eo/settings.php18
-rw-r--r--platform/www/lib/plugins/translation/lang/eo/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/es/lang.php18
-rw-r--r--platform/www/lib/plugins/translation/lang/es/settings.php22
-rw-r--r--platform/www/lib/plugins/translation/lang/es/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/fa/lang.php16
-rw-r--r--platform/www/lib/plugins/translation/lang/fa/settings.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/fa/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/fr/lang.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/fr/settings.php22
-rw-r--r--platform/www/lib/plugins/translation/lang/fr/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/hr/lang.php16
-rw-r--r--platform/www/lib/plugins/translation/lang/hr/settings.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/hr/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/hu/lang.php11
-rw-r--r--platform/www/lib/plugins/translation/lang/hu/settings.php18
-rw-r--r--platform/www/lib/plugins/translation/lang/hu/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/it/lang.php10
-rw-r--r--platform/www/lib/plugins/translation/lang/it/settings.php20
-rw-r--r--platform/www/lib/plugins/translation/lang/it/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/ja/lang.php16
-rw-r--r--platform/www/lib/plugins/translation/lang/ja/settings.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/ja/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/ko/lang.php16
-rw-r--r--platform/www/lib/plugins/translation/lang/ko/settings.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/ko/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/langnames.txt188
-rw-r--r--platform/www/lib/plugins/translation/lang/lv/lang.php11
-rw-r--r--platform/www/lib/plugins/translation/lang/lv/settings.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/lv/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/nl/lang.php17
-rw-r--r--platform/www/lib/plugins/translation/lang/nl/settings.php21
-rw-r--r--platform/www/lib/plugins/translation/lang/nl/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/pt-br/lang.php17
-rw-r--r--platform/www/lib/plugins/translation/lang/pt-br/settings.php21
-rw-r--r--platform/www/lib/plugins/translation/lang/pt-br/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/pt/lang.php11
-rw-r--r--platform/www/lib/plugins/translation/lang/pt/settings.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/ru/lang.php18
-rw-r--r--platform/www/lib/plugins/translation/lang/ru/settings.php21
-rw-r--r--platform/www/lib/plugins/translation/lang/ru/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/sl/lang.php9
-rw-r--r--platform/www/lib/plugins/translation/lang/sl/settings.php18
-rw-r--r--platform/www/lib/plugins/translation/lang/sv/lang.php16
-rw-r--r--platform/www/lib/plugins/translation/lang/sv/settings.php19
-rw-r--r--platform/www/lib/plugins/translation/lang/sv/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/tr/lang.php10
-rw-r--r--platform/www/lib/plugins/translation/lang/tr/settings.php15
-rw-r--r--platform/www/lib/plugins/translation/lang/tr/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/uk/lang.php17
-rw-r--r--platform/www/lib/plugins/translation/lang/uk/settings.php23
-rw-r--r--platform/www/lib/plugins/translation/lang/uk/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/lang/zh-tw/lang.php6
-rw-r--r--platform/www/lib/plugins/translation/lang/zh-tw/settings.php16
-rw-r--r--platform/www/lib/plugins/translation/lang/zh/lang.php18
-rw-r--r--platform/www/lib/plugins/translation/lang/zh/settings.php21
-rw-r--r--platform/www/lib/plugins/translation/lang/zh/totranslate.txt1
-rw-r--r--platform/www/lib/plugins/translation/manager.dat2
-rw-r--r--platform/www/lib/plugins/translation/plugin.info.txt8
-rw-r--r--platform/www/lib/plugins/translation/print.css1
-rw-r--r--platform/www/lib/plugins/translation/script.js20
-rw-r--r--platform/www/lib/plugins/translation/style.css114
-rw-r--r--platform/www/lib/plugins/translation/syntax/notrans.php92
-rw-r--r--platform/www/lib/plugins/translation/syntax/trans.php72
-rw-r--r--platform/www/lib/plugins/usermanager/admin.php1235
-rw-r--r--platform/www/lib/plugins/usermanager/admin.svg1
-rw-r--r--platform/www/lib/plugins/usermanager/images/search.pngbin0 -> 549 bytes
-rw-r--r--platform/www/lib/plugins/usermanager/lang/en/add.txt1
-rw-r--r--platform/www/lib/plugins/usermanager/lang/en/delete.txt1
-rw-r--r--platform/www/lib/plugins/usermanager/lang/en/edit.txt1
-rw-r--r--platform/www/lib/plugins/usermanager/lang/en/import.txt9
-rw-r--r--platform/www/lib/plugins/usermanager/lang/en/intro.txt1
-rw-r--r--platform/www/lib/plugins/usermanager/lang/en/lang.php86
-rw-r--r--platform/www/lib/plugins/usermanager/lang/en/list.txt1
-rw-r--r--platform/www/lib/plugins/usermanager/plugin.info.txt7
-rw-r--r--platform/www/lib/plugins/usermanager/script.js8
-rw-r--r--platform/www/lib/plugins/usermanager/style.css33
-rw-r--r--platform/www/lib/scripts/behaviour.js195
-rw-r--r--platform/www/lib/scripts/compatibility.js42
-rw-r--r--platform/www/lib/scripts/cookie.js71
-rw-r--r--platform/www/lib/scripts/delay.js70
-rw-r--r--platform/www/lib/scripts/edit.js307
-rw-r--r--platform/www/lib/scripts/editor.js205
-rw-r--r--platform/www/lib/scripts/fileuploader.js1249
-rw-r--r--platform/www/lib/scripts/fileuploaderextended.js345
-rw-r--r--platform/www/lib/scripts/helpers.js69
-rw-r--r--platform/www/lib/scripts/hotkeys.js302
-rw-r--r--platform/www/lib/scripts/index.html11
-rw-r--r--platform/www/lib/scripts/index.js16
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_55_fbf9ee_1x400.pngbin0 -> 393 bytes
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_65_ffffff_1x400.pngbin0 -> 265 bytes
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_75_dadada_1x400.pngbin0 -> 323 bytes
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_75_e6e6e6_1x400.pngbin0 -> 324 bytes
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_95_fef1ec_1x400.pngbin0 -> 390 bytes
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_highlight-soft_75_cccccc_1x100.pngbin0 -> 325 bytes
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_222222_256x240.pngbin0 -> 7025 bytes
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_2e83ff_256x240.pngbin0 -> 4676 bytes
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_454545_256x240.pngbin0 -> 7090 bytes
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_888888_256x240.pngbin0 -> 7111 bytes
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_cd0a0a_256x240.pngbin0 -> 4676 bytes
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui-theme/smoothness.css1311
-rw-r--r--platform/www/lib/scripts/jquery/jquery-ui.min.js13
-rw-r--r--platform/www/lib/scripts/jquery/jquery.cookie.js117
-rw-r--r--platform/www/lib/scripts/jquery/jquery.min.js2
-rwxr-xr-xplatform/www/lib/scripts/jquery/update.sh48
-rw-r--r--platform/www/lib/scripts/jquery/versions3
-rw-r--r--platform/www/lib/scripts/linkwiz.js339
-rw-r--r--platform/www/lib/scripts/locktimer.js151
-rw-r--r--platform/www/lib/scripts/media.js974
-rw-r--r--platform/www/lib/scripts/page.js201
-rw-r--r--platform/www/lib/scripts/qsearch.js191
-rw-r--r--platform/www/lib/scripts/script.js30
-rw-r--r--platform/www/lib/scripts/search.js48
-rw-r--r--platform/www/lib/scripts/textselection.js152
-rw-r--r--platform/www/lib/scripts/toolbar.js282
-rw-r--r--platform/www/lib/scripts/tree.js107
-rw-r--r--platform/www/lib/styles/all.css68
-rw-r--r--platform/www/lib/styles/feed.css63
-rw-r--r--platform/www/lib/styles/geshi.less144
-rw-r--r--platform/www/lib/styles/index.html11
-rw-r--r--platform/www/lib/styles/print.css15
-rw-r--r--platform/www/lib/styles/screen.css96
-rw-r--r--platform/www/lib/tpl/acervus/COPYING339
-rw-r--r--platform/www/lib/tpl/acervus/README.md3
-rw-r--r--platform/www/lib/tpl/acervus/conf/default.php11
-rw-r--r--platform/www/lib/tpl/acervus/conf/metadata.php11
-rw-r--r--platform/www/lib/tpl/acervus/css/basic.less506
-rw-r--r--platform/www/lib/tpl/acervus/css/content.less361
-rw-r--r--platform/www/lib/tpl/acervus/css/design.less301
-rw-r--r--platform/www/lib/tpl/acervus/css/fonts/Norwester-Regular.eotbin0 -> 13538 bytes
-rw-r--r--platform/www/lib/tpl/acervus/css/fonts/Norwester-Regular.ttfbin0 -> 13268 bytes
-rw-r--r--platform/www/lib/tpl/acervus/css/fonts/Norwester-Regular.woffbin0 -> 7228 bytes
-rw-r--r--platform/www/lib/tpl/acervus/css/hacks.css187
-rw-r--r--platform/www/lib/tpl/acervus/css/mobile.less136
-rw-r--r--platform/www/lib/tpl/acervus/css/print.less94
-rw-r--r--platform/www/lib/tpl/acervus/css/responsive.css13
-rw-r--r--platform/www/lib/tpl/acervus/css/structure.less64
-rw-r--r--platform/www/lib/tpl/acervus/detail.php93
-rw-r--r--platform/www/lib/tpl/acervus/images/apple-touch-icon.pngbin0 -> 23590 bytes
-rw-r--r--platform/www/lib/tpl/acervus/images/bg.pngbin0 -> 2948 bytes
-rw-r--r--platform/www/lib/tpl/acervus/images/bg2.pngbin0 -> 9103 bytes
-rw-r--r--platform/www/lib/tpl/acervus/images/bg3.pngbin0 -> 10549 bytes
-rw-r--r--platform/www/lib/tpl/acervus/images/favicon.icobin0 -> 371 bytes
-rw-r--r--platform/www/lib/tpl/acervus/images/ipari-simpleline.pngbin0 -> 11135 bytes
-rw-r--r--platform/www/lib/tpl/acervus/images/top.pngbin0 -> 4105 bytes
-rw-r--r--platform/www/lib/tpl/acervus/lang/de/lang.php15
-rw-r--r--platform/www/lib/tpl/acervus/lang/de/settings.php6
-rw-r--r--platform/www/lib/tpl/acervus/lang/de/style.txt1
-rw-r--r--platform/www/lib/tpl/acervus/lang/en/lang.php13
-rw-r--r--platform/www/lib/tpl/acervus/lang/en/settings.php7
-rw-r--r--platform/www/lib/tpl/acervus/lang/en/style.txt1
-rw-r--r--platform/www/lib/tpl/acervus/lang/es/lang.php5
-rw-r--r--platform/www/lib/tpl/acervus/lang/ko/lang.php15
-rw-r--r--platform/www/lib/tpl/acervus/lang/ko/settings.php7
-rw-r--r--platform/www/lib/tpl/acervus/lang/ko/style.txt1
-rw-r--r--platform/www/lib/tpl/acervus/lang/ru/lang.php12
-rw-r--r--platform/www/lib/tpl/acervus/lang/ru/settings.php3
-rw-r--r--platform/www/lib/tpl/acervus/lang/ru/style.txt1
-rw-r--r--platform/www/lib/tpl/acervus/main.php265
-rw-r--r--platform/www/lib/tpl/acervus/manager.dat2
-rw-r--r--platform/www/lib/tpl/acervus/mediamanager.php46
-rw-r--r--platform/www/lib/tpl/acervus/script.js81
-rw-r--r--platform/www/lib/tpl/acervus/style.ini86
-rw-r--r--platform/www/lib/tpl/acervus/template.info.txt6
-rw-r--r--platform/www/lib/tpl/acervus/tpl_functions.php91
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_admin.less64
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_diff.css137
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_edit.css141
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_fileuploader.css107
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_footnotes.css31
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_forms.css106
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_imgdetail.css38
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_links.css69
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_media_fullscreen.css541
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_media_popup.css208
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_modal.css94
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_recent.css75
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_search.less204
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_tabs.css84
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/_toc.css93
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/basic.less464
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/content.less393
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/design.less354
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/mobile.less332
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/pagetools.less124
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/print.css177
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/structure.less89
-rw-r--r--platform/www/lib/tpl/dokuwiki/css/usertools.less50
-rw-r--r--platform/www/lib/tpl/dokuwiki/detail.php105
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/apple-touch-icon.pngbin0 -> 6336 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/button-css.pngbin0 -> 297 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/button-donate.gifbin0 -> 187 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/button-dw.pngbin0 -> 398 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/button-html5.pngbin0 -> 305 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/button-php.gifbin0 -> 207 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/button-rss.pngbin0 -> 178 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/favicon.icobin0 -> 7406 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/license.txt5
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/logo.pngbin0 -> 3744 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/page-background.svg8
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/page-gradient.pngbin0 -> 209 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools-build.php125
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools-sprite.pngbin0 -> 7759 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/00_default.pngbin0 -> 494 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/01_edit.pngbin0 -> 519 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/02_create.pngbin0 -> 580 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/03_draft.pngbin0 -> 592 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/04_show.pngbin0 -> 321 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/05_source.pngbin0 -> 478 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/06_revert.pngbin0 -> 462 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/07_revisions.pngbin0 -> 769 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/08_backlink.pngbin0 -> 527 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/09_subscribe.pngbin0 -> 374 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/10_top.pngbin0 -> 297 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/11_mediamanager.pngbin0 -> 320 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/12_back.pngbin0 -> 288 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/pagetools/license.txt4
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/search.pngbin0 -> 307 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/toc-arrows.pngbin0 -> 225 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/toc-bullet.pngbin0 -> 113 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/images/usertools.pngbin0 -> 1428 bytes
-rw-r--r--platform/www/lib/tpl/dokuwiki/lang/en/lang.php13
-rw-r--r--platform/www/lib/tpl/dokuwiki/lang/en/style.txt4
-rw-r--r--platform/www/lib/tpl/dokuwiki/main.php87
-rw-r--r--platform/www/lib/tpl/dokuwiki/mediamanager.php44
-rw-r--r--platform/www/lib/tpl/dokuwiki/script.js89
-rw-r--r--platform/www/lib/tpl/dokuwiki/style.ini89
-rw-r--r--platform/www/lib/tpl/dokuwiki/template.info.txt7
-rw-r--r--platform/www/lib/tpl/dokuwiki/tpl_footer.php34
-rw-r--r--platform/www/lib/tpl/dokuwiki/tpl_header.php84
-rw-r--r--platform/www/lib/tpl/index.php71
1168 files changed, 46410 insertions, 0 deletions
diff --git a/platform/www/lib/exe/ajax.php b/platform/www/lib/exe/ajax.php
new file mode 100644
index 0000000..55f1c87
--- /dev/null
+++ b/platform/www/lib/exe/ajax.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * DokuWiki AJAX call handler
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__) . '/../../');
+require_once(DOKU_INC . 'inc/init.php');
+
+//close session
+session_write_close();
+
+// default header, ajax call may overwrite it later
+header('Content-Type: text/html; charset=utf-8');
+
+//call the requested function
+global $INPUT;
+if($INPUT->has('call')) {
+ $call = $INPUT->filter('utf8_stripspecials')->str('call');
+ new \dokuwiki\Ajax($call);
+} else {
+ http_status(404);
+}
diff --git a/platform/www/lib/exe/css.php b/platform/www/lib/exe/css.php
new file mode 100644
index 0000000..2ea2c09
--- /dev/null
+++ b/platform/www/lib/exe/css.php
@@ -0,0 +1,676 @@
+<?php
+/**
+ * DokuWiki StyleSheet creator
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+use dokuwiki\Cache\Cache;
+use dokuwiki\Extension\Event;
+
+if(!defined('DOKU_INC')) define('DOKU_INC', __DIR__ .'/../../');
+if(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching)
+if(!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT',1); // we gzip ourself here
+if(!defined('NL')) define('NL',"\n");
+require_once(DOKU_INC.'inc/init.php');
+
+// Main (don't run when UNIT test)
+if(!defined('SIMPLE_TEST')){
+ header('Content-Type: text/css; charset=utf-8');
+ css_out();
+}
+
+
+// ---------------------- functions ------------------------------
+
+/**
+ * Output all needed Styles
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function css_out(){
+ global $conf;
+ global $lang;
+ global $config_cascade;
+ global $INPUT;
+
+ if ($INPUT->str('s') == 'feed') {
+ $mediatypes = array('feed');
+ $type = 'feed';
+ } else {
+ $mediatypes = array('screen', 'all', 'print', 'speech');
+ $type = '';
+ }
+
+ // decide from where to get the template
+ $tpl = trim(preg_replace('/[^\w-]+/','',$INPUT->str('t')));
+ if(!$tpl) $tpl = $conf['template'];
+
+ // load style.ini
+ $styleUtil = new \dokuwiki\StyleUtils($tpl, $INPUT->bool('preview'));
+ $styleini = $styleUtil->cssStyleini();
+
+ // cache influencers
+ $tplinc = tpl_incdir($tpl);
+ $cache_files = getConfigFiles('main');
+ $cache_files[] = $tplinc.'style.ini';
+ $cache_files[] = DOKU_CONF."tpl/$tpl/style.ini";
+ $cache_files[] = __FILE__;
+ if($INPUT->bool('preview')) $cache_files[] = $conf['cachedir'].'/preview.ini';
+
+ // Array of needed files and their web locations, the latter ones
+ // are needed to fix relative paths in the stylesheets
+ $media_files = array();
+ foreach($mediatypes as $mediatype) {
+ $files = array();
+
+ // load core styles
+ $files[DOKU_INC.'lib/styles/'.$mediatype.'.css'] = DOKU_BASE.'lib/styles/';
+
+ // load jQuery-UI theme
+ if ($mediatype == 'screen') {
+ $files[DOKU_INC.'lib/scripts/jquery/jquery-ui-theme/smoothness.css'] =
+ DOKU_BASE.'lib/scripts/jquery/jquery-ui-theme/';
+ }
+ // load plugin styles
+ $files = array_merge($files, css_pluginstyles($mediatype));
+ // load template styles
+ if (isset($styleini['stylesheets'][$mediatype])) {
+ $files = array_merge($files, $styleini['stylesheets'][$mediatype]);
+ }
+ // load user styles
+ if(is_array($config_cascade['userstyle'][$mediatype])) {
+ foreach($config_cascade['userstyle'][$mediatype] as $userstyle) {
+ $files[$userstyle] = DOKU_BASE;
+ }
+ }
+
+ // Let plugins decide to either put more styles here or to remove some
+ $media_files[$mediatype] = css_filewrapper($mediatype, $files);
+ $CSSEvt = new Event('CSS_STYLES_INCLUDED', $media_files[$mediatype]);
+
+ // Make it preventable.
+ if ( $CSSEvt->advise_before() ) {
+ $cache_files = array_merge($cache_files, array_keys($media_files[$mediatype]['files']));
+ } else {
+ // unset if prevented. Nothing will be printed for this mediatype.
+ unset($media_files[$mediatype]);
+ }
+
+ // finish event.
+ $CSSEvt->advise_after();
+ }
+
+ // The generated script depends on some dynamic options
+ $cache = new Cache(
+ 'styles' .
+ $_SERVER['HTTP_HOST'] .
+ $_SERVER['SERVER_PORT'] .
+ $INPUT->bool('preview') .
+ DOKU_BASE .
+ $tpl .
+ $type,
+ '.css'
+ );
+ $cache->setEvent('CSS_CACHE_USE');
+
+ // check cache age & handle conditional request
+ // This may exit if a cache can be used
+ $cache_ok = $cache->useCache(array('files' => $cache_files));
+ http_cached($cache->cache, $cache_ok);
+
+ // start output buffering
+ ob_start();
+
+ // Fire CSS_STYLES_INCLUDED for one last time to let the
+ // plugins decide whether to include the DW default styles.
+ // This can be done by preventing the Default.
+ $media_files['DW_DEFAULT'] = css_filewrapper('DW_DEFAULT');
+ Event::createAndTrigger('CSS_STYLES_INCLUDED', $media_files['DW_DEFAULT'], 'css_defaultstyles');
+
+ // build the stylesheet
+ foreach ($mediatypes as $mediatype) {
+
+ // Check if there is a wrapper set for this type.
+ if ( !isset($media_files[$mediatype]) ) {
+ continue;
+ }
+
+ $cssData = $media_files[$mediatype];
+
+ // Print the styles.
+ print NL;
+ if ( $cssData['encapsulate'] === true ) print $cssData['encapsulationPrefix'] . ' {';
+ print '/* START '.$cssData['mediatype'].' styles */'.NL;
+
+ // load files
+ foreach($cssData['files'] as $file => $location){
+ $display = str_replace(fullpath(DOKU_INC), '', fullpath($file));
+ print "\n/* XXXXXXXXX $display XXXXXXXXX */\n";
+ print css_loadfile($file, $location);
+ }
+
+ print NL;
+ if ( $cssData['encapsulate'] === true ) print '} /* /@media ';
+ else print '/*';
+ print ' END '.$cssData['mediatype'].' styles */'.NL;
+ }
+
+ // end output buffering and get contents
+ $css = ob_get_contents();
+ ob_end_clean();
+
+ // strip any source maps
+ stripsourcemaps($css);
+
+ // apply style replacements
+ $css = css_applystyle($css, $styleini['replacements']);
+
+ // parse less
+ $css = css_parseless($css);
+
+ // compress whitespace and comments
+ if($conf['compress']){
+ $css = css_compress($css);
+ }
+
+ // embed small images right into the stylesheet
+ if($conf['cssdatauri']){
+ $base = preg_quote(DOKU_BASE,'#');
+ $css = preg_replace_callback('#(url\([ \'"]*)('.$base.')(.*?(?:\.(png|gif)))#i','css_datauri',$css);
+ }
+
+ http_cached_finish($cache->cache, $css);
+}
+
+/**
+ * Uses phpless to parse LESS in our CSS
+ *
+ * most of this function is error handling to show a nice useful error when
+ * LESS compilation fails
+ *
+ * @param string $css
+ * @return string
+ */
+function css_parseless($css) {
+ global $conf;
+
+ $less = new lessc();
+ $less->importDir = array(DOKU_INC);
+ $less->setPreserveComments(!$conf['compress']);
+
+ if (defined('DOKU_UNITTEST')){
+ $less->importDir[] = TMP_DIR;
+ }
+
+ try {
+ return $less->compile($css);
+ } catch(Exception $e) {
+ // get exception message
+ $msg = str_replace(array("\n", "\r", "'"), array(), $e->getMessage());
+
+ // try to use line number to find affected file
+ if(preg_match('/line: (\d+)$/', $msg, $m)){
+ $msg = substr($msg, 0, -1* strlen($m[0])); //remove useless linenumber
+ $lno = $m[1];
+
+ // walk upwards to last include
+ $lines = explode("\n", $css);
+ for($i=$lno-1; $i>=0; $i--){
+ if(preg_match('/\/(\* XXXXXXXXX )(.*?)( XXXXXXXXX \*)\//', $lines[$i], $m)){
+ // we found it, add info to message
+ $msg .= ' in '.$m[2].' at line '.($lno-$i);
+ break;
+ }
+ }
+ }
+
+ // something went wrong
+ $error = 'A fatal error occured during compilation of the CSS files. '.
+ 'If you recently installed a new plugin or template it '.
+ 'might be broken and you should try disabling it again. ['.$msg.']';
+
+ echo ".dokuwiki:before {
+ content: '$error';
+ background-color: red;
+ display: block;
+ background-color: #fcc;
+ border-color: #ebb;
+ color: #000;
+ padding: 0.5em;
+ }";
+
+ exit;
+ }
+}
+
+/**
+ * Does placeholder replacements in the style according to
+ * the ones defined in a templates style.ini file
+ *
+ * This also adds the ini defined placeholders as less variables
+ * (sans the surrounding __ and with a ini_ prefix)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string $css
+ * @param array $replacements array(placeholder => value)
+ * @return string
+ */
+function css_applystyle($css, $replacements) {
+ // we convert ini replacements to LESS variable names
+ // and build a list of variable: value; pairs
+ $less = '';
+ foreach((array) $replacements as $key => $value) {
+ $lkey = trim($key, '_');
+ $lkey = '@ini_'.$lkey;
+ $less .= "$lkey: $value;\n";
+
+ $replacements[$key] = $lkey;
+ }
+
+ // we now replace all old ini replacements with LESS variables
+ $css = strtr($css, $replacements);
+
+ // now prepend the list of LESS variables as the very first thing
+ $css = $less.$css;
+ return $css;
+}
+
+/**
+ * Wrapper for the files, content and mediatype for the event CSS_STYLES_INCLUDED
+ *
+ * @author Gerry Weißbach <gerry.w@gammaproduction.de>
+ *
+ * @param string $mediatype type ofthe current media files/content set
+ * @param array $files set of files that define the current mediatype
+ * @return array
+ */
+function css_filewrapper($mediatype, $files=array()){
+ return array(
+ 'files' => $files,
+ 'mediatype' => $mediatype,
+ 'encapsulate' => $mediatype != 'all',
+ 'encapsulationPrefix' => '@media '.$mediatype
+ );
+}
+
+/**
+ * Prints the @media encapsulated default styles of DokuWiki
+ *
+ * @author Gerry Weißbach <gerry.w@gammaproduction.de>
+ *
+ * This function is being called by a CSS_STYLES_INCLUDED event
+ * The event can be distinguished by the mediatype which is:
+ * DW_DEFAULT
+ */
+function css_defaultstyles(){
+ // print the default classes for interwiki links and file downloads
+ print '@media screen {';
+ css_interwiki();
+ css_filetypes();
+ print '}';
+}
+
+/**
+ * Prints classes for interwikilinks
+ *
+ * Interwiki links have two classes: 'interwiki' and 'iw_$name>' where
+ * $name is the identifier given in the config. All Interwiki links get
+ * an default style with a default icon. If a special icon is available
+ * for an interwiki URL it is set in it's own class. Both classes can be
+ * overwritten in the template or userstyles.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function css_interwiki(){
+
+ // default style
+ echo 'a.interwiki {';
+ echo ' background: transparent url('.DOKU_BASE.'lib/images/interwiki.png) 0px 1px no-repeat;';
+ echo ' padding: 1px 0px 1px 16px;';
+ echo '}';
+
+ // additional styles when icon available
+ $iwlinks = getInterwiki();
+ foreach(array_keys($iwlinks) as $iw){
+ $class = preg_replace('/[^_\-a-z0-9]+/i','_',$iw);
+ if(file_exists(DOKU_INC.'lib/images/interwiki/'.$iw.'.png')){
+ echo "a.iw_$class {";
+ echo ' background-image: url('.DOKU_BASE.'lib/images/interwiki/'.$iw.'.png)';
+ echo '}';
+ }elseif(file_exists(DOKU_INC.'lib/images/interwiki/'.$iw.'.gif')){
+ echo "a.iw_$class {";
+ echo ' background-image: url('.DOKU_BASE.'lib/images/interwiki/'.$iw.'.gif)';
+ echo '}';
+ }
+ }
+}
+
+/**
+ * Prints classes for file download links
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function css_filetypes(){
+
+ // default style
+ echo '.mediafile {';
+ echo ' background: transparent url('.DOKU_BASE.'lib/images/fileicons/file.png) 0px 1px no-repeat;';
+ echo ' padding-left: 18px;';
+ echo ' padding-bottom: 1px;';
+ echo '}';
+
+ // additional styles when icon available
+ // scan directory for all icons
+ $exts = array();
+ if($dh = opendir(DOKU_INC.'lib/images/fileicons')){
+ while(false !== ($file = readdir($dh))){
+ if(preg_match('/([_\-a-z0-9]+(?:\.[_\-a-z0-9]+)*?)\.(png|gif)/i',$file,$match)){
+ $ext = strtolower($match[1]);
+ $type = '.'.strtolower($match[2]);
+ if($ext!='file' && (!isset($exts[$ext]) || $type=='.png')){
+ $exts[$ext] = $type;
+ }
+ }
+ }
+ closedir($dh);
+ }
+ foreach($exts as $ext=>$type){
+ $class = preg_replace('/[^_\-a-z0-9]+/','_',$ext);
+ echo ".mf_$class {";
+ echo ' background-image: url('.DOKU_BASE.'lib/images/fileicons/'.$ext.$type.')';
+ echo '}';
+ }
+}
+
+/**
+ * Loads a given file and fixes relative URLs with the
+ * given location prefix
+ *
+ * @param string $file file system path
+ * @param string $location
+ * @return string
+ */
+function css_loadfile($file,$location=''){
+ $css_file = new DokuCssFile($file);
+ return $css_file->load($location);
+}
+
+/**
+ * Helper class to abstract loading of css/less files
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+class DokuCssFile {
+
+ protected $filepath; // file system path to the CSS/Less file
+ protected $location; // base url location of the CSS/Less file
+ protected $relative_path = null;
+
+ public function __construct($file) {
+ $this->filepath = $file;
+ }
+
+ /**
+ * Load the contents of the css/less file and adjust any relative paths/urls (relative to this file) to be
+ * relative to the dokuwiki root: the web root (DOKU_BASE) for most files; the file system root (DOKU_INC)
+ * for less files.
+ *
+ * @param string $location base url for this file
+ * @return string the CSS/Less contents of the file
+ */
+ public function load($location='') {
+ if (!file_exists($this->filepath)) return '';
+
+ $css = io_readFile($this->filepath);
+ if (!$location) return $css;
+
+ $this->location = $location;
+
+ $css = preg_replace_callback('#(url\( *)([\'"]?)(.*?)(\2)( *\))#',array($this,'replacements'),$css);
+ $css = preg_replace_callback('#(@import\s+)([\'"])(.*?)(\2)#',array($this,'replacements'),$css);
+
+ return $css;
+ }
+
+ /**
+ * Get the relative file system path of this file, relative to dokuwiki's root folder, DOKU_INC
+ *
+ * @return string relative file system path
+ */
+ protected function getRelativePath(){
+
+ if (is_null($this->relative_path)) {
+ $basedir = array(DOKU_INC);
+
+ // during testing, files may be found relative to a second base dir, TMP_DIR
+ if (defined('DOKU_UNITTEST')) {
+ $basedir[] = realpath(TMP_DIR);
+ }
+
+ $basedir = array_map('preg_quote_cb', $basedir);
+ $regex = '/^('.join('|',$basedir).')/';
+ $this->relative_path = preg_replace($regex, '', dirname($this->filepath));
+ }
+
+ return $this->relative_path;
+ }
+
+ /**
+ * preg_replace callback to adjust relative urls from relative to this file to relative
+ * to the appropriate dokuwiki root location as described in the code
+ *
+ * @param array see http://php.net/preg_replace_callback
+ * @return string see http://php.net/preg_replace_callback
+ */
+ public function replacements($match) {
+
+ if (preg_match('#^(/|data:|https?://)#', $match[3])) { // not a relative url? - no adjustment required
+ return $match[0];
+ } elseif (substr($match[3], -5) == '.less') { // a less file import? - requires a file system location
+ if ($match[3][0] != '/') {
+ $match[3] = $this->getRelativePath() . '/' . $match[3];
+ }
+ } else { // everything else requires a url adjustment
+ $match[3] = $this->location . $match[3];
+ }
+
+ return join('',array_slice($match,1));
+ }
+}
+
+/**
+ * Convert local image URLs to data URLs if the filesize is small
+ *
+ * Callback for preg_replace_callback
+ *
+ * @param array $match
+ * @return string
+ */
+function css_datauri($match){
+ global $conf;
+
+ $pre = unslash($match[1]);
+ $base = unslash($match[2]);
+ $url = unslash($match[3]);
+ $ext = unslash($match[4]);
+
+ $local = DOKU_INC.$url;
+ $size = @filesize($local);
+ if($size && $size < $conf['cssdatauri']){
+ $data = base64_encode(file_get_contents($local));
+ }
+ if (!empty($data)){
+ $url = 'data:image/'.$ext.';base64,'.$data;
+ }else{
+ $url = $base.$url;
+ }
+ return $pre.$url;
+}
+
+
+/**
+ * Returns a list of possible Plugin Styles (no existance check here)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string $mediatype
+ * @return array
+ */
+function css_pluginstyles($mediatype='screen'){
+ $list = array();
+ $plugins = plugin_list();
+ foreach ($plugins as $p){
+ $list[DOKU_PLUGIN."$p/$mediatype.css"] = DOKU_BASE."lib/plugins/$p/";
+ $list[DOKU_PLUGIN."$p/$mediatype.less"] = DOKU_BASE."lib/plugins/$p/";
+ // alternative for screen.css
+ if ($mediatype=='screen') {
+ $list[DOKU_PLUGIN."$p/style.css"] = DOKU_BASE."lib/plugins/$p/";
+ $list[DOKU_PLUGIN."$p/style.less"] = DOKU_BASE."lib/plugins/$p/";
+ }
+ }
+ return $list;
+}
+
+/**
+ * Very simple CSS optimizer
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string $css
+ * @return string
+ */
+function css_compress($css){
+ // replace quoted strings with placeholder
+ $quote_storage = [];
+
+ $quote_cb = function ($match) use (&$quote_storage) {
+ $quote_storage[] = $match[0];
+ return '"STR'.(count($quote_storage)-1).'"';
+ };
+
+ $css = preg_replace_callback('/(([\'"]).*?(?<!\\\\)\2)/', $quote_cb, $css);
+
+ // strip comments through a callback
+ $css = preg_replace_callback('#(/\*)(.*?)(\*/)#s','css_comment_cb',$css);
+
+ // strip (incorrect but common) one line comments
+ $css = preg_replace_callback('/^.*\/\/.*$/m','css_onelinecomment_cb',$css);
+
+ // strip whitespaces
+ $css = preg_replace('![\r\n\t ]+!',' ',$css);
+ $css = preg_replace('/ ?([;,{}\/]) ?/','\\1',$css);
+ $css = preg_replace('/ ?: /',':',$css);
+
+ // number compression
+ $css = preg_replace(
+ '/([: ])0+(\.\d+?)0*((?:pt|pc|in|mm|cm|em|ex|px)\b|%)(?=[^\{]*[;\}])/',
+ '$1$2$3',
+ $css
+ ); // "0.1em" to ".1em", "1.10em" to "1.1em"
+ $css = preg_replace(
+ '/([: ])\.(0)+((?:pt|pc|in|mm|cm|em|ex|px)\b|%)(?=[^\{]*[;\}])/',
+ '$1$2',
+ $css
+ ); // ".0em" to "0"
+ $css = preg_replace(
+ '/([: ]0)0*(\.0*)?((?:pt|pc|in|mm|cm|em|ex|px)(?=[^\{]*[;\}])\b|%)/',
+ '$1',
+ $css
+ ); // "0.0em" to "0"
+ $css = preg_replace(
+ '/([: ]\d+)(\.0*)((?:pt|pc|in|mm|cm|em|ex|px)(?=[^\{]*[;\}])\b|%)/',
+ '$1$3',
+ $css
+ ); // "1.0em" to "1em"
+ $css = preg_replace(
+ '/([: ])0+(\d+|\d*\.\d+)((?:pt|pc|in|mm|cm|em|ex|px)(?=[^\{]*[;\}])\b|%)/',
+ '$1$2$3',
+ $css
+ ); // "001em" to "1em"
+
+ // shorten attributes (1em 1em 1em 1em -> 1em)
+ $css = preg_replace(
+ '/(?<![\w\-])((?:margin|padding|border|border-(?:width|radius)):)([\w\.]+)( \2)+(?=[;\}]| !)/',
+ '$1$2',
+ $css
+ ); // "1em 1em 1em 1em" to "1em"
+ $css = preg_replace(
+ '/(?<![\w\-])((?:margin|padding|border|border-(?:width)):)([\w\.]+) ([\w\.]+) \2 \3(?=[;\}]| !)/',
+ '$1$2 $3',
+ $css
+ ); // "1em 2em 1em 2em" to "1em 2em"
+
+ // shorten colors
+ $css = preg_replace(
+ "/#([0-9a-fA-F]{1})\\1([0-9a-fA-F]{1})\\2([0-9a-fA-F]{1})\\3(?=[^\{]*[;\}])/",
+ "#\\1\\2\\3",
+ $css
+ );
+
+ // replace back protected strings
+ $quote_back_cb = function ($match) use (&$quote_storage) {
+ return $quote_storage[$match[1]];
+ };
+
+ $css = preg_replace_callback('/"STR(\d+)"/', $quote_back_cb, $css);
+ $css = trim($css);
+
+ return $css;
+}
+
+/**
+ * Callback for css_compress()
+ *
+ * Keeps short comments (< 5 chars) to maintain typical browser hacks
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param array $matches
+ * @return string
+ */
+function css_comment_cb($matches){
+ if(strlen($matches[2]) > 4) return '';
+ return $matches[0];
+}
+
+/**
+ * Callback for css_compress()
+ *
+ * Strips one line comments but makes sure it will not destroy url() constructs with slashes
+ *
+ * @param array $matches
+ * @return string
+ */
+function css_onelinecomment_cb($matches) {
+ $line = $matches[0];
+
+ $i = 0;
+ $len = strlen($line);
+
+ while ($i< $len){
+ $nextcom = strpos($line, '//', $i);
+ $nexturl = stripos($line, 'url(', $i);
+
+ if($nextcom === false) {
+ // no more comments, we're done
+ $i = $len;
+ break;
+ }
+
+ if($nexturl === false || $nextcom < $nexturl) {
+ // no url anymore, strip comment and be done
+ $i = $nextcom;
+ break;
+ }
+
+ // we have an upcoming url
+ $i = strpos($line, ')', $nexturl);
+ }
+
+ return substr($line, 0, $i);
+}
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/exe/detail.php b/platform/www/lib/exe/detail.php
new file mode 100644
index 0000000..a6cffa7
--- /dev/null
+++ b/platform/www/lib/exe/detail.php
@@ -0,0 +1,42 @@
+<?php
+
+use dokuwiki\Extension\Event;
+
+if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../');
+define('DOKU_MEDIADETAIL',1);
+require_once(DOKU_INC.'inc/init.php');
+
+$IMG = getID('media');
+$ID = cleanID($INPUT->str('id'));
+$REV = $INPUT->int('rev');
+
+// this makes some general info available as well as the info about the
+// "parent" page
+$INFO = array_merge(pageinfo(),mediainfo());
+
+$tmp = array();
+Event::createAndTrigger('DETAIL_STARTED', $tmp);
+
+//close session
+session_write_close();
+
+$ERROR = false;
+// check image permissions
+$AUTH = auth_quickaclcheck($IMG);
+if($AUTH >= AUTH_READ){
+ // check if image exists
+ $SRC = mediaFN($IMG,$REV);
+ if(!file_exists($SRC)){
+ //doesn't exist!
+ http_status(404);
+ $ERROR = 'File not found';
+ }
+}else{
+ // no auth
+ $ERROR = p_locale_xhtml('denied');
+}
+
+//start output and load template
+header('Content-Type: text/html; charset=utf-8');
+include(template('detail.php'));
+
diff --git a/platform/www/lib/exe/fetch.php b/platform/www/lib/exe/fetch.php
new file mode 100644
index 0000000..5026b80
--- /dev/null
+++ b/platform/www/lib/exe/fetch.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * DokuWiki media passthrough file
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+use dokuwiki\Extension\Event;
+
+if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__).'/../../');
+if (!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT', 1);
+require_once(DOKU_INC.'inc/init.php');
+session_write_close(); //close session
+
+require_once(DOKU_INC.'inc/fetch.functions.php');
+
+if (defined('SIMPLE_TEST')) {
+ $INPUT = new \dokuwiki\Input\Input();
+}
+
+// BEGIN main
+ $mimetypes = getMimeTypes();
+
+ //get input
+ $MEDIA = stripctl(getID('media', false)); // no cleaning except control chars - maybe external
+ $CACHE = calc_cache($INPUT->str('cache'));
+ $WIDTH = $INPUT->int('w');
+ $HEIGHT = $INPUT->int('h');
+ $REV = & $INPUT->ref('rev');
+ //sanitize revision
+ $REV = preg_replace('/[^0-9]/', '', $REV);
+
+ list($EXT, $MIME, $DL) = mimetype($MEDIA, false);
+ if($EXT === false) {
+ $EXT = 'unknown';
+ $MIME = 'application/octet-stream';
+ $DL = true;
+ }
+
+ // check for permissions, preconditions and cache external files
+ list($STATUS, $STATUSMESSAGE) = checkFileStatus($MEDIA, $FILE, $REV, $WIDTH, $HEIGHT);
+
+ // prepare data for plugin events
+ $data = array(
+ 'media' => $MEDIA,
+ 'file' => $FILE,
+ 'orig' => $FILE,
+ 'mime' => $MIME,
+ 'download' => $DL,
+ 'cache' => $CACHE,
+ 'ext' => $EXT,
+ 'width' => $WIDTH,
+ 'height' => $HEIGHT,
+ 'status' => $STATUS,
+ 'statusmessage' => $STATUSMESSAGE,
+ 'ispublic' => media_ispublic($MEDIA),
+ );
+
+ // handle the file status
+ $evt = new Event('FETCH_MEDIA_STATUS', $data);
+ if($evt->advise_before()) {
+ // redirects
+ if($data['status'] > 300 && $data['status'] <= 304) {
+ if (defined('SIMPLE_TEST')) return; //TestResponse doesn't recognize redirects
+ send_redirect($data['statusmessage']);
+ }
+ // send any non 200 status
+ if($data['status'] != 200) {
+ http_status($data['status'], $data['statusmessage']);
+ }
+ // die on errors
+ if($data['status'] > 203) {
+ print $data['statusmessage'];
+ if (defined('SIMPLE_TEST')) return;
+ exit;
+ }
+ }
+ $evt->advise_after();
+ unset($evt);
+
+ //handle image resizing/cropping
+ $evt = new Event('MEDIA_RESIZE', $data);
+ if($evt->advise_before()) {
+ if((substr($MIME, 0, 5) == 'image') && ($WIDTH || $HEIGHT)) {
+ if($HEIGHT && $WIDTH) {
+ $data['file'] = $FILE = media_crop_image($data['file'], $EXT, $WIDTH, $HEIGHT);
+ } else {
+ $data['file'] = $FILE = media_resize_image($data['file'], $EXT, $WIDTH, $HEIGHT);
+ }
+ }
+ }
+ $evt->advise_after();
+ unset($evt);
+
+ // finally send the file to the client
+ $evt = new Event('MEDIA_SENDFILE', $data);
+ if($evt->advise_before()) {
+ sendFile($data['file'], $data['mime'], $data['download'], $data['cache'], $data['ispublic'], $data['orig']);
+ }
+ // Do something after the download finished.
+ $evt->advise_after(); // will not be emitted on 304 or x-sendfile
+
+// END DO main
+
+//Setup VIM: ex: et ts=2 :
diff --git a/platform/www/lib/exe/index.html b/platform/www/lib/exe/index.html
new file mode 100644
index 0000000..977f90e
--- /dev/null
+++ b/platform/www/lib/exe/index.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="refresh" content="0; URL=../../" />
+<meta name="robots" content="noindex" />
+<title>nothing here...</title>
+</head>
+<body>
+<!-- this is just here to prevent directory browsing -->
+</body>
+</html>
diff --git a/platform/www/lib/exe/indexer.php b/platform/www/lib/exe/indexer.php
new file mode 100644
index 0000000..c5f6e40
--- /dev/null
+++ b/platform/www/lib/exe/indexer.php
@@ -0,0 +1,5 @@
+<?php
+/**
+ * @deprecated 2020-06-04 use taskrunner instead
+ */
+include __DIR__ . '/taskrunner.php';
diff --git a/platform/www/lib/exe/jquery.php b/platform/www/lib/exe/jquery.php
new file mode 100644
index 0000000..b8638ec
--- /dev/null
+++ b/platform/www/lib/exe/jquery.php
@@ -0,0 +1,44 @@
+<?php
+
+use dokuwiki\Cache\Cache;
+
+if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__) . '/../../');
+if(!defined('NOSESSION')) define('NOSESSION', true); // we do not use a session or authentication here (better caching)
+if(!defined('NL')) define('NL', "\n");
+if(!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT', 1); // we gzip ourself here
+require_once(DOKU_INC . 'inc/init.php');
+
+// MAIN
+header('Content-Type: application/javascript; charset=utf-8');
+jquery_out();
+
+/**
+ * Delivers the jQuery JavaScript
+ *
+ * We do absolutely nothing fancy here but concatenating the different files
+ * and handling conditional and gzipped requests
+ *
+ * uses cache or fills it
+ */
+function jquery_out() {
+ $cache = new Cache('jquery', '.js');
+ $files = array(
+ DOKU_INC . 'lib/scripts/jquery/jquery.min.js',
+ DOKU_INC . 'lib/scripts/jquery/jquery-ui.min.js',
+ );
+ $cache_files = $files;
+ $cache_files[] = __FILE__;
+
+ // check cache age & handle conditional request
+ // This may exit if a cache can be used
+ $cache_ok = $cache->useCache(array('files' => $cache_files));
+ http_cached($cache->cache, $cache_ok);
+
+ $js = '';
+ foreach($files as $file) {
+ $js .= file_get_contents($file)."\n";
+ }
+ stripsourcemaps($js);
+
+ http_cached_finish($cache->cache, $js);
+}
diff --git a/platform/www/lib/exe/js.php b/platform/www/lib/exe/js.php
new file mode 100644
index 0000000..04abec6
--- /dev/null
+++ b/platform/www/lib/exe/js.php
@@ -0,0 +1,490 @@
+<?php
+/**
+ * DokuWiki JavaScript creator
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+use dokuwiki\Cache\Cache;
+use dokuwiki\Extension\Event;
+
+if(!defined('DOKU_INC')) define('DOKU_INC', __DIR__ .'/../../');
+if(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching)
+if(!defined('NL')) define('NL',"\n");
+if(!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT',1); // we gzip ourself here
+require_once(DOKU_INC.'inc/init.php');
+
+// Main (don't run when UNIT test)
+if(!defined('SIMPLE_TEST')){
+ header('Content-Type: application/javascript; charset=utf-8');
+ js_out();
+}
+
+
+// ---------------------- functions ------------------------------
+
+/**
+ * Output all needed JavaScript
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function js_out(){
+ global $conf;
+ global $lang;
+ global $config_cascade;
+ global $INPUT;
+
+ // decide from where to get the template
+ $tpl = trim(preg_replace('/[^\w-]+/','',$INPUT->str('t')));
+ if(!$tpl) $tpl = $conf['template'];
+
+ // array of core files
+ $files = array(
+ DOKU_INC.'lib/scripts/jquery/jquery.cookie.js',
+ DOKU_INC.'inc/lang/'.$conf['lang'].'/jquery.ui.datepicker.js',
+ DOKU_INC."lib/scripts/fileuploader.js",
+ DOKU_INC."lib/scripts/fileuploaderextended.js",
+ DOKU_INC.'lib/scripts/helpers.js',
+ DOKU_INC.'lib/scripts/delay.js',
+ DOKU_INC.'lib/scripts/cookie.js',
+ DOKU_INC.'lib/scripts/script.js',
+ DOKU_INC.'lib/scripts/qsearch.js',
+ DOKU_INC.'lib/scripts/search.js',
+ DOKU_INC.'lib/scripts/tree.js',
+ DOKU_INC.'lib/scripts/index.js',
+ DOKU_INC.'lib/scripts/textselection.js',
+ DOKU_INC.'lib/scripts/toolbar.js',
+ DOKU_INC.'lib/scripts/edit.js',
+ DOKU_INC.'lib/scripts/editor.js',
+ DOKU_INC.'lib/scripts/locktimer.js',
+ DOKU_INC.'lib/scripts/linkwiz.js',
+ DOKU_INC.'lib/scripts/media.js',
+ DOKU_INC.'lib/scripts/compatibility.js',
+# disabled for FS#1958 DOKU_INC.'lib/scripts/hotkeys.js',
+ DOKU_INC.'lib/scripts/behaviour.js',
+ DOKU_INC.'lib/scripts/page.js',
+ tpl_incdir($tpl).'script.js',
+ );
+
+ // add possible plugin scripts and userscript
+ $files = array_merge($files,js_pluginscripts());
+ if(is_array($config_cascade['userscript']['default'])) {
+ foreach($config_cascade['userscript']['default'] as $userscript) {
+ $files[] = $userscript;
+ }
+ }
+
+ // Let plugins decide to either put more scripts here or to remove some
+ Event::createAndTrigger('JS_SCRIPT_LIST', $files);
+
+ // The generated script depends on some dynamic options
+ $cache = new Cache('scripts'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'].md5(serialize($files)),'.js');
+ $cache->setEvent('JS_CACHE_USE');
+
+ $cache_files = array_merge($files, getConfigFiles('main'));
+ $cache_files[] = __FILE__;
+
+ // check cache age & handle conditional request
+ // This may exit if a cache can be used
+ $cache_ok = $cache->useCache(array('files' => $cache_files));
+ http_cached($cache->cache, $cache_ok);
+
+ // start output buffering and build the script
+ ob_start();
+
+ // add some global variables
+ print "var DOKU_BASE = '".DOKU_BASE."';";
+ print "var DOKU_TPL = '".tpl_basedir($tpl)."';";
+ print "var DOKU_COOKIE_PARAM = " . json_encode(
+ array(
+ 'path' => empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'],
+ 'secure' => $conf['securecookie'] && is_ssl()
+ )).";";
+ // FIXME: Move those to JSINFO
+ print "Object.defineProperty(window, 'DOKU_UHN', { get: function() {".
+ "console.warn('Using DOKU_UHN is deprecated. Please use JSINFO.useHeadingNavigation instead');".
+ "return JSINFO.useHeadingNavigation; } });";
+ print "Object.defineProperty(window, 'DOKU_UHC', { get: function() {".
+ "console.warn('Using DOKU_UHC is deprecated. Please use JSINFO.useHeadingContent instead');".
+ "return JSINFO.useHeadingContent; } });";
+
+ // load JS specific translations
+ $lang['js']['plugins'] = js_pluginstrings();
+ $templatestrings = js_templatestrings($tpl);
+ if(!empty($templatestrings)) {
+ $lang['js']['template'] = $templatestrings;
+ }
+ echo 'LANG = '.json_encode($lang['js']).";\n";
+
+ // load toolbar
+ toolbar_JSdefines('toolbar');
+
+ // load files
+ foreach($files as $file){
+ if(!file_exists($file)) continue;
+ $ismin = (substr($file,-7) == '.min.js');
+ $debugjs = ($conf['allowdebug'] && strpos($file, DOKU_INC.'lib/scripts/') !== 0);
+
+ echo "\n\n/* XXXXXXXXXX begin of ".str_replace(DOKU_INC, '', $file) ." XXXXXXXXXX */\n\n";
+ if($ismin) echo "\n/* BEGIN NOCOMPRESS */\n";
+ if ($debugjs) echo "\ntry {\n";
+ js_load($file);
+ if ($debugjs) echo "\n} catch (e) {\n logError(e, '".str_replace(DOKU_INC, '', $file)."');\n}\n";
+ if($ismin) echo "\n/* END NOCOMPRESS */\n";
+ echo "\n\n/* XXXXXXXXXX end of " . str_replace(DOKU_INC, '', $file) . " XXXXXXXXXX */\n\n";
+ }
+
+ // init stuff
+ if($conf['locktime'] != 0){
+ js_runonstart("dw_locktimer.init(".($conf['locktime'] - 60).",".$conf['usedraft'].")");
+ }
+ // init hotkeys - must have been done after init of toolbar
+# disabled for FS#1958 js_runonstart('initializeHotkeys()');
+
+ // end output buffering and get contents
+ $js = ob_get_contents();
+ ob_end_clean();
+
+ // strip any source maps
+ stripsourcemaps($js);
+
+ // compress whitespace and comments
+ if($conf['compress']){
+ $js = js_compress($js);
+ }
+
+ $js .= "\n"; // https://bugzilla.mozilla.org/show_bug.cgi?id=316033
+
+ http_cached_finish($cache->cache, $js);
+}
+
+/**
+ * Load the given file, handle include calls and print it
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string $file filename path to file
+ */
+function js_load($file){
+ if(!file_exists($file)) return;
+ static $loaded = array();
+
+ $data = io_readFile($file);
+ while(preg_match('#/\*\s*DOKUWIKI:include(_once)?\s+([\w\.\-_/]+)\s*\*/#',$data,$match)){
+ $ifile = $match[2];
+
+ // is it a include_once?
+ if($match[1]){
+ $base = \dokuwiki\Utf8\PhpString::basename($ifile);
+ if(array_key_exists($base, $loaded) && $loaded[$base] === true){
+ $data = str_replace($match[0], '' ,$data);
+ continue;
+ }
+ $loaded[$base] = true;
+ }
+
+ if($ifile[0] != '/') $ifile = dirname($file).'/'.$ifile;
+
+ if(file_exists($ifile)){
+ $idata = io_readFile($ifile);
+ }else{
+ $idata = '';
+ }
+ $data = str_replace($match[0],$idata,$data);
+ }
+ echo "$data\n";
+}
+
+/**
+ * Returns a list of possible Plugin Scripts (no existance check here)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @return array
+ */
+function js_pluginscripts(){
+ $list = array();
+ $plugins = plugin_list();
+ foreach ($plugins as $p){
+ $list[] = DOKU_PLUGIN."$p/script.js";
+ }
+ return $list;
+}
+
+/**
+ * Return an two-dimensional array with strings from the language file of each plugin.
+ *
+ * - $lang['js'] must be an array.
+ * - Nothing is returned for plugins without an entry for $lang['js']
+ *
+ * @author Gabriel Birke <birke@d-scribe.de>
+ *
+ * @return array
+ */
+function js_pluginstrings() {
+ global $conf, $config_cascade;
+ $pluginstrings = array();
+ $plugins = plugin_list();
+ foreach($plugins as $p) {
+ $path = DOKU_PLUGIN . $p . '/lang/';
+
+ if(isset($lang)) unset($lang);
+ if(file_exists($path . "en/lang.php")) {
+ include $path . "en/lang.php";
+ }
+ foreach($config_cascade['lang']['plugin'] as $config_file) {
+ if(file_exists($config_file . $p . '/en/lang.php')) {
+ include($config_file . $p . '/en/lang.php');
+ }
+ }
+ if(isset($conf['lang']) && $conf['lang'] != 'en') {
+ if(file_exists($path . $conf['lang'] . "/lang.php")) {
+ include($path . $conf['lang'] . '/lang.php');
+ }
+ foreach($config_cascade['lang']['plugin'] as $config_file) {
+ if(file_exists($config_file . $p . '/' . $conf['lang'] . '/lang.php')) {
+ include($config_file . $p . '/' . $conf['lang'] . '/lang.php');
+ }
+ }
+ }
+
+ if(isset($lang['js'])) {
+ $pluginstrings[$p] = $lang['js'];
+ }
+ }
+ return $pluginstrings;
+}
+
+/**
+ * Return an two-dimensional array with strings from the language file of current active template.
+ *
+ * - $lang['js'] must be an array.
+ * - Nothing is returned for template without an entry for $lang['js']
+ *
+ * @param string $tpl
+ * @return array
+ */
+function js_templatestrings($tpl) {
+ global $conf, $config_cascade;
+
+ $path = tpl_incdir() . 'lang/';
+
+ $templatestrings = array();
+ if(file_exists($path . "en/lang.php")) {
+ include $path . "en/lang.php";
+ }
+ foreach($config_cascade['lang']['template'] as $config_file) {
+ if(file_exists($config_file . $conf['template'] . '/en/lang.php')) {
+ include($config_file . $conf['template'] . '/en/lang.php');
+ }
+ }
+ if(isset($conf['lang']) && $conf['lang'] != 'en' && file_exists($path . $conf['lang'] . "/lang.php")) {
+ include $path . $conf['lang'] . "/lang.php";
+ }
+ if(isset($conf['lang']) && $conf['lang'] != 'en') {
+ if(file_exists($path . $conf['lang'] . "/lang.php")) {
+ include $path . $conf['lang'] . "/lang.php";
+ }
+ foreach($config_cascade['lang']['template'] as $config_file) {
+ if(file_exists($config_file . $conf['template'] . '/' . $conf['lang'] . '/lang.php')) {
+ include($config_file . $conf['template'] . '/' . $conf['lang'] . '/lang.php');
+ }
+ }
+ }
+
+ if(isset($lang['js'])) {
+ $templatestrings[$tpl] = $lang['js'];
+ }
+ return $templatestrings;
+}
+
+/**
+ * Escapes a String to be embedded in a JavaScript call, keeps \n
+ * as newline
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string $string
+ * @return string
+ */
+function js_escape($string){
+ return str_replace('\\\\n','\\n',addslashes($string));
+}
+
+/**
+ * Adds the given JavaScript code to the window.onload() event
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string $func
+ */
+function js_runonstart($func){
+ echo "jQuery(function(){ $func; });".NL;
+}
+
+/**
+ * Strip comments and whitespaces from given JavaScript Code
+ *
+ * This is a port of Nick Galbreath's python tool jsstrip.py which is
+ * released under BSD license. See link for original code.
+ *
+ * @author Nick Galbreath <nickg@modp.com>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @link http://code.google.com/p/jsstrip/
+ *
+ * @param string $s
+ * @return string
+ */
+function js_compress($s){
+ $s = ltrim($s); // strip all initial whitespace
+ $s .= "\n";
+ $i = 0; // char index for input string
+ $j = 0; // char forward index for input string
+ $line = 0; // line number of file (close to it anyways)
+ $slen = strlen($s); // size of input string
+ $lch = ''; // last char added
+ $result = ''; // we store the final result here
+
+ // items that don't need spaces next to them
+ $chars = "^&|!+\-*\/%=\?:;,{}()<>% \t\n\r'\"[]";
+
+ // items which need a space if the sign before and after whitespace is equal.
+ // E.g. '+ ++' may not be compressed to '+++' --> syntax error.
+ $ops = "+-";
+
+ $regex_starters = array("(", "=", "[", "," , ":", "!", "&", "|");
+
+ $whitespaces_chars = array(" ", "\t", "\n", "\r", "\0", "\x0B");
+
+ while($i < $slen){
+ // skip all "boring" characters. This is either
+ // reserved word (e.g. "for", "else", "if") or a
+ // variable/object/method (e.g. "foo.color")
+ while ($i < $slen && (strpos($chars,$s[$i]) === false) ){
+ $result .= $s[$i];
+ $i = $i + 1;
+ }
+
+ $ch = $s[$i];
+ // multiline comments (keeping IE conditionals)
+ if($ch == '/' && $s[$i+1] == '*' && $s[$i+2] != '@'){
+ $endC = strpos($s,'*/',$i+2);
+ if($endC === false) trigger_error('Found invalid /*..*/ comment', E_USER_ERROR);
+
+ // check if this is a NOCOMPRESS comment
+ if(substr($s, $i, $endC+2-$i) == '/* BEGIN NOCOMPRESS */'){
+ $endNC = strpos($s, '/* END NOCOMPRESS */', $endC+2);
+ if($endNC === false) trigger_error('Found invalid NOCOMPRESS comment', E_USER_ERROR);
+
+ // verbatim copy contents, trimming but putting it on its own line
+ $result .= "\n".trim(substr($s, $i + 22, $endNC - ($i + 22)))."\n"; // BEGIN comment = 22 chars
+ $i = $endNC + 20; // END comment = 20 chars
+ }else{
+ $i = $endC + 2;
+ }
+ continue;
+ }
+
+ // singleline
+ if($ch == '/' && $s[$i+1] == '/'){
+ $endC = strpos($s,"\n",$i+2);
+ if($endC === false) trigger_error('Invalid comment', E_USER_ERROR);
+ $i = $endC;
+ continue;
+ }
+
+ // tricky. might be an RE
+ if($ch == '/'){
+ // rewind, skip white space
+ $j = 1;
+ while(in_array($s[$i-$j], $whitespaces_chars)){
+ $j = $j + 1;
+ }
+ if( in_array($s[$i-$j], $regex_starters) ){
+ // yes, this is an re
+ // now move forward and find the end of it
+ $j = 1;
+ while($s[$i+$j] != '/'){
+ if($s[$i+$j] == '\\') $j = $j + 2;
+ else $j++;
+ }
+ $result .= substr($s,$i,$j+1);
+ $i = $i + $j + 1;
+ continue;
+ }
+ }
+
+ // double quote strings
+ if($ch == '"'){
+ $j = 1;
+ while( ($i+$j < $slen) && $s[$i+$j] != '"' ){
+ if( $s[$i+$j] == '\\' && ($s[$i+$j+1] == '"' || $s[$i+$j+1] == '\\') ){
+ $j += 2;
+ }else{
+ $j += 1;
+ }
+ }
+ $string = substr($s,$i,$j+1);
+ // remove multiline markers:
+ $string = str_replace("\\\n",'',$string);
+ $result .= $string;
+ $i = $i + $j + 1;
+ continue;
+ }
+
+ // single quote strings
+ if($ch == "'"){
+ $j = 1;
+ while( ($i+$j < $slen) && $s[$i+$j] != "'" ){
+ if( $s[$i+$j] == '\\' && ($s[$i+$j+1] == "'" || $s[$i+$j+1] == '\\') ){
+ $j += 2;
+ }else{
+ $j += 1;
+ }
+ }
+ $string = substr($s,$i,$j+1);
+ // remove multiline markers:
+ $string = str_replace("\\\n",'',$string);
+ $result .= $string;
+ $i = $i + $j + 1;
+ continue;
+ }
+
+ // whitespaces
+ if( $ch == ' ' || $ch == "\r" || $ch == "\n" || $ch == "\t" ){
+ $lch = substr($result,-1);
+
+ // Only consider deleting whitespace if the signs before and after
+ // are not equal and are not an operator which may not follow itself.
+ if ($i+1 < $slen && ((!$lch || $s[$i+1] == ' ')
+ || $lch != $s[$i+1]
+ || strpos($ops,$s[$i+1]) === false)) {
+ // leading spaces
+ if($i+1 < $slen && (strpos($chars,$s[$i+1]) !== false)){
+ $i = $i + 1;
+ continue;
+ }
+ // trailing spaces
+ // if this ch is space AND the last char processed
+ // is special, then skip the space
+ if($lch && (strpos($chars,$lch) !== false)){
+ $i = $i + 1;
+ continue;
+ }
+ }
+
+ // else after all of this convert the "whitespace" to
+ // a single space. It will get appended below
+ $ch = ' ';
+ }
+
+ // other chars
+ $result .= $ch;
+ $i = $i + 1;
+ }
+
+ return trim($result);
+}
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/exe/manifest.php b/platform/www/lib/exe/manifest.php
new file mode 100644
index 0000000..e9a3528
--- /dev/null
+++ b/platform/www/lib/exe/manifest.php
@@ -0,0 +1,14 @@
+<?php
+
+if (!defined('DOKU_INC')) {
+ define('DOKU_INC', __DIR__ . '/../../');
+}
+require_once(DOKU_INC . 'inc/init.php');
+
+if (!actionOK('manifest')) {
+ http_status(404, 'Manifest has been disabled in DokuWiki configuration.');
+ exit();
+}
+
+$manifest = new \dokuwiki\Manifest();
+$manifest->sendManifest();
diff --git a/platform/www/lib/exe/mediamanager.php b/platform/www/lib/exe/mediamanager.php
new file mode 100644
index 0000000..b43cff7
--- /dev/null
+++ b/platform/www/lib/exe/mediamanager.php
@@ -0,0 +1,129 @@
+<?php
+
+use dokuwiki\Extension\Event;
+
+ if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../');
+ define('DOKU_MEDIAMANAGER',1);
+
+ // for multi uploader:
+ @ini_set('session.use_only_cookies',0);
+
+ require_once(DOKU_INC.'inc/init.php');
+
+ global $INPUT;
+ global $lang;
+ global $conf;
+ // handle passed message
+ if($INPUT->str('msg1')) msg(hsc($INPUT->str('msg1')),1);
+ if($INPUT->str('err')) msg(hsc($INPUT->str('err')),-1);
+
+ global $DEL;
+ // get namespace to display (either direct or from deletion order)
+ if($INPUT->str('delete')){
+ $DEL = cleanID($INPUT->str('delete'));
+ $IMG = $DEL;
+ $NS = getNS($DEL);
+ }elseif($INPUT->str('edit')){
+ $IMG = cleanID($INPUT->str('edit'));
+ $NS = getNS($IMG);
+ }elseif($INPUT->str('img')){
+ $IMG = cleanID($INPUT->str('img'));
+ $NS = getNS($IMG);
+ }else{
+ $NS = cleanID($INPUT->str('ns'));
+ $IMG = null;
+ }
+
+ global $INFO, $JSINFO;
+ $INFO = !empty($INFO) ? array_merge($INFO, mediainfo()) : mediainfo();
+ $JSINFO['id'] = '';
+ $JSINFO['namespace'] = '';
+ $AUTH = $INFO['perm']; // shortcut for historical reasons
+
+ $tmp = array();
+ Event::createAndTrigger('MEDIAMANAGER_STARTED', $tmp);
+ session_write_close(); //close session
+
+ // do not display the manager if user does not have read access
+ if($AUTH < AUTH_READ && !$fullscreen) {
+ http_status(403);
+ die($lang['accessdenied']);
+ }
+
+ // handle flash upload
+ if(isset($_FILES['Filedata'])){
+ $_FILES['upload'] =& $_FILES['Filedata'];
+ $JUMPTO = media_upload($NS,$AUTH);
+ if($JUMPTO == false){
+ http_status(400);
+ echo 'Upload failed';
+ }
+ echo 'ok';
+ exit;
+ }
+
+ // give info on PHP caught upload errors
+ if(!empty($_FILES['upload']['error'])){
+ switch($_FILES['upload']['error']){
+ case 1:
+ case 2:
+ msg(sprintf($lang['uploadsize'],
+ filesize_h(php_to_byte(ini_get('upload_max_filesize')))),-1);
+ break;
+ default:
+ msg($lang['uploadfail'].' ('.$_FILES['upload']['error'].')',-1);
+ }
+ unset($_FILES['upload']);
+ }
+
+ // handle upload
+ if(!empty($_FILES['upload']['tmp_name'])){
+ $JUMPTO = media_upload($NS,$AUTH);
+ if($JUMPTO) $NS = getNS($JUMPTO);
+ }
+
+ // handle meta saving
+ if($IMG && @array_key_exists('save', $INPUT->arr('do'))){
+ $JUMPTO = media_metasave($IMG,$AUTH,$INPUT->arr('meta'));
+ }
+
+ if($IMG && ($INPUT->str('mediado') == 'save' || @array_key_exists('save', $INPUT->arr('mediado')))) {
+ $JUMPTO = media_metasave($IMG,$AUTH,$INPUT->arr('meta'));
+ }
+
+ if ($INPUT->int('rev') && $conf['mediarevisions']) $REV = $INPUT->int('rev');
+
+ if($INPUT->str('mediado') == 'restore' && $conf['mediarevisions']){
+ $JUMPTO = media_restore($INPUT->str('image'), $REV, $AUTH);
+ }
+
+ // handle deletion
+ if($DEL) {
+ $res = 0;
+ if(checkSecurityToken()) {
+ $res = media_delete($DEL,$AUTH);
+ }
+ if ($res & DOKU_MEDIA_DELETED) {
+ $msg = sprintf($lang['deletesucc'], noNS($DEL));
+ if ($res & DOKU_MEDIA_EMPTY_NS && !$fullscreen) {
+ // current namespace was removed. redirecting to root ns passing msg along
+ send_redirect(DOKU_URL.'lib/exe/mediamanager.php?msg1='.
+ rawurlencode($msg).'&edid='.$INPUT->str('edid'));
+ }
+ msg($msg,1);
+ } elseif ($res & DOKU_MEDIA_INUSE) {
+ if(!$conf['refshow']) {
+ msg(sprintf($lang['mediainuse'],noNS($DEL)),0);
+ }
+ } else {
+ msg(sprintf($lang['deletefail'],noNS($DEL)),-1);
+ }
+ }
+ // finished - start output
+
+ if (!$fullscreen) {
+ header('Content-Type: text/html; charset=utf-8');
+ include(template('mediamanager.php'));
+ }
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
diff --git a/platform/www/lib/exe/opensearch.php b/platform/www/lib/exe/opensearch.php
new file mode 100644
index 0000000..b00b2b7
--- /dev/null
+++ b/platform/www/lib/exe/opensearch.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * DokuWiki OpenSearch creator
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @link http://www.opensearch.org/
+ * @author Mike Frysinger <vapier@gentoo.org>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../');
+if(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching)
+if(!defined('NL')) define('NL',"\n");
+require_once(DOKU_INC.'inc/init.php');
+
+// try to be clever about the favicon location
+if(file_exists(DOKU_INC.'favicon.ico')){
+ $ico = DOKU_URL.'favicon.ico';
+}elseif(file_exists(tpl_incdir().'images/favicon.ico')){
+ $ico = DOKU_URL.'lib/tpl/'.$conf['template'].'/images/favicon.ico';
+}elseif(file_exists(tpl_incdir().'favicon.ico')){
+ $ico = DOKU_URL.'lib/tpl/'.$conf['template'].'/favicon.ico';
+}else{
+ $ico = DOKU_URL.'lib/tpl/dokuwiki/images/favicon.ico';
+}
+
+// output
+header('Content-Type: application/opensearchdescription+xml; charset=utf-8');
+echo '<?xml version="1.0"?>'.NL;
+echo '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">'.NL;
+echo ' <ShortName>'.hsc($conf['title']).'</ShortName>'.NL;
+echo ' <Image width="16" height="16" type="image/x-icon">'.$ico.'</Image>'.NL;
+echo ' <Url type="text/html" template="'.DOKU_URL.DOKU_SCRIPT.'?do=search&amp;id={searchTerms}" />'.NL;
+echo ' <Url type="application/x-suggestions+json" template="'.
+ DOKU_URL.'lib/exe/ajax.php?call=suggestions&amp;q={searchTerms}" />'.NL;
+echo '</OpenSearchDescription>'.NL;
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/exe/taskrunner.php b/platform/www/lib/exe/taskrunner.php
new file mode 100644
index 0000000..69ab445
--- /dev/null
+++ b/platform/www/lib/exe/taskrunner.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * DokuWiki indexer
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+if (!defined('DOKU_INC')) {
+ define('DOKU_INC', __DIR__ . '/../../');
+}
+define('DOKU_DISABLE_GZIP_OUTPUT',1);
+require_once DOKU_INC.'inc/init.php';
+session_write_close(); //close session
+
+$taskRunner = new \dokuwiki\TaskRunner();
+$taskRunner->run();
diff --git a/platform/www/lib/exe/xmlrpc.php b/platform/www/lib/exe/xmlrpc.php
new file mode 100644
index 0000000..dc0438e
--- /dev/null
+++ b/platform/www/lib/exe/xmlrpc.php
@@ -0,0 +1,15 @@
+<?php
+/**
+ * XMLRPC API backend
+ */
+
+use dokuwiki\Remote\XmlRpcServer;
+
+if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__).'/../../');
+
+require_once(DOKU_INC.'inc/init.php');
+session_write_close(); //close session
+
+if(!$conf['remote']) die((new IXR_Error(-32605, "XML-RPC server not enabled."))->getXml());
+
+$server = new XmlRpcServer();
diff --git a/platform/www/lib/images/README b/platform/www/lib/images/README
new file mode 100644
index 0000000..e2788b4
--- /dev/null
+++ b/platform/www/lib/images/README
@@ -0,0 +1,6 @@
+
+Icons: email.png, external-link.png, unc.png
+Icon set: Dusseldorf
+Designer: pc.de
+License: Creative Commons Attribution License [http://creativecommons.org/licenses/by/3.0/]
+URL: http://pc.de/icons/#Dusseldorf
diff --git a/platform/www/lib/images/_deprecated.txt b/platform/www/lib/images/_deprecated.txt
new file mode 100644
index 0000000..a347f8b
--- /dev/null
+++ b/platform/www/lib/images/_deprecated.txt
@@ -0,0 +1,2 @@
+
+(none)
diff --git a/platform/www/lib/images/admin/README b/platform/www/lib/images/admin/README
new file mode 100644
index 0000000..53e7d83
--- /dev/null
+++ b/platform/www/lib/images/admin/README
@@ -0,0 +1,4 @@
+These icons were taken from the nuvoX KDE icon theme and are GPL licensed
+See http://www.kde-look.org/content/show.php/nuvoX?content=38467
+
+styling.png from https://openclipart.org/detail/25595/brush Public Domain
diff --git a/platform/www/lib/images/admin/acl.png b/platform/www/lib/images/admin/acl.png
new file mode 100644
index 0000000..542e108
--- /dev/null
+++ b/platform/www/lib/images/admin/acl.png
Binary files differ
diff --git a/platform/www/lib/images/admin/config.png b/platform/www/lib/images/admin/config.png
new file mode 100644
index 0000000..679a673
--- /dev/null
+++ b/platform/www/lib/images/admin/config.png
Binary files differ
diff --git a/platform/www/lib/images/admin/plugin.png b/platform/www/lib/images/admin/plugin.png
new file mode 100644
index 0000000..27bd154
--- /dev/null
+++ b/platform/www/lib/images/admin/plugin.png
Binary files differ
diff --git a/platform/www/lib/images/admin/popularity.png b/platform/www/lib/images/admin/popularity.png
new file mode 100644
index 0000000..e18a8cb
--- /dev/null
+++ b/platform/www/lib/images/admin/popularity.png
Binary files differ
diff --git a/platform/www/lib/images/admin/revert.png b/platform/www/lib/images/admin/revert.png
new file mode 100644
index 0000000..c74c792
--- /dev/null
+++ b/platform/www/lib/images/admin/revert.png
Binary files differ
diff --git a/platform/www/lib/images/admin/styling.png b/platform/www/lib/images/admin/styling.png
new file mode 100644
index 0000000..859c8c9
--- /dev/null
+++ b/platform/www/lib/images/admin/styling.png
Binary files differ
diff --git a/platform/www/lib/images/admin/usermanager.png b/platform/www/lib/images/admin/usermanager.png
new file mode 100644
index 0000000..e6f72e0
--- /dev/null
+++ b/platform/www/lib/images/admin/usermanager.png
Binary files differ
diff --git a/platform/www/lib/images/blank.gif b/platform/www/lib/images/blank.gif
new file mode 100644
index 0000000..9935f82
--- /dev/null
+++ b/platform/www/lib/images/blank.gif
Binary files differ
diff --git a/platform/www/lib/images/bullet.png b/platform/www/lib/images/bullet.png
new file mode 100644
index 0000000..b8ec60c
--- /dev/null
+++ b/platform/www/lib/images/bullet.png
Binary files differ
diff --git a/platform/www/lib/images/closed-rtl.png b/platform/www/lib/images/closed-rtl.png
new file mode 100644
index 0000000..016a3c3
--- /dev/null
+++ b/platform/www/lib/images/closed-rtl.png
Binary files differ
diff --git a/platform/www/lib/images/closed.png b/platform/www/lib/images/closed.png
new file mode 100644
index 0000000..927bfc5
--- /dev/null
+++ b/platform/www/lib/images/closed.png
Binary files differ
diff --git a/platform/www/lib/images/diff.png b/platform/www/lib/images/diff.png
new file mode 100644
index 0000000..04fab07
--- /dev/null
+++ b/platform/www/lib/images/diff.png
Binary files differ
diff --git a/platform/www/lib/images/email.png b/platform/www/lib/images/email.png
new file mode 100644
index 0000000..575b831
--- /dev/null
+++ b/platform/www/lib/images/email.png
Binary files differ
diff --git a/platform/www/lib/images/error.png b/platform/www/lib/images/error.png
new file mode 100644
index 0000000..da06924
--- /dev/null
+++ b/platform/www/lib/images/error.png
Binary files differ
diff --git a/platform/www/lib/images/external-link.png b/platform/www/lib/images/external-link.png
new file mode 100644
index 0000000..fecac61
--- /dev/null
+++ b/platform/www/lib/images/external-link.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/7z.png b/platform/www/lib/images/fileicons/32x32/7z.png
new file mode 100644
index 0000000..2537cb9
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/7z.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/asm.png b/platform/www/lib/images/fileicons/32x32/asm.png
new file mode 100644
index 0000000..17e74d0
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/asm.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/bash.png b/platform/www/lib/images/fileicons/32x32/bash.png
new file mode 100644
index 0000000..a31ee68
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/bash.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/bz2.png b/platform/www/lib/images/fileicons/32x32/bz2.png
new file mode 100644
index 0000000..c780316
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/bz2.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/c.png b/platform/www/lib/images/fileicons/32x32/c.png
new file mode 100644
index 0000000..d8032d0
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/c.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/cc.png b/platform/www/lib/images/fileicons/32x32/cc.png
new file mode 100644
index 0000000..241ebd4
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/cc.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/conf.png b/platform/www/lib/images/fileicons/32x32/conf.png
new file mode 100644
index 0000000..9797c2a
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/conf.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/cpp.png b/platform/www/lib/images/fileicons/32x32/cpp.png
new file mode 100644
index 0000000..1289065
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/cpp.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/cs.png b/platform/www/lib/images/fileicons/32x32/cs.png
new file mode 100644
index 0000000..6c2aae2
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/cs.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/csh.png b/platform/www/lib/images/fileicons/32x32/csh.png
new file mode 100644
index 0000000..e43584c
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/csh.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/css.png b/platform/www/lib/images/fileicons/32x32/css.png
new file mode 100644
index 0000000..786f304
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/css.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/csv.png b/platform/www/lib/images/fileicons/32x32/csv.png
new file mode 100644
index 0000000..e5cdbf9
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/csv.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/deb.png b/platform/www/lib/images/fileicons/32x32/deb.png
new file mode 100644
index 0000000..e2828a3
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/deb.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/diff.png b/platform/www/lib/images/fileicons/32x32/diff.png
new file mode 100644
index 0000000..9e413cb
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/diff.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/doc.png b/platform/www/lib/images/fileicons/32x32/doc.png
new file mode 100644
index 0000000..43ec354
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/doc.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/docx.png b/platform/www/lib/images/fileicons/32x32/docx.png
new file mode 100644
index 0000000..a25f260
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/docx.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/file.png b/platform/www/lib/images/fileicons/32x32/file.png
new file mode 100644
index 0000000..7f6d51a
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/file.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/gif.png b/platform/www/lib/images/fileicons/32x32/gif.png
new file mode 100644
index 0000000..dde2d84
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/gif.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/gz.png b/platform/www/lib/images/fileicons/32x32/gz.png
new file mode 100644
index 0000000..5bddffb
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/gz.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/h.png b/platform/www/lib/images/fileicons/32x32/h.png
new file mode 100644
index 0000000..5c169a3
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/h.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/hpp.png b/platform/www/lib/images/fileicons/32x32/hpp.png
new file mode 100644
index 0000000..128110d
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/hpp.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/htm.png b/platform/www/lib/images/fileicons/32x32/htm.png
new file mode 100644
index 0000000..79096dc
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/htm.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/html.png b/platform/www/lib/images/fileicons/32x32/html.png
new file mode 100644
index 0000000..79096dc
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/html.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/ico.png b/platform/www/lib/images/fileicons/32x32/ico.png
new file mode 100644
index 0000000..60f73bd
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/ico.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/java.png b/platform/www/lib/images/fileicons/32x32/java.png
new file mode 100644
index 0000000..1d86949
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/java.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/jpeg.png b/platform/www/lib/images/fileicons/32x32/jpeg.png
new file mode 100644
index 0000000..4b5c425
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/jpeg.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/jpg.png b/platform/www/lib/images/fileicons/32x32/jpg.png
new file mode 100644
index 0000000..4b5c425
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/jpg.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/js.png b/platform/www/lib/images/fileicons/32x32/js.png
new file mode 100644
index 0000000..5a8dabe
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/js.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/json.png b/platform/www/lib/images/fileicons/32x32/json.png
new file mode 100644
index 0000000..e4a55e6
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/json.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/lua.png b/platform/www/lib/images/fileicons/32x32/lua.png
new file mode 100644
index 0000000..c8e0bf2
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/lua.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/mp3.png b/platform/www/lib/images/fileicons/32x32/mp3.png
new file mode 100644
index 0000000..9bf1695
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/mp3.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/mp4.png b/platform/www/lib/images/fileicons/32x32/mp4.png
new file mode 100644
index 0000000..071abc3
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/mp4.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/odc.png b/platform/www/lib/images/fileicons/32x32/odc.png
new file mode 100644
index 0000000..3ad6a3c
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/odc.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/odf.png b/platform/www/lib/images/fileicons/32x32/odf.png
new file mode 100644
index 0000000..8dd89ea
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/odf.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/odg.png b/platform/www/lib/images/fileicons/32x32/odg.png
new file mode 100644
index 0000000..7020d13
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/odg.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/odi.png b/platform/www/lib/images/fileicons/32x32/odi.png
new file mode 100644
index 0000000..9a08a42
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/odi.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/odp.png b/platform/www/lib/images/fileicons/32x32/odp.png
new file mode 100644
index 0000000..e6b538d
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/odp.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/ods.png b/platform/www/lib/images/fileicons/32x32/ods.png
new file mode 100644
index 0000000..cf4a226
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/ods.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/odt.png b/platform/www/lib/images/fileicons/32x32/odt.png
new file mode 100644
index 0000000..1eae19c
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/odt.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/ogg.png b/platform/www/lib/images/fileicons/32x32/ogg.png
new file mode 100644
index 0000000..d7b0553
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/ogg.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/ogv.png b/platform/www/lib/images/fileicons/32x32/ogv.png
new file mode 100644
index 0000000..4fdedba
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/ogv.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/pas.png b/platform/www/lib/images/fileicons/32x32/pas.png
new file mode 100644
index 0000000..8d2999e
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/pas.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/pdf.png b/platform/www/lib/images/fileicons/32x32/pdf.png
new file mode 100644
index 0000000..09ae62e
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/pdf.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/php.png b/platform/www/lib/images/fileicons/32x32/php.png
new file mode 100644
index 0000000..1f4cabf
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/php.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/pl.png b/platform/www/lib/images/fileicons/32x32/pl.png
new file mode 100644
index 0000000..038e9f3
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/pl.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/png.png b/platform/www/lib/images/fileicons/32x32/png.png
new file mode 100644
index 0000000..e3ea1c3
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/png.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/ppt.png b/platform/www/lib/images/fileicons/32x32/ppt.png
new file mode 100644
index 0000000..acee945
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/ppt.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/pptx.png b/platform/www/lib/images/fileicons/32x32/pptx.png
new file mode 100644
index 0000000..b57b091
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/pptx.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/ps.png b/platform/www/lib/images/fileicons/32x32/ps.png
new file mode 100644
index 0000000..523a0be
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/ps.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/py.png b/platform/www/lib/images/fileicons/32x32/py.png
new file mode 100644
index 0000000..ae6e06a
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/py.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/rar.png b/platform/www/lib/images/fileicons/32x32/rar.png
new file mode 100644
index 0000000..5b1cfcb
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/rar.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/rb.png b/platform/www/lib/images/fileicons/32x32/rb.png
new file mode 100644
index 0000000..398f208
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/rb.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/rpm.png b/platform/www/lib/images/fileicons/32x32/rpm.png
new file mode 100644
index 0000000..c66a907
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/rpm.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/rtf.png b/platform/www/lib/images/fileicons/32x32/rtf.png
new file mode 100644
index 0000000..43182f3
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/rtf.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/sh.png b/platform/www/lib/images/fileicons/32x32/sh.png
new file mode 100644
index 0000000..52e3f95
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/sh.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/sql.png b/platform/www/lib/images/fileicons/32x32/sql.png
new file mode 100644
index 0000000..bb23e56
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/sql.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/svg.png b/platform/www/lib/images/fileicons/32x32/svg.png
new file mode 100644
index 0000000..81a3644
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/svg.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/swf.png b/platform/www/lib/images/fileicons/32x32/swf.png
new file mode 100644
index 0000000..be8f546
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/swf.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/sxc.png b/platform/www/lib/images/fileicons/32x32/sxc.png
new file mode 100644
index 0000000..cc45ffa
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/sxc.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/sxd.png b/platform/www/lib/images/fileicons/32x32/sxd.png
new file mode 100644
index 0000000..26f44c2
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/sxd.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/sxi.png b/platform/www/lib/images/fileicons/32x32/sxi.png
new file mode 100644
index 0000000..62e90bc
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/sxi.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/sxw.png b/platform/www/lib/images/fileicons/32x32/sxw.png
new file mode 100644
index 0000000..5196307
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/sxw.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/tar.png b/platform/www/lib/images/fileicons/32x32/tar.png
new file mode 100644
index 0000000..8eb0ef4
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/tar.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/tgz.png b/platform/www/lib/images/fileicons/32x32/tgz.png
new file mode 100644
index 0000000..77faacb
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/tgz.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/txt.png b/platform/www/lib/images/fileicons/32x32/txt.png
new file mode 100644
index 0000000..5d09e3c
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/txt.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/wav.png b/platform/www/lib/images/fileicons/32x32/wav.png
new file mode 100644
index 0000000..37b871b
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/wav.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/webm.png b/platform/www/lib/images/fileicons/32x32/webm.png
new file mode 100644
index 0000000..9044845
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/webm.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/xls.png b/platform/www/lib/images/fileicons/32x32/xls.png
new file mode 100644
index 0000000..1c21a6e
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/xls.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/xlsx.png b/platform/www/lib/images/fileicons/32x32/xlsx.png
new file mode 100644
index 0000000..cba5937
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/xlsx.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/xml.png b/platform/www/lib/images/fileicons/32x32/xml.png
new file mode 100644
index 0000000..8eee583
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/xml.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/32x32/zip.png b/platform/www/lib/images/fileicons/32x32/zip.png
new file mode 100644
index 0000000..0ce83b6
--- /dev/null
+++ b/platform/www/lib/images/fileicons/32x32/zip.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/7z.png b/platform/www/lib/images/fileicons/7z.png
new file mode 100644
index 0000000..fa6abe3
--- /dev/null
+++ b/platform/www/lib/images/fileicons/7z.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/README b/platform/www/lib/images/fileicons/README
new file mode 100644
index 0000000..0538586
--- /dev/null
+++ b/platform/www/lib/images/fileicons/README
@@ -0,0 +1,2 @@
+For the generator of these files see
+https://github.com/splitbrain/file-icon-generator/blob/master/example-dokuwiki.php
diff --git a/platform/www/lib/images/fileicons/asm.png b/platform/www/lib/images/fileicons/asm.png
new file mode 100644
index 0000000..c22c451
--- /dev/null
+++ b/platform/www/lib/images/fileicons/asm.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/bash.png b/platform/www/lib/images/fileicons/bash.png
new file mode 100644
index 0000000..f352cfd
--- /dev/null
+++ b/platform/www/lib/images/fileicons/bash.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/bz2.png b/platform/www/lib/images/fileicons/bz2.png
new file mode 100644
index 0000000..a1b048f
--- /dev/null
+++ b/platform/www/lib/images/fileicons/bz2.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/c.png b/platform/www/lib/images/fileicons/c.png
new file mode 100644
index 0000000..51d9c7f
--- /dev/null
+++ b/platform/www/lib/images/fileicons/c.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/cc.png b/platform/www/lib/images/fileicons/cc.png
new file mode 100644
index 0000000..8aeae79
--- /dev/null
+++ b/platform/www/lib/images/fileicons/cc.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/conf.png b/platform/www/lib/images/fileicons/conf.png
new file mode 100644
index 0000000..c845d49
--- /dev/null
+++ b/platform/www/lib/images/fileicons/conf.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/cpp.png b/platform/www/lib/images/fileicons/cpp.png
new file mode 100644
index 0000000..1a04c32
--- /dev/null
+++ b/platform/www/lib/images/fileicons/cpp.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/cs.png b/platform/www/lib/images/fileicons/cs.png
new file mode 100644
index 0000000..740725a
--- /dev/null
+++ b/platform/www/lib/images/fileicons/cs.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/csh.png b/platform/www/lib/images/fileicons/csh.png
new file mode 100644
index 0000000..c0131c5
--- /dev/null
+++ b/platform/www/lib/images/fileicons/csh.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/css.png b/platform/www/lib/images/fileicons/css.png
new file mode 100644
index 0000000..89ac364
--- /dev/null
+++ b/platform/www/lib/images/fileicons/css.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/csv.png b/platform/www/lib/images/fileicons/csv.png
new file mode 100644
index 0000000..837ae29
--- /dev/null
+++ b/platform/www/lib/images/fileicons/csv.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/deb.png b/platform/www/lib/images/fileicons/deb.png
new file mode 100644
index 0000000..1db6fa5
--- /dev/null
+++ b/platform/www/lib/images/fileicons/deb.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/diff.png b/platform/www/lib/images/fileicons/diff.png
new file mode 100644
index 0000000..03e9af9
--- /dev/null
+++ b/platform/www/lib/images/fileicons/diff.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/doc.png b/platform/www/lib/images/fileicons/doc.png
new file mode 100644
index 0000000..dcc070f
--- /dev/null
+++ b/platform/www/lib/images/fileicons/doc.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/docx.png b/platform/www/lib/images/fileicons/docx.png
new file mode 100644
index 0000000..1a98a8d
--- /dev/null
+++ b/platform/www/lib/images/fileicons/docx.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/file.png b/platform/www/lib/images/fileicons/file.png
new file mode 100644
index 0000000..54fe8ab
--- /dev/null
+++ b/platform/www/lib/images/fileicons/file.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/gif.png b/platform/www/lib/images/fileicons/gif.png
new file mode 100644
index 0000000..38bdbf2
--- /dev/null
+++ b/platform/www/lib/images/fileicons/gif.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/gz.png b/platform/www/lib/images/fileicons/gz.png
new file mode 100644
index 0000000..422693a
--- /dev/null
+++ b/platform/www/lib/images/fileicons/gz.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/h.png b/platform/www/lib/images/fileicons/h.png
new file mode 100644
index 0000000..d65f2f5
--- /dev/null
+++ b/platform/www/lib/images/fileicons/h.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/hpp.png b/platform/www/lib/images/fileicons/hpp.png
new file mode 100644
index 0000000..6d314f5
--- /dev/null
+++ b/platform/www/lib/images/fileicons/hpp.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/htm.png b/platform/www/lib/images/fileicons/htm.png
new file mode 100644
index 0000000..f45847f
--- /dev/null
+++ b/platform/www/lib/images/fileicons/htm.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/html.png b/platform/www/lib/images/fileicons/html.png
new file mode 100644
index 0000000..f45847f
--- /dev/null
+++ b/platform/www/lib/images/fileicons/html.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/ico.png b/platform/www/lib/images/fileicons/ico.png
new file mode 100644
index 0000000..38aa34b
--- /dev/null
+++ b/platform/www/lib/images/fileicons/ico.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/index.php b/platform/www/lib/images/fileicons/index.php
new file mode 100644
index 0000000..d1f233e
--- /dev/null
+++ b/platform/www/lib/images/fileicons/index.php
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+ <title>Filetype icons</title>
+
+ <style>
+ body {
+ background-color: #ccc;
+ font-family: Arial;
+ }
+
+ .box {
+ width: 200px;
+ float: left;
+ padding: 0.5em;
+ margin: 0;
+ }
+
+ .white {
+ background-color: #fff;
+ }
+
+ .black {
+ background-color: #000;
+ }
+ </style>
+
+</head>
+<body>
+
+<?php
+$fi_list = ''; $fi_list32 = '';
+foreach (glob('*.png') as $img) {
+ $fi_list .= '<img src="'.$img.'" alt="'.$img.'" title="'.$img.'" /> ';
+}
+foreach (glob('32x32/*.png') as $img) {
+ $fi_list32 .= '<img src="'.$img.'" alt="'.$img.'" title="'.$img.'" /> ';
+}
+echo '<div class="white box">
+'.$fi_list.'
+</div>
+
+<div class="black box">
+'.$fi_list.'
+</div>
+
+<br style="clear: left" />
+
+<div class="white box">
+'.$fi_list32.'
+</div>
+
+<div class="black box">
+'.$fi_list32;
+?>
+</div>
+
+</body>
+</html>
diff --git a/platform/www/lib/images/fileicons/java.png b/platform/www/lib/images/fileicons/java.png
new file mode 100644
index 0000000..0c62347
--- /dev/null
+++ b/platform/www/lib/images/fileicons/java.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/jpeg.png b/platform/www/lib/images/fileicons/jpeg.png
new file mode 100644
index 0000000..e446dd4
--- /dev/null
+++ b/platform/www/lib/images/fileicons/jpeg.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/jpg.png b/platform/www/lib/images/fileicons/jpg.png
new file mode 100644
index 0000000..e446dd4
--- /dev/null
+++ b/platform/www/lib/images/fileicons/jpg.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/js.png b/platform/www/lib/images/fileicons/js.png
new file mode 100644
index 0000000..bee428f
--- /dev/null
+++ b/platform/www/lib/images/fileicons/js.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/json.png b/platform/www/lib/images/fileicons/json.png
new file mode 100644
index 0000000..4d0a3cf
--- /dev/null
+++ b/platform/www/lib/images/fileicons/json.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/lua.png b/platform/www/lib/images/fileicons/lua.png
new file mode 100644
index 0000000..fcebe3d
--- /dev/null
+++ b/platform/www/lib/images/fileicons/lua.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/mp3.png b/platform/www/lib/images/fileicons/mp3.png
new file mode 100644
index 0000000..2be976f
--- /dev/null
+++ b/platform/www/lib/images/fileicons/mp3.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/mp4.png b/platform/www/lib/images/fileicons/mp4.png
new file mode 100644
index 0000000..dc6fd00
--- /dev/null
+++ b/platform/www/lib/images/fileicons/mp4.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/odc.png b/platform/www/lib/images/fileicons/odc.png
new file mode 100644
index 0000000..bf3b3a1
--- /dev/null
+++ b/platform/www/lib/images/fileicons/odc.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/odf.png b/platform/www/lib/images/fileicons/odf.png
new file mode 100644
index 0000000..fcfc58f
--- /dev/null
+++ b/platform/www/lib/images/fileicons/odf.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/odg.png b/platform/www/lib/images/fileicons/odg.png
new file mode 100644
index 0000000..0a8196c
--- /dev/null
+++ b/platform/www/lib/images/fileicons/odg.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/odi.png b/platform/www/lib/images/fileicons/odi.png
new file mode 100644
index 0000000..0fc8508
--- /dev/null
+++ b/platform/www/lib/images/fileicons/odi.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/odp.png b/platform/www/lib/images/fileicons/odp.png
new file mode 100644
index 0000000..75b1db8
--- /dev/null
+++ b/platform/www/lib/images/fileicons/odp.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/ods.png b/platform/www/lib/images/fileicons/ods.png
new file mode 100644
index 0000000..2017426
--- /dev/null
+++ b/platform/www/lib/images/fileicons/ods.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/odt.png b/platform/www/lib/images/fileicons/odt.png
new file mode 100644
index 0000000..6f8fae4
--- /dev/null
+++ b/platform/www/lib/images/fileicons/odt.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/ogg.png b/platform/www/lib/images/fileicons/ogg.png
new file mode 100644
index 0000000..8bb5080
--- /dev/null
+++ b/platform/www/lib/images/fileicons/ogg.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/ogv.png b/platform/www/lib/images/fileicons/ogv.png
new file mode 100644
index 0000000..e6b65ac
--- /dev/null
+++ b/platform/www/lib/images/fileicons/ogv.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/pas.png b/platform/www/lib/images/fileicons/pas.png
new file mode 100644
index 0000000..19f0a3c
--- /dev/null
+++ b/platform/www/lib/images/fileicons/pas.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/pdf.png b/platform/www/lib/images/fileicons/pdf.png
new file mode 100644
index 0000000..42fbfd2
--- /dev/null
+++ b/platform/www/lib/images/fileicons/pdf.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/php.png b/platform/www/lib/images/fileicons/php.png
new file mode 100644
index 0000000..de0d8ee
--- /dev/null
+++ b/platform/www/lib/images/fileicons/php.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/pl.png b/platform/www/lib/images/fileicons/pl.png
new file mode 100644
index 0000000..d95513d
--- /dev/null
+++ b/platform/www/lib/images/fileicons/pl.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/png.png b/platform/www/lib/images/fileicons/png.png
new file mode 100644
index 0000000..273476d
--- /dev/null
+++ b/platform/www/lib/images/fileicons/png.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/ppt.png b/platform/www/lib/images/fileicons/ppt.png
new file mode 100644
index 0000000..a03d3c0
--- /dev/null
+++ b/platform/www/lib/images/fileicons/ppt.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/pptx.png b/platform/www/lib/images/fileicons/pptx.png
new file mode 100644
index 0000000..9b5c633
--- /dev/null
+++ b/platform/www/lib/images/fileicons/pptx.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/ps.png b/platform/www/lib/images/fileicons/ps.png
new file mode 100644
index 0000000..3b7848c
--- /dev/null
+++ b/platform/www/lib/images/fileicons/ps.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/py.png b/platform/www/lib/images/fileicons/py.png
new file mode 100644
index 0000000..893019e
--- /dev/null
+++ b/platform/www/lib/images/fileicons/py.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/rar.png b/platform/www/lib/images/fileicons/rar.png
new file mode 100644
index 0000000..091a635
--- /dev/null
+++ b/platform/www/lib/images/fileicons/rar.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/rb.png b/platform/www/lib/images/fileicons/rb.png
new file mode 100644
index 0000000..9b58db0
--- /dev/null
+++ b/platform/www/lib/images/fileicons/rb.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/rpm.png b/platform/www/lib/images/fileicons/rpm.png
new file mode 100644
index 0000000..75da50e
--- /dev/null
+++ b/platform/www/lib/images/fileicons/rpm.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/rtf.png b/platform/www/lib/images/fileicons/rtf.png
new file mode 100644
index 0000000..2e5a6e5
--- /dev/null
+++ b/platform/www/lib/images/fileicons/rtf.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/sh.png b/platform/www/lib/images/fileicons/sh.png
new file mode 100644
index 0000000..bc48354
--- /dev/null
+++ b/platform/www/lib/images/fileicons/sh.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/sql.png b/platform/www/lib/images/fileicons/sql.png
new file mode 100644
index 0000000..c36f3a8
--- /dev/null
+++ b/platform/www/lib/images/fileicons/sql.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/svg.png b/platform/www/lib/images/fileicons/svg.png
new file mode 100644
index 0000000..a52132d
--- /dev/null
+++ b/platform/www/lib/images/fileicons/svg.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/swf.png b/platform/www/lib/images/fileicons/swf.png
new file mode 100644
index 0000000..5c88387
--- /dev/null
+++ b/platform/www/lib/images/fileicons/swf.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/sxc.png b/platform/www/lib/images/fileicons/sxc.png
new file mode 100644
index 0000000..3b5c71f
--- /dev/null
+++ b/platform/www/lib/images/fileicons/sxc.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/sxd.png b/platform/www/lib/images/fileicons/sxd.png
new file mode 100644
index 0000000..15390cd
--- /dev/null
+++ b/platform/www/lib/images/fileicons/sxd.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/sxi.png b/platform/www/lib/images/fileicons/sxi.png
new file mode 100644
index 0000000..a0fb654
--- /dev/null
+++ b/platform/www/lib/images/fileicons/sxi.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/sxw.png b/platform/www/lib/images/fileicons/sxw.png
new file mode 100644
index 0000000..865dc0c
--- /dev/null
+++ b/platform/www/lib/images/fileicons/sxw.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/tar.png b/platform/www/lib/images/fileicons/tar.png
new file mode 100644
index 0000000..8f9fd0f
--- /dev/null
+++ b/platform/www/lib/images/fileicons/tar.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/tgz.png b/platform/www/lib/images/fileicons/tgz.png
new file mode 100644
index 0000000..8423ef0
--- /dev/null
+++ b/platform/www/lib/images/fileicons/tgz.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/txt.png b/platform/www/lib/images/fileicons/txt.png
new file mode 100644
index 0000000..1619cc4
--- /dev/null
+++ b/platform/www/lib/images/fileicons/txt.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/wav.png b/platform/www/lib/images/fileicons/wav.png
new file mode 100644
index 0000000..80eac97
--- /dev/null
+++ b/platform/www/lib/images/fileicons/wav.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/webm.png b/platform/www/lib/images/fileicons/webm.png
new file mode 100644
index 0000000..cec3e6d
--- /dev/null
+++ b/platform/www/lib/images/fileicons/webm.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/xls.png b/platform/www/lib/images/fileicons/xls.png
new file mode 100644
index 0000000..be9b42f
--- /dev/null
+++ b/platform/www/lib/images/fileicons/xls.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/xlsx.png b/platform/www/lib/images/fileicons/xlsx.png
new file mode 100644
index 0000000..fd5d4f1
--- /dev/null
+++ b/platform/www/lib/images/fileicons/xlsx.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/xml.png b/platform/www/lib/images/fileicons/xml.png
new file mode 100644
index 0000000..2a96d8b
--- /dev/null
+++ b/platform/www/lib/images/fileicons/xml.png
Binary files differ
diff --git a/platform/www/lib/images/fileicons/zip.png b/platform/www/lib/images/fileicons/zip.png
new file mode 100644
index 0000000..4ce08bf
--- /dev/null
+++ b/platform/www/lib/images/fileicons/zip.png
Binary files differ
diff --git a/platform/www/lib/images/history.png b/platform/www/lib/images/history.png
new file mode 100644
index 0000000..f6af0f6
--- /dev/null
+++ b/platform/www/lib/images/history.png
Binary files differ
diff --git a/platform/www/lib/images/icon-list.png b/platform/www/lib/images/icon-list.png
new file mode 100644
index 0000000..4ae738a
--- /dev/null
+++ b/platform/www/lib/images/icon-list.png
Binary files differ
diff --git a/platform/www/lib/images/icon-sort.png b/platform/www/lib/images/icon-sort.png
new file mode 100644
index 0000000..190397e
--- /dev/null
+++ b/platform/www/lib/images/icon-sort.png
Binary files differ
diff --git a/platform/www/lib/images/index.html b/platform/www/lib/images/index.html
new file mode 100644
index 0000000..977f90e
--- /dev/null
+++ b/platform/www/lib/images/index.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="refresh" content="0; URL=../../" />
+<meta name="robots" content="noindex" />
+<title>nothing here...</title>
+</head>
+<body>
+<!-- this is just here to prevent directory browsing -->
+</body>
+</html>
diff --git a/platform/www/lib/images/info.png b/platform/www/lib/images/info.png
new file mode 100644
index 0000000..5e23364
--- /dev/null
+++ b/platform/www/lib/images/info.png
Binary files differ
diff --git a/platform/www/lib/images/interwiki.png b/platform/www/lib/images/interwiki.png
new file mode 100644
index 0000000..10a2bbe
--- /dev/null
+++ b/platform/www/lib/images/interwiki.png
Binary files differ
diff --git a/platform/www/lib/images/interwiki/amazon.de.gif b/platform/www/lib/images/interwiki/amazon.de.gif
new file mode 100644
index 0000000..a0d2cd4
--- /dev/null
+++ b/platform/www/lib/images/interwiki/amazon.de.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/amazon.gif b/platform/www/lib/images/interwiki/amazon.gif
new file mode 100644
index 0000000..a0d2cd4
--- /dev/null
+++ b/platform/www/lib/images/interwiki/amazon.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/amazon.uk.gif b/platform/www/lib/images/interwiki/amazon.uk.gif
new file mode 100644
index 0000000..a0d2cd4
--- /dev/null
+++ b/platform/www/lib/images/interwiki/amazon.uk.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/callto.gif b/platform/www/lib/images/interwiki/callto.gif
new file mode 100644
index 0000000..60158c5
--- /dev/null
+++ b/platform/www/lib/images/interwiki/callto.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/doku.gif b/platform/www/lib/images/interwiki/doku.gif
new file mode 100644
index 0000000..3ccf012
--- /dev/null
+++ b/platform/www/lib/images/interwiki/doku.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/google.gif b/platform/www/lib/images/interwiki/google.gif
new file mode 100644
index 0000000..3a28437
--- /dev/null
+++ b/platform/www/lib/images/interwiki/google.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/paypal.gif b/platform/www/lib/images/interwiki/paypal.gif
new file mode 100644
index 0000000..633797f
--- /dev/null
+++ b/platform/www/lib/images/interwiki/paypal.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/phpfn.gif b/platform/www/lib/images/interwiki/phpfn.gif
new file mode 100644
index 0000000..89ac1db
--- /dev/null
+++ b/platform/www/lib/images/interwiki/phpfn.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/skype.gif b/platform/www/lib/images/interwiki/skype.gif
new file mode 100644
index 0000000..d9bd575
--- /dev/null
+++ b/platform/www/lib/images/interwiki/skype.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/tel.gif b/platform/www/lib/images/interwiki/tel.gif
new file mode 100644
index 0000000..60158c5
--- /dev/null
+++ b/platform/www/lib/images/interwiki/tel.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/user.png b/platform/www/lib/images/interwiki/user.png
new file mode 100644
index 0000000..da84e3d
--- /dev/null
+++ b/platform/www/lib/images/interwiki/user.png
Binary files differ
diff --git a/platform/www/lib/images/interwiki/wp.gif b/platform/www/lib/images/interwiki/wp.gif
new file mode 100644
index 0000000..b07fd89
--- /dev/null
+++ b/platform/www/lib/images/interwiki/wp.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/wpde.gif b/platform/www/lib/images/interwiki/wpde.gif
new file mode 100644
index 0000000..b07fd89
--- /dev/null
+++ b/platform/www/lib/images/interwiki/wpde.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/wpes.gif b/platform/www/lib/images/interwiki/wpes.gif
new file mode 100644
index 0000000..b07fd89
--- /dev/null
+++ b/platform/www/lib/images/interwiki/wpes.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/wpfr.gif b/platform/www/lib/images/interwiki/wpfr.gif
new file mode 100644
index 0000000..b07fd89
--- /dev/null
+++ b/platform/www/lib/images/interwiki/wpfr.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/wpjp.gif b/platform/www/lib/images/interwiki/wpjp.gif
new file mode 100644
index 0000000..b07fd89
--- /dev/null
+++ b/platform/www/lib/images/interwiki/wpjp.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/wpmeta.gif b/platform/www/lib/images/interwiki/wpmeta.gif
new file mode 100644
index 0000000..b07fd89
--- /dev/null
+++ b/platform/www/lib/images/interwiki/wpmeta.gif
Binary files differ
diff --git a/platform/www/lib/images/interwiki/wppl.gif b/platform/www/lib/images/interwiki/wppl.gif
new file mode 100644
index 0000000..b07fd89
--- /dev/null
+++ b/platform/www/lib/images/interwiki/wppl.gif
Binary files differ
diff --git a/platform/www/lib/images/larger.gif b/platform/www/lib/images/larger.gif
new file mode 100644
index 0000000..e137c92
--- /dev/null
+++ b/platform/www/lib/images/larger.gif
Binary files differ
diff --git a/platform/www/lib/images/license/badge/cc-by-nc-nd.png b/platform/www/lib/images/license/badge/cc-by-nc-nd.png
new file mode 100644
index 0000000..c84aff1
--- /dev/null
+++ b/platform/www/lib/images/license/badge/cc-by-nc-nd.png
Binary files differ
diff --git a/platform/www/lib/images/license/badge/cc-by-nc-sa.png b/platform/www/lib/images/license/badge/cc-by-nc-sa.png
new file mode 100644
index 0000000..e7b5784
--- /dev/null
+++ b/platform/www/lib/images/license/badge/cc-by-nc-sa.png
Binary files differ
diff --git a/platform/www/lib/images/license/badge/cc-by-nc.png b/platform/www/lib/images/license/badge/cc-by-nc.png
new file mode 100644
index 0000000..b422cdc
--- /dev/null
+++ b/platform/www/lib/images/license/badge/cc-by-nc.png
Binary files differ
diff --git a/platform/www/lib/images/license/badge/cc-by-nd.png b/platform/www/lib/images/license/badge/cc-by-nd.png
new file mode 100644
index 0000000..1832299
--- /dev/null
+++ b/platform/www/lib/images/license/badge/cc-by-nd.png
Binary files differ
diff --git a/platform/www/lib/images/license/badge/cc-by-sa.png b/platform/www/lib/images/license/badge/cc-by-sa.png
new file mode 100644
index 0000000..5749f65
--- /dev/null
+++ b/platform/www/lib/images/license/badge/cc-by-sa.png
Binary files differ
diff --git a/platform/www/lib/images/license/badge/cc-by.png b/platform/www/lib/images/license/badge/cc-by.png
new file mode 100644
index 0000000..700679a
--- /dev/null
+++ b/platform/www/lib/images/license/badge/cc-by.png
Binary files differ
diff --git a/platform/www/lib/images/license/badge/cc-zero.png b/platform/www/lib/images/license/badge/cc-zero.png
new file mode 100644
index 0000000..e6d82bf
--- /dev/null
+++ b/platform/www/lib/images/license/badge/cc-zero.png
Binary files differ
diff --git a/platform/www/lib/images/license/badge/cc.png b/platform/www/lib/images/license/badge/cc.png
new file mode 100644
index 0000000..e28f32c
--- /dev/null
+++ b/platform/www/lib/images/license/badge/cc.png
Binary files differ
diff --git a/platform/www/lib/images/license/badge/gnufdl.png b/platform/www/lib/images/license/badge/gnufdl.png
new file mode 100644
index 0000000..635de2b
--- /dev/null
+++ b/platform/www/lib/images/license/badge/gnufdl.png
Binary files differ
diff --git a/platform/www/lib/images/license/badge/publicdomain.png b/platform/www/lib/images/license/badge/publicdomain.png
new file mode 100644
index 0000000..fd742cc
--- /dev/null
+++ b/platform/www/lib/images/license/badge/publicdomain.png
Binary files differ
diff --git a/platform/www/lib/images/license/button/cc-by-nc-nd.png b/platform/www/lib/images/license/button/cc-by-nc-nd.png
new file mode 100644
index 0000000..994025f
--- /dev/null
+++ b/platform/www/lib/images/license/button/cc-by-nc-nd.png
Binary files differ
diff --git a/platform/www/lib/images/license/button/cc-by-nc-sa.png b/platform/www/lib/images/license/button/cc-by-nc-sa.png
new file mode 100644
index 0000000..3b896bd
--- /dev/null
+++ b/platform/www/lib/images/license/button/cc-by-nc-sa.png
Binary files differ
diff --git a/platform/www/lib/images/license/button/cc-by-nc.png b/platform/www/lib/images/license/button/cc-by-nc.png
new file mode 100644
index 0000000..d5be8f8
--- /dev/null
+++ b/platform/www/lib/images/license/button/cc-by-nc.png
Binary files differ
diff --git a/platform/www/lib/images/license/button/cc-by-nd.png b/platform/www/lib/images/license/button/cc-by-nd.png
new file mode 100644
index 0000000..e1918b0
--- /dev/null
+++ b/platform/www/lib/images/license/button/cc-by-nd.png
Binary files differ
diff --git a/platform/www/lib/images/license/button/cc-by-sa.png b/platform/www/lib/images/license/button/cc-by-sa.png
new file mode 100644
index 0000000..9b9b522
--- /dev/null
+++ b/platform/www/lib/images/license/button/cc-by-sa.png
Binary files differ
diff --git a/platform/www/lib/images/license/button/cc-by.png b/platform/www/lib/images/license/button/cc-by.png
new file mode 100644
index 0000000..53b1dea
--- /dev/null
+++ b/platform/www/lib/images/license/button/cc-by.png
Binary files differ
diff --git a/platform/www/lib/images/license/button/cc-zero.png b/platform/www/lib/images/license/button/cc-zero.png
new file mode 100644
index 0000000..e6a1a5b
--- /dev/null
+++ b/platform/www/lib/images/license/button/cc-zero.png
Binary files differ
diff --git a/platform/www/lib/images/license/button/cc.png b/platform/www/lib/images/license/button/cc.png
new file mode 100644
index 0000000..e04958a
--- /dev/null
+++ b/platform/www/lib/images/license/button/cc.png
Binary files differ
diff --git a/platform/www/lib/images/license/button/gnufdl.png b/platform/www/lib/images/license/button/gnufdl.png
new file mode 100644
index 0000000..b0e0793
--- /dev/null
+++ b/platform/www/lib/images/license/button/gnufdl.png
Binary files differ
diff --git a/platform/www/lib/images/license/button/publicdomain.png b/platform/www/lib/images/license/button/publicdomain.png
new file mode 100644
index 0000000..b301baf
--- /dev/null
+++ b/platform/www/lib/images/license/button/publicdomain.png
Binary files differ
diff --git a/platform/www/lib/images/magnifier.png b/platform/www/lib/images/magnifier.png
new file mode 100644
index 0000000..014fa92
--- /dev/null
+++ b/platform/www/lib/images/magnifier.png
Binary files differ
diff --git a/platform/www/lib/images/media_align_center.png b/platform/www/lib/images/media_align_center.png
new file mode 100644
index 0000000..8b30a05
--- /dev/null
+++ b/platform/www/lib/images/media_align_center.png
Binary files differ
diff --git a/platform/www/lib/images/media_align_left.png b/platform/www/lib/images/media_align_left.png
new file mode 100644
index 0000000..d32bbc2
--- /dev/null
+++ b/platform/www/lib/images/media_align_left.png
Binary files differ
diff --git a/platform/www/lib/images/media_align_noalign.png b/platform/www/lib/images/media_align_noalign.png
new file mode 100644
index 0000000..e6ce857
--- /dev/null
+++ b/platform/www/lib/images/media_align_noalign.png
Binary files differ
diff --git a/platform/www/lib/images/media_align_right.png b/platform/www/lib/images/media_align_right.png
new file mode 100644
index 0000000..32a5cb0
--- /dev/null
+++ b/platform/www/lib/images/media_align_right.png
Binary files differ
diff --git a/platform/www/lib/images/media_link_direct.png b/platform/www/lib/images/media_link_direct.png
new file mode 100644
index 0000000..13d24ad
--- /dev/null
+++ b/platform/www/lib/images/media_link_direct.png
Binary files differ
diff --git a/platform/www/lib/images/media_link_displaylnk.png b/platform/www/lib/images/media_link_displaylnk.png
new file mode 100644
index 0000000..102834e
--- /dev/null
+++ b/platform/www/lib/images/media_link_displaylnk.png
Binary files differ
diff --git a/platform/www/lib/images/media_link_lnk.png b/platform/www/lib/images/media_link_lnk.png
new file mode 100644
index 0000000..5db14ad
--- /dev/null
+++ b/platform/www/lib/images/media_link_lnk.png
Binary files differ
diff --git a/platform/www/lib/images/media_link_nolnk.png b/platform/www/lib/images/media_link_nolnk.png
new file mode 100644
index 0000000..d277ac9
--- /dev/null
+++ b/platform/www/lib/images/media_link_nolnk.png
Binary files differ
diff --git a/platform/www/lib/images/media_size_large.png b/platform/www/lib/images/media_size_large.png
new file mode 100644
index 0000000..c4f745e
--- /dev/null
+++ b/platform/www/lib/images/media_size_large.png
Binary files differ
diff --git a/platform/www/lib/images/media_size_medium.png b/platform/www/lib/images/media_size_medium.png
new file mode 100644
index 0000000..580c63e
--- /dev/null
+++ b/platform/www/lib/images/media_size_medium.png
Binary files differ
diff --git a/platform/www/lib/images/media_size_original.png b/platform/www/lib/images/media_size_original.png
new file mode 100644
index 0000000..60d1925
--- /dev/null
+++ b/platform/www/lib/images/media_size_original.png
Binary files differ
diff --git a/platform/www/lib/images/media_size_small.png b/platform/www/lib/images/media_size_small.png
new file mode 100644
index 0000000..8d5a629
--- /dev/null
+++ b/platform/www/lib/images/media_size_small.png
Binary files differ
diff --git a/platform/www/lib/images/mediamanager.png b/platform/www/lib/images/mediamanager.png
new file mode 100644
index 0000000..5093381
--- /dev/null
+++ b/platform/www/lib/images/mediamanager.png
Binary files differ
diff --git a/platform/www/lib/images/menu/00-default_checkbox-blank-circle-outline.svg b/platform/www/lib/images/menu/00-default_checkbox-blank-circle-outline.svg
new file mode 100644
index 0000000..e8f8c07
--- /dev/null
+++ b/platform/www/lib/images/menu/00-default_checkbox-blank-circle-outline.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 20a8 8 0 0 1-8-8 8 8 0 0 1 8-8 8 8 0 0 1 8 8 8 8 0 0 1-8 8m0-18A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/01-edit_pencil.svg b/platform/www/lib/images/menu/01-edit_pencil.svg
new file mode 100644
index 0000000..e3a4faa
--- /dev/null
+++ b/platform/www/lib/images/menu/01-edit_pencil.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/02-create_pencil.svg b/platform/www/lib/images/menu/02-create_pencil.svg
new file mode 100644
index 0000000..4c30b49
--- /dev/null
+++ b/platform/www/lib/images/menu/02-create_pencil.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M13.118 16.118h3v-3h2v3h3v2h-3v3h-2v-3h-3zM20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/03-draft_android-studio.svg b/platform/www/lib/images/menu/03-draft_android-studio.svg
new file mode 100644
index 0000000..589658d
--- /dev/null
+++ b/platform/www/lib/images/menu/03-draft_android-studio.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M11 2h2v2h.5A1.5 1.5 0 0 1 15 5.5V9l-.44.44 1.64 2.84C17.31 11.19 18 9.68 18 8h2c0 2.42-1.07 4.59-2.77 6.06l3.14 5.44.13 2.22-1.87-1.22-3.07-5.33c-1.06.53-2.28.83-3.56.83-1.28 0-2.5-.3-3.56-.83L5.37 20.5 3.5 21.72l.13-2.22L9.44 9.44 9 9V5.5A1.5 1.5 0 0 1 10.5 4h.5V2M9.44 13.43c.78.37 1.65.57 2.56.57.91 0 1.78-.2 2.56-.57L13.1 10.9h-.01c-.62.6-1.56.6-2.18 0h-.01l-1.46 2.53M12 6a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/04-show_file-document.svg b/platform/www/lib/images/menu/04-show_file-document.svg
new file mode 100644
index 0000000..0eed274
--- /dev/null
+++ b/platform/www/lib/images/menu/04-show_file-document.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m9 16v-2H6v2h9m3-4v-2H6v2h12z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/05-source_file-xml.svg b/platform/www/lib/images/menu/05-source_file-xml.svg
new file mode 100644
index 0000000..7e00342
--- /dev/null
+++ b/platform/www/lib/images/menu/05-source_file-xml.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m.12 13.5l3.74 3.74 1.42-1.41-2.33-2.33 2.33-2.33-1.42-1.41-3.74 3.74m11.16 0l-3.74-3.74-1.42 1.41 2.33 2.33-2.33 2.33 1.42 1.41 3.74-3.74z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/06-revert_replay.svg b/platform/www/lib/images/menu/06-revert_replay.svg
new file mode 100644
index 0000000..0911e5b
--- /dev/null
+++ b/platform/www/lib/images/menu/06-revert_replay.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 5V1L7 6l5 5V7a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6H4a8 8 0 0 0 8 8 8 8 0 0 0 8-8 8 8 0 0 0-8-8z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/07-revisions_history.svg b/platform/www/lib/images/menu/07-revisions_history.svg
new file mode 100644
index 0000000..cedbc1b
--- /dev/null
+++ b/platform/www/lib/images/menu/07-revisions_history.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M11 7v5.11l4.71 2.79.79-1.28-4-2.37V7m0-5C8.97 2 5.91 3.92 4.27 6.77L2 4.5V11h6.5L5.75 8.25C6.96 5.73 9.5 4 12.5 4a7.5 7.5 0 0 1 7.5 7.5 7.5 7.5 0 0 1-7.5 7.5c-3.27 0-6.03-2.09-7.06-5h-2.1c1.1 4.03 4.77 7 9.16 7 5.24 0 9.5-4.25 9.5-9.5A9.5 9.5 0 0 0 12.5 2z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/08-backlink_link-variant.svg b/platform/www/lib/images/menu/08-backlink_link-variant.svg
new file mode 100644
index 0000000..4d639a5
--- /dev/null
+++ b/platform/www/lib/images/menu/08-backlink_link-variant.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24m2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/09-subscribe_email-outline.svg b/platform/www/lib/images/menu/09-subscribe_email-outline.svg
new file mode 100644
index 0000000..3b23dac
--- /dev/null
+++ b/platform/www/lib/images/menu/09-subscribe_email-outline.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2m0 14H4V8l8 5 8-5v10m0-12l-8 5-8-5h16z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/10-top_arrow-up.svg b/platform/www/lib/images/menu/10-top_arrow-up.svg
new file mode 100644
index 0000000..61003a8
--- /dev/null
+++ b/platform/www/lib/images/menu/10-top_arrow-up.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/11-mediamanager_folder-image.svg b/platform/www/lib/images/menu/11-mediamanager_folder-image.svg
new file mode 100644
index 0000000..4376fdf
--- /dev/null
+++ b/platform/www/lib/images/menu/11-mediamanager_folder-image.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M5 17l4.5-6 3.5 4.5 2.5-3L19 17m1-11h-8l-2-2H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/12-back_arrow-left.svg b/platform/www/lib/images/menu/12-back_arrow-left.svg
new file mode 100644
index 0000000..d8011b1
--- /dev/null
+++ b/platform/www/lib/images/menu/12-back_arrow-left.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/account-card-details.svg b/platform/www/lib/images/menu/account-card-details.svg
new file mode 100644
index 0000000..ba74256
--- /dev/null
+++ b/platform/www/lib/images/menu/account-card-details.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M2 3h20c1.05 0 2 .95 2 2v14c0 1.05-.95 2-2 2H2c-1.05 0-2-.95-2-2V5c0-1.05.95-2 2-2m12 3v1h8V6h-8m0 2v1h8V8h-8m0 2v1h7v-1h-7m-6 3.91C6 13.91 2 15 2 17v1h12v-1c0-2-4-3.09-6-3.09M8 6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/account-plus.svg b/platform/www/lib/images/menu/account-plus.svg
new file mode 100644
index 0000000..a978bf1
--- /dev/null
+++ b/platform/www/lib/images/menu/account-plus.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M15 14c-2.67 0-8 1.33-8 4v2h16v-2c0-2.67-5.33-4-8-4m-9-4V7H4v3H1v2h3v3h2v-3h3v-2m6 2a4 4 0 0 0 4-4 4 4 0 0 0-4-4 4 4 0 0 0-4 4 4 4 0 0 0 4 4z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/calendar-clock.svg b/platform/www/lib/images/menu/calendar-clock.svg
new file mode 100644
index 0000000..b19735d
--- /dev/null
+++ b/platform/www/lib/images/menu/calendar-clock.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M15 13h1.5v2.82l2.44 1.41-.75 1.3L15 16.69V13m4-5H5v11h4.67c-.43-.91-.67-1.93-.67-3a7 7 0 0 1 7-7c1.07 0 2.09.24 3 .67V8M5 21a2 2 0 0 1-2-2V5c0-1.11.89-2 2-2h1V1h2v2h8V1h2v2h1a2 2 0 0 1 2 2v6.1c1.24 1.26 2 2.99 2 4.9a7 7 0 0 1-7 7c-1.91 0-3.64-.76-4.9-2H5m11-9.85A4.85 4.85 0 0 0 11.15 16c0 2.68 2.17 4.85 4.85 4.85A4.85 4.85 0 0 0 20.85 16c0-2.68-2.17-4.85-4.85-4.85z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/file-tree.svg b/platform/www/lib/images/menu/file-tree.svg
new file mode 100644
index 0000000..0f26188
--- /dev/null
+++ b/platform/www/lib/images/menu/file-tree.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M3 3h6v4H3V3m12 7h6v4h-6v-4m0 7h6v4h-6v-4m-2-4H7v5h6v2H5V9h2v2h6v2z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/folder-multiple-image.svg b/platform/www/lib/images/menu/folder-multiple-image.svg
new file mode 100644
index 0000000..f66aaad
--- /dev/null
+++ b/platform/www/lib/images/menu/folder-multiple-image.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7 15l4.5-6 3.5 4.5 2.5-3L21 15m1-11h-8l-2-2H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2M2 6H0v14a2 2 0 0 0 2 2h18v-2H2V6z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/lock-reset.svg b/platform/www/lib/images/menu/lock-reset.svg
new file mode 100644
index 0000000..49693c6
--- /dev/null
+++ b/platform/www/lib/images/menu/lock-reset.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12.63 2c5.53 0 10.01 4.5 10.01 10s-4.48 10-10.01 10c-3.51 0-6.58-1.82-8.37-4.57l1.58-1.25C7.25 18.47 9.76 20 12.64 20a8 8 0 0 0 8-8 8 8 0 0 0-8-8C8.56 4 5.2 7.06 4.71 11h2.76l-3.74 3.73L0 11h2.69c.5-5.05 4.76-9 9.94-9m2.96 8.24c.5.01.91.41.91.92v4.61c0 .5-.41.92-.92.92h-5.53c-.51 0-.92-.42-.92-.92v-4.61c0-.51.41-.91.91-.92V9.23c0-1.53 1.25-2.77 2.77-2.77 1.53 0 2.78 1.24 2.78 2.77v1.01m-2.78-2.38c-.75 0-1.37.61-1.37 1.37v1.01h2.75V9.23c0-.76-.62-1.37-1.38-1.37z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/login.svg b/platform/www/lib/images/menu/login.svg
new file mode 100644
index 0000000..07a9896
--- /dev/null
+++ b/platform/www/lib/images/menu/login.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 17.25V14H3v-4h7V6.75L15.25 12 10 17.25M8 2h9a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2v-4h2v4h9V4H8v4H6V4a2 2 0 0 1 2-2z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/logout.svg b/platform/www/lib/images/menu/logout.svg
new file mode 100644
index 0000000..97d3158
--- /dev/null
+++ b/platform/www/lib/images/menu/logout.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M17 17.25V14h-7v-4h7V6.75L22.25 12 17 17.25M13 2a2 2 0 0 1 2 2v4h-2V4H4v16h9v-4h2v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/menu/settings.svg b/platform/www/lib/images/menu/settings.svg
new file mode 100644
index 0000000..ced9871
--- /dev/null
+++ b/platform/www/lib/images/menu/settings.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 15.5A3.5 3.5 0 0 1 8.5 12 3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97 0-.33-.03-.66-.07-1l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65A.506.506 0 0 0 14 2h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.34-.07.67-.07 1 0 .33.03.65.07.97l-2.11 1.66c-.19.15-.25.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1.01c.52.4 1.06.74 1.69.99l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.26 1.17-.59 1.69-.99l2.49 1.01c.22.08.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.66z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/images/minus.gif b/platform/www/lib/images/minus.gif
new file mode 100644
index 0000000..7e8cbd7
--- /dev/null
+++ b/platform/www/lib/images/minus.gif
Binary files differ
diff --git a/platform/www/lib/images/notify.png b/platform/www/lib/images/notify.png
new file mode 100644
index 0000000..f6c56ee
--- /dev/null
+++ b/platform/www/lib/images/notify.png
Binary files differ
diff --git a/platform/www/lib/images/ns.png b/platform/www/lib/images/ns.png
new file mode 100644
index 0000000..77e03b1
--- /dev/null
+++ b/platform/www/lib/images/ns.png
Binary files differ
diff --git a/platform/www/lib/images/open.png b/platform/www/lib/images/open.png
new file mode 100644
index 0000000..b9e4fdf
--- /dev/null
+++ b/platform/www/lib/images/open.png
Binary files differ
diff --git a/platform/www/lib/images/page.png b/platform/www/lib/images/page.png
new file mode 100644
index 0000000..b1b7ebe
--- /dev/null
+++ b/platform/www/lib/images/page.png
Binary files differ
diff --git a/platform/www/lib/images/plus.gif b/platform/www/lib/images/plus.gif
new file mode 100644
index 0000000..3da3b94
--- /dev/null
+++ b/platform/www/lib/images/plus.gif
Binary files differ
diff --git a/platform/www/lib/images/resizecol.png b/platform/www/lib/images/resizecol.png
new file mode 100644
index 0000000..91ad7d1
--- /dev/null
+++ b/platform/www/lib/images/resizecol.png
Binary files differ
diff --git a/platform/www/lib/images/smaller.gif b/platform/www/lib/images/smaller.gif
new file mode 100644
index 0000000..66d3a51
--- /dev/null
+++ b/platform/www/lib/images/smaller.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/delete.gif b/platform/www/lib/images/smileys/delete.gif
new file mode 100644
index 0000000..e94c68c
--- /dev/null
+++ b/platform/www/lib/images/smileys/delete.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/facepalm.gif b/platform/www/lib/images/smileys/facepalm.gif
new file mode 100644
index 0000000..5bebb20
--- /dev/null
+++ b/platform/www/lib/images/smileys/facepalm.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/fixme.gif b/platform/www/lib/images/smileys/fixme.gif
new file mode 100644
index 0000000..e191413
--- /dev/null
+++ b/platform/www/lib/images/smileys/fixme.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_arrow.gif b/platform/www/lib/images/smileys/icon_arrow.gif
new file mode 100644
index 0000000..6771def
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_arrow.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_biggrin.gif b/platform/www/lib/images/smileys/icon_biggrin.gif
new file mode 100644
index 0000000..aa29c14
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_biggrin.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_confused.gif b/platform/www/lib/images/smileys/icon_confused.gif
new file mode 100644
index 0000000..0ea9ed2
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_confused.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_cool.gif b/platform/www/lib/images/smileys/icon_cool.gif
new file mode 100644
index 0000000..3469ad4
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_cool.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_cry.gif b/platform/www/lib/images/smileys/icon_cry.gif
new file mode 100644
index 0000000..25aea57
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_cry.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_doubt.gif b/platform/www/lib/images/smileys/icon_doubt.gif
new file mode 100644
index 0000000..b4afc6d
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_doubt.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_doubt2.gif b/platform/www/lib/images/smileys/icon_doubt2.gif
new file mode 100644
index 0000000..1f57eb9
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_doubt2.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_eek.gif b/platform/www/lib/images/smileys/icon_eek.gif
new file mode 100644
index 0000000..276b01d
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_eek.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_evil.gif b/platform/www/lib/images/smileys/icon_evil.gif
new file mode 100644
index 0000000..d756916
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_evil.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_exclaim.gif b/platform/www/lib/images/smileys/icon_exclaim.gif
new file mode 100644
index 0000000..215b32e
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_exclaim.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_frown.gif b/platform/www/lib/images/smileys/icon_frown.gif
new file mode 100644
index 0000000..d46caf7
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_frown.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_fun.gif b/platform/www/lib/images/smileys/icon_fun.gif
new file mode 100644
index 0000000..6d3c442
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_fun.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_idea.gif b/platform/www/lib/images/smileys/icon_idea.gif
new file mode 100644
index 0000000..41eaa06
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_idea.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_kaddi.gif b/platform/www/lib/images/smileys/icon_kaddi.gif
new file mode 100644
index 0000000..56344bb
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_kaddi.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_lol.gif b/platform/www/lib/images/smileys/icon_lol.gif
new file mode 100644
index 0000000..d1c20c0
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_lol.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_mrgreen.gif b/platform/www/lib/images/smileys/icon_mrgreen.gif
new file mode 100644
index 0000000..fc5d916
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_mrgreen.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_neutral.gif b/platform/www/lib/images/smileys/icon_neutral.gif
new file mode 100644
index 0000000..c82a974
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_neutral.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_question.gif b/platform/www/lib/images/smileys/icon_question.gif
new file mode 100644
index 0000000..4e30924
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_question.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_razz.gif b/platform/www/lib/images/smileys/icon_razz.gif
new file mode 100644
index 0000000..310655e
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_razz.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_redface.gif b/platform/www/lib/images/smileys/icon_redface.gif
new file mode 100644
index 0000000..160c20f
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_redface.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_rolleyes.gif b/platform/www/lib/images/smileys/icon_rolleyes.gif
new file mode 100644
index 0000000..502c5c1
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_rolleyes.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_sad.gif b/platform/www/lib/images/smileys/icon_sad.gif
new file mode 100644
index 0000000..d46caf7
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_sad.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_silenced.gif b/platform/www/lib/images/smileys/icon_silenced.gif
new file mode 100644
index 0000000..5f722e0
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_silenced.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_smile.gif b/platform/www/lib/images/smileys/icon_smile.gif
new file mode 100644
index 0000000..df125e2
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_smile.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_smile2.gif b/platform/www/lib/images/smileys/icon_smile2.gif
new file mode 100644
index 0000000..6b4909c
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_smile2.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_surprised.gif b/platform/www/lib/images/smileys/icon_surprised.gif
new file mode 100644
index 0000000..aaa94f1
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_surprised.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_twisted.gif b/platform/www/lib/images/smileys/icon_twisted.gif
new file mode 100644
index 0000000..eaec193
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_twisted.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/icon_wink.gif b/platform/www/lib/images/smileys/icon_wink.gif
new file mode 100644
index 0000000..78b6ad3
--- /dev/null
+++ b/platform/www/lib/images/smileys/icon_wink.gif
Binary files differ
diff --git a/platform/www/lib/images/smileys/index.php b/platform/www/lib/images/smileys/index.php
new file mode 100644
index 0000000..5749666
--- /dev/null
+++ b/platform/www/lib/images/smileys/index.php
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+ <title>Smileys</title>
+
+ <style>
+ body {
+ background-color: #ccc;
+ font-family: Arial;
+ }
+
+ .box {
+ width: 200px;
+ float: left;
+ padding: 0.5em;
+ margin: 0;
+ }
+
+ .white {
+ background-color: #fff;
+ }
+
+ .black {
+ background-color: #000;
+ }
+ </style>
+
+</head>
+<body>
+
+<?php
+$smi_list = '';
+foreach (glob('*.gif') as $img) {
+ $smi_list .= '<img src="'.$img.'" alt="'.$img.'" title="'.$img.'" /> ';
+}
+if(is_dir('local')) {
+ $smi_list .= '<hr />';
+ foreach (glob('local/*.gif') as $img) {
+ $smi_list .= '<img src="'.$img.'" alt="'.$img.'" title="'.$img.'" /> ';
+ }
+}
+
+echo '<div class="white box">
+'.$smi_list.'
+</div>
+
+<div class="black box">
+'.$smi_list;
+?>
+</div>
+
+</body>
+</html>
diff --git a/platform/www/lib/images/success.png b/platform/www/lib/images/success.png
new file mode 100644
index 0000000..200142f
--- /dev/null
+++ b/platform/www/lib/images/success.png
Binary files differ
diff --git a/platform/www/lib/images/throbber.gif b/platform/www/lib/images/throbber.gif
new file mode 100644
index 0000000..27178a8
--- /dev/null
+++ b/platform/www/lib/images/throbber.gif
Binary files differ
diff --git a/platform/www/lib/images/toolbar/bold.png b/platform/www/lib/images/toolbar/bold.png
new file mode 100644
index 0000000..8f425e9
--- /dev/null
+++ b/platform/www/lib/images/toolbar/bold.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/chars.png b/platform/www/lib/images/toolbar/chars.png
new file mode 100644
index 0000000..a906bc8
--- /dev/null
+++ b/platform/www/lib/images/toolbar/chars.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/h.png b/platform/www/lib/images/toolbar/h.png
new file mode 100644
index 0000000..7e43d64
--- /dev/null
+++ b/platform/www/lib/images/toolbar/h.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/h1.png b/platform/www/lib/images/toolbar/h1.png
new file mode 100644
index 0000000..9f1970f
--- /dev/null
+++ b/platform/www/lib/images/toolbar/h1.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/h2.png b/platform/www/lib/images/toolbar/h2.png
new file mode 100644
index 0000000..adec9ec
--- /dev/null
+++ b/platform/www/lib/images/toolbar/h2.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/h3.png b/platform/www/lib/images/toolbar/h3.png
new file mode 100644
index 0000000..a758b89
--- /dev/null
+++ b/platform/www/lib/images/toolbar/h3.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/h4.png b/platform/www/lib/images/toolbar/h4.png
new file mode 100644
index 0000000..9cd6061
--- /dev/null
+++ b/platform/www/lib/images/toolbar/h4.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/h5.png b/platform/www/lib/images/toolbar/h5.png
new file mode 100644
index 0000000..86b7259
--- /dev/null
+++ b/platform/www/lib/images/toolbar/h5.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/hequal.png b/platform/www/lib/images/toolbar/hequal.png
new file mode 100644
index 0000000..869a2dd
--- /dev/null
+++ b/platform/www/lib/images/toolbar/hequal.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/hminus.png b/platform/www/lib/images/toolbar/hminus.png
new file mode 100644
index 0000000..1a99ee4
--- /dev/null
+++ b/platform/www/lib/images/toolbar/hminus.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/hplus.png b/platform/www/lib/images/toolbar/hplus.png
new file mode 100644
index 0000000..92efcdb
--- /dev/null
+++ b/platform/www/lib/images/toolbar/hplus.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/hr.png b/platform/www/lib/images/toolbar/hr.png
new file mode 100644
index 0000000..40ae210
--- /dev/null
+++ b/platform/www/lib/images/toolbar/hr.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/image.png b/platform/www/lib/images/toolbar/image.png
new file mode 100644
index 0000000..5cc7afa
--- /dev/null
+++ b/platform/www/lib/images/toolbar/image.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/italic.png b/platform/www/lib/images/toolbar/italic.png
new file mode 100644
index 0000000..b37dc2d
--- /dev/null
+++ b/platform/www/lib/images/toolbar/italic.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/link.png b/platform/www/lib/images/toolbar/link.png
new file mode 100644
index 0000000..3d2180a
--- /dev/null
+++ b/platform/www/lib/images/toolbar/link.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/linkextern.png b/platform/www/lib/images/toolbar/linkextern.png
new file mode 100644
index 0000000..e854572
--- /dev/null
+++ b/platform/www/lib/images/toolbar/linkextern.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/mono.png b/platform/www/lib/images/toolbar/mono.png
new file mode 100644
index 0000000..a6f56d6
--- /dev/null
+++ b/platform/www/lib/images/toolbar/mono.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/ol.png b/platform/www/lib/images/toolbar/ol.png
new file mode 100644
index 0000000..c12229a
--- /dev/null
+++ b/platform/www/lib/images/toolbar/ol.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/sig.png b/platform/www/lib/images/toolbar/sig.png
new file mode 100644
index 0000000..72fdad0
--- /dev/null
+++ b/platform/www/lib/images/toolbar/sig.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/smiley.png b/platform/www/lib/images/toolbar/smiley.png
new file mode 100644
index 0000000..54f1e6f
--- /dev/null
+++ b/platform/www/lib/images/toolbar/smiley.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/strike.png b/platform/www/lib/images/toolbar/strike.png
new file mode 100644
index 0000000..5adbba4
--- /dev/null
+++ b/platform/www/lib/images/toolbar/strike.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/ul.png b/platform/www/lib/images/toolbar/ul.png
new file mode 100644
index 0000000..39e5d34
--- /dev/null
+++ b/platform/www/lib/images/toolbar/ul.png
Binary files differ
diff --git a/platform/www/lib/images/toolbar/underline.png b/platform/www/lib/images/toolbar/underline.png
new file mode 100644
index 0000000..57bf3e2
--- /dev/null
+++ b/platform/www/lib/images/toolbar/underline.png
Binary files differ
diff --git a/platform/www/lib/images/trash.png b/platform/www/lib/images/trash.png
new file mode 100644
index 0000000..350c5e1
--- /dev/null
+++ b/platform/www/lib/images/trash.png
Binary files differ
diff --git a/platform/www/lib/images/unc.png b/platform/www/lib/images/unc.png
new file mode 100644
index 0000000..145b728
--- /dev/null
+++ b/platform/www/lib/images/unc.png
Binary files differ
diff --git a/platform/www/lib/images/up.png b/platform/www/lib/images/up.png
new file mode 100644
index 0000000..dbacf3f
--- /dev/null
+++ b/platform/www/lib/images/up.png
Binary files differ
diff --git a/platform/www/lib/images/wrap.gif b/platform/www/lib/images/wrap.gif
new file mode 100644
index 0000000..f2253e4
--- /dev/null
+++ b/platform/www/lib/images/wrap.gif
Binary files differ
diff --git a/platform/www/lib/index.html b/platform/www/lib/index.html
new file mode 100644
index 0000000..885c954
--- /dev/null
+++ b/platform/www/lib/index.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="refresh" content="0; URL=../" />
+<meta name="robots" content="noindex" />
+<title>nothing here...</title>
+</head>
+<body>
+<!-- this is just here to prevent directory browsing -->
+</body>
+</html>
diff --git a/platform/www/lib/plugins/acl/action.php b/platform/www/lib/plugins/acl/action.php
new file mode 100644
index 0000000..86e5870
--- /dev/null
+++ b/platform/www/lib/plugins/acl/action.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * AJAX call handler for ACL plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+/**
+ * Register handler
+ */
+class action_plugin_acl extends DokuWiki_Action_Plugin
+{
+
+ /**
+ * Registers a callback function for a given event
+ *
+ * @param Doku_Event_Handler $controller DokuWiki's event controller object
+ * @return void
+ */
+ public function register(Doku_Event_Handler $controller)
+ {
+
+ $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjaxCallAcl');
+ }
+
+ /**
+ * AJAX call handler for ACL plugin
+ *
+ * @param Doku_Event $event event object by reference
+ * @param mixed $param empty
+ * @return void
+ */
+ public function handleAjaxCallAcl(Doku_Event $event, $param)
+ {
+ if ($event->data !== 'plugin_acl') {
+ return;
+ }
+ $event->stopPropagation();
+ $event->preventDefault();
+
+ global $ID;
+ global $INPUT;
+
+ /** @var $acl admin_plugin_acl */
+ $acl = plugin_load('admin', 'acl');
+ if (!$acl->isAccessibleByCurrentUser()) {
+ echo 'for admins only';
+ return;
+ }
+ if (!checkSecurityToken()) {
+ echo 'CRSF Attack';
+ return;
+ }
+
+ $ID = getID();
+ $acl->handle();
+
+ $ajax = $INPUT->str('ajax');
+ header('Content-Type: text/html; charset=utf-8');
+
+ if ($ajax == 'info') {
+ $acl->printInfo();
+ } elseif ($ajax == 'tree') {
+ $ns = $INPUT->str('ns');
+ if ($ns == '*') {
+ $ns = '';
+ }
+ $ns = cleanID($ns);
+ $lvl = count(explode(':', $ns));
+ $ns = utf8_encodeFN(str_replace(':', '/', $ns));
+
+ $data = $acl->makeTree($ns, $ns);
+
+ foreach (array_keys($data) as $item) {
+ $data[$item]['level'] = $lvl + 1;
+ }
+ echo html_buildlist(
+ $data,
+ 'acl',
+ array($acl, 'makeTreeItem'),
+ array($acl, 'makeListItem')
+ );
+ }
+ }
+}
diff --git a/platform/www/lib/plugins/acl/admin.php b/platform/www/lib/plugins/acl/admin.php
new file mode 100644
index 0000000..02842fd
--- /dev/null
+++ b/platform/www/lib/plugins/acl/admin.php
@@ -0,0 +1,858 @@
+<?php
+/**
+ * ACL administration functions
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Anika Henke <anika@selfthinker.org> (concepts)
+ * @author Frank Schubert <frank@schokilade.de> (old version)
+ */
+
+/**
+ * All DokuWiki plugins to extend the admin function
+ * need to inherit from this class
+ */
+class admin_plugin_acl extends DokuWiki_Admin_Plugin
+{
+ public $acl = null;
+ protected $ns = null;
+ /**
+ * The currently selected item, associative array with id and type.
+ * Populated from (in this order):
+ * $_REQUEST['current_ns']
+ * $_REQUEST['current_id']
+ * $ns
+ * $ID
+ */
+ protected $current_item = null;
+ protected $who = '';
+ protected $usersgroups = array();
+ protected $specials = array();
+
+ /**
+ * return prompt for admin menu
+ */
+ public function getMenuText($language)
+ {
+ return $this->getLang('admin_acl');
+ }
+
+ /**
+ * return sort order for position in admin menu
+ */
+ public function getMenuSort()
+ {
+ return 1;
+ }
+
+ /**
+ * handle user request
+ *
+ * Initializes internal vars and handles modifications
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ public function handle()
+ {
+ global $AUTH_ACL;
+ global $ID;
+ global $auth;
+ global $config_cascade;
+ global $INPUT;
+
+ // fresh 1:1 copy without replacements
+ $AUTH_ACL = file($config_cascade['acl']['default']);
+
+ // namespace given?
+ if ($INPUT->str('ns') == '*') {
+ $this->ns = '*';
+ } else {
+ $this->ns = cleanID($INPUT->str('ns'));
+ }
+
+ if ($INPUT->str('current_ns')) {
+ $this->current_item = array('id' => cleanID($INPUT->str('current_ns')), 'type' => 'd');
+ } elseif ($INPUT->str('current_id')) {
+ $this->current_item = array('id' => cleanID($INPUT->str('current_id')), 'type' => 'f');
+ } elseif ($this->ns) {
+ $this->current_item = array('id' => $this->ns, 'type' => 'd');
+ } else {
+ $this->current_item = array('id' => $ID, 'type' => 'f');
+ }
+
+ // user or group choosen?
+ $who = trim($INPUT->str('acl_w'));
+ if ($INPUT->str('acl_t') == '__g__' && $who) {
+ $this->who = '@'.ltrim($auth->cleanGroup($who), '@');
+ } elseif ($INPUT->str('acl_t') == '__u__' && $who) {
+ $this->who = ltrim($who, '@');
+ if ($this->who != '%USER%' && $this->who != '%GROUP%') { #keep wildcard as is
+ $this->who = $auth->cleanUser($this->who);
+ }
+ } elseif ($INPUT->str('acl_t') &&
+ $INPUT->str('acl_t') != '__u__' &&
+ $INPUT->str('acl_t') != '__g__') {
+ $this->who = $INPUT->str('acl_t');
+ } elseif ($who) {
+ $this->who = $who;
+ }
+
+ // handle modifications
+ if ($INPUT->has('cmd') && checkSecurityToken()) {
+ $cmd = $INPUT->extract('cmd')->str('cmd');
+
+ // scope for modifications
+ if ($this->ns) {
+ if ($this->ns == '*') {
+ $scope = '*';
+ } else {
+ $scope = $this->ns.':*';
+ }
+ } else {
+ $scope = $ID;
+ }
+
+ if ($cmd == 'save' && $scope && $this->who && $INPUT->has('acl')) {
+ // handle additions or single modifications
+ $this->deleteACL($scope, $this->who);
+ $this->addOrUpdateACL($scope, $this->who, $INPUT->int('acl'));
+ } elseif ($cmd == 'del' && $scope && $this->who) {
+ // handle single deletions
+ $this->deleteACL($scope, $this->who);
+ } elseif ($cmd == 'update') {
+ $acl = $INPUT->arr('acl');
+
+ // handle update of the whole file
+ foreach ($INPUT->arr('del') as $where => $names) {
+ // remove all rules marked for deletion
+ foreach ($names as $who)
+ unset($acl[$where][$who]);
+ }
+ // prepare lines
+ $lines = array();
+ // keep header
+ foreach ($AUTH_ACL as $line) {
+ if ($line[0] == '#') {
+ $lines[] = $line;
+ } else {
+ break;
+ }
+ }
+ // re-add all rules
+ foreach ($acl as $where => $opt) {
+ foreach ($opt as $who => $perm) {
+ if ($who[0]=='@') {
+ if ($who!='@ALL') {
+ $who = '@'.ltrim($auth->cleanGroup($who), '@');
+ }
+ } elseif ($who != '%USER%' && $who != '%GROUP%') { #keep wildcard as is
+ $who = $auth->cleanUser($who);
+ }
+ $who = auth_nameencode($who, true);
+ $lines[] = "$where\t$who\t$perm\n";
+ }
+ }
+ // save it
+ io_saveFile($config_cascade['acl']['default'], join('', $lines));
+ }
+
+ // reload ACL config
+ $AUTH_ACL = file($config_cascade['acl']['default']);
+ }
+
+ // initialize ACL array
+ $this->initAclConfig();
+ }
+
+ /**
+ * ACL Output function
+ *
+ * print a table with all significant permissions for the
+ * current id
+ *
+ * @author Frank Schubert <frank@schokilade.de>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ public function html()
+ {
+ echo '<div id="acl_manager">'.NL;
+ echo '<h1>'.$this->getLang('admin_acl').'</h1>'.NL;
+ echo '<div class="level1">'.NL;
+
+ echo '<div id="acl__tree">'.NL;
+ $this->makeExplorer();
+ echo '</div>'.NL;
+
+ echo '<div id="acl__detail">'.NL;
+ $this->printDetail();
+ echo '</div>'.NL;
+ echo '</div>'.NL;
+
+ echo '<div class="clearer"></div>';
+ echo '<h2>'.$this->getLang('current').'</h2>'.NL;
+ echo '<div class="level2">'.NL;
+ $this->printAclTable();
+ echo '</div>'.NL;
+
+ echo '<div class="footnotes"><div class="fn">'.NL;
+ echo '<sup><a id="fn__1" class="fn_bot" href="#fnt__1">1)</a></sup>'.NL;
+ echo '<div class="content">'.$this->getLang('p_include').'</div>';
+ echo '</div></div>';
+
+ echo '</div>'.NL;
+ }
+
+ /**
+ * returns array with set options for building links
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function getLinkOptions($addopts = null)
+ {
+ $opts = array(
+ 'do'=>'admin',
+ 'page'=>'acl',
+ );
+ if ($this->ns) $opts['ns'] = $this->ns;
+ if ($this->who) $opts['acl_w'] = $this->who;
+
+ if (is_null($addopts)) return $opts;
+ return array_merge($opts, $addopts);
+ }
+
+ /**
+ * Display a tree menu to select a page or namespace
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function makeExplorer()
+ {
+ global $conf;
+ global $ID;
+ global $lang;
+
+ $ns = $this->ns;
+ if (empty($ns)) {
+ $ns = dirname(str_replace(':', '/', $ID));
+ if ($ns == '.') $ns ='';
+ } elseif ($ns == '*') {
+ $ns ='';
+ }
+ $ns = utf8_encodeFN(str_replace(':', '/', $ns));
+
+ $data = $this->makeTree($ns);
+
+ // wrap a list with the root level around the other namespaces
+ array_unshift($data, array( 'level' => 0, 'id' => '*', 'type' => 'd',
+ 'open' =>'true', 'label' => '['.$lang['mediaroot'].']'));
+
+ echo html_buildlist(
+ $data,
+ 'acl',
+ array($this, 'makeTreeItem'),
+ array($this, 'makeListItem')
+ );
+ }
+
+ /**
+ * get a combined list of media and page files
+ *
+ * also called via AJAX
+ *
+ * @param string $folder an already converted filesystem folder of the current namespace
+ * @param string $limit limit the search to this folder
+ * @return array
+ */
+ public function makeTree($folder, $limit = '')
+ {
+ global $conf;
+
+ // read tree structure from pages and media
+ $data = array();
+ search($data, $conf['datadir'], 'search_index', array('ns' => $folder), $limit);
+ $media = array();
+ search($media, $conf['mediadir'], 'search_index', array('ns' => $folder, 'nofiles' => true), $limit);
+ $data = array_merge($data, $media);
+ unset($media);
+
+ // combine by sorting and removing duplicates
+ usort($data, array($this, 'treeSort'));
+ $count = count($data);
+ if ($count>0) for ($i=1; $i<$count; $i++) {
+ if ($data[$i-1]['id'] == $data[$i]['id'] && $data[$i-1]['type'] == $data[$i]['type']) {
+ unset($data[$i]);
+ $i++; // duplicate found, next $i can't be a duplicate, so skip forward one
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * usort callback
+ *
+ * Sorts the combined trees of media and page files
+ */
+ public function treeSort($a, $b)
+ {
+ // handle the trivial cases first
+ if ($a['id'] == '') return -1;
+ if ($b['id'] == '') return 1;
+ // split up the id into parts
+ $a_ids = explode(':', $a['id']);
+ $b_ids = explode(':', $b['id']);
+ // now loop through the parts
+ while (count($a_ids) && count($b_ids)) {
+ // compare each level from upper to lower
+ // until a non-equal component is found
+ $cur_result = strcmp(array_shift($a_ids), array_shift($b_ids));
+ if ($cur_result) {
+ // if one of the components is the last component and is a file
+ // and the other one is either of a deeper level or a directory,
+ // the file has to come after the deeper level or directory
+ if (empty($a_ids) && $a['type'] == 'f' && (count($b_ids) || $b['type'] == 'd')) return 1;
+ if (empty($b_ids) && $b['type'] == 'f' && (count($a_ids) || $a['type'] == 'd')) return -1;
+ return $cur_result;
+ }
+ }
+ // The two ids seem to be equal. One of them might however refer
+ // to a page, one to a namespace, the namespace needs to be first.
+ if (empty($a_ids) && empty($b_ids)) {
+ if ($a['type'] == $b['type']) return 0;
+ if ($a['type'] == 'f') return 1;
+ return -1;
+ }
+ // Now the empty part is either a page in the parent namespace
+ // that obviously needs to be after the namespace
+ // Or it is the namespace that contains the other part and should be
+ // before that other part.
+ if (empty($a_ids)) return ($a['type'] == 'd') ? -1 : 1;
+ if (empty($b_ids)) return ($b['type'] == 'd') ? 1 : -1;
+ return 0; //shouldn't happen
+ }
+
+ /**
+ * Display the current ACL for selected where/who combination with
+ * selectors and modification form
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function printDetail()
+ {
+ global $ID;
+
+ echo '<form action="'.wl().'" method="post" accept-charset="utf-8"><div class="no">'.NL;
+
+ echo '<div id="acl__user">';
+ echo $this->getLang('acl_perms').' ';
+ $inl = $this->makeSelect();
+ echo '<input type="text" name="acl_w" class="edit" value="'.(($inl)?'':hsc(ltrim($this->who, '@'))).'" />'.NL;
+ echo '<button type="submit">'.$this->getLang('btn_select').'</button>'.NL;
+ echo '</div>'.NL;
+
+ echo '<div id="acl__info">';
+ $this->printInfo();
+ echo '</div>';
+
+ echo '<input type="hidden" name="ns" value="'.hsc($this->ns).'" />'.NL;
+ echo '<input type="hidden" name="id" value="'.hsc($ID).'" />'.NL;
+ echo '<input type="hidden" name="do" value="admin" />'.NL;
+ echo '<input type="hidden" name="page" value="acl" />'.NL;
+ echo '<input type="hidden" name="sectok" value="'.getSecurityToken().'" />'.NL;
+ echo '</div></form>'.NL;
+ }
+
+ /**
+ * Print info and editor
+ *
+ * also loaded via Ajax
+ */
+ public function printInfo()
+ {
+ global $ID;
+
+ if ($this->who) {
+ $current = $this->getExactPermisson();
+
+ // explain current permissions
+ $this->printExplanation($current);
+ // load editor
+ $this->printAclEditor($current);
+ } else {
+ echo '<p>';
+ if ($this->ns) {
+ printf($this->getLang('p_choose_ns'), hsc($this->ns));
+ } else {
+ printf($this->getLang('p_choose_id'), hsc($ID));
+ }
+ echo '</p>';
+
+ echo $this->locale_xhtml('help');
+ }
+ }
+
+ /**
+ * Display the ACL editor
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function printAclEditor($current)
+ {
+ global $lang;
+
+ echo '<fieldset>';
+ if (is_null($current)) {
+ echo '<legend>'.$this->getLang('acl_new').'</legend>';
+ } else {
+ echo '<legend>'.$this->getLang('acl_mod').'</legend>';
+ }
+
+ echo $this->makeCheckboxes($current, empty($this->ns), 'acl');
+
+ if (is_null($current)) {
+ echo '<button type="submit" name="cmd[save]">'.$lang['btn_save'].'</button>'.NL;
+ } else {
+ echo '<button type="submit" name="cmd[save]">'.$lang['btn_update'].'</button>'.NL;
+ echo '<button type="submit" name="cmd[del]">'.$lang['btn_delete'].'</button>'.NL;
+ }
+
+ echo '</fieldset>';
+ }
+
+ /**
+ * Explain the currently set permissions in plain english/$lang
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function printExplanation($current)
+ {
+ global $ID;
+ global $auth;
+
+ $who = $this->who;
+ $ns = $this->ns;
+
+ // prepare where to check
+ if ($ns) {
+ if ($ns == '*') {
+ $check='*';
+ } else {
+ $check=$ns.':*';
+ }
+ } else {
+ $check = $ID;
+ }
+
+ // prepare who to check
+ if ($who[0] == '@') {
+ $user = '';
+ $groups = array(ltrim($who, '@'));
+ } else {
+ $user = $who;
+ $info = $auth->getUserData($user);
+ if ($info === false) {
+ $groups = array();
+ } else {
+ $groups = $info['grps'];
+ }
+ }
+
+ // check the permissions
+ $perm = auth_aclcheck($check, $user, $groups);
+
+ // build array of named permissions
+ $names = array();
+ if ($perm) {
+ if ($ns) {
+ if ($perm >= AUTH_DELETE) $names[] = $this->getLang('acl_perm16');
+ if ($perm >= AUTH_UPLOAD) $names[] = $this->getLang('acl_perm8');
+ if ($perm >= AUTH_CREATE) $names[] = $this->getLang('acl_perm4');
+ }
+ if ($perm >= AUTH_EDIT) $names[] = $this->getLang('acl_perm2');
+ if ($perm >= AUTH_READ) $names[] = $this->getLang('acl_perm1');
+ $names = array_reverse($names);
+ } else {
+ $names[] = $this->getLang('acl_perm0');
+ }
+
+ // print permission explanation
+ echo '<p>';
+ if ($user) {
+ if ($ns) {
+ printf($this->getLang('p_user_ns'), hsc($who), hsc($ns), join(', ', $names));
+ } else {
+ printf($this->getLang('p_user_id'), hsc($who), hsc($ID), join(', ', $names));
+ }
+ } else {
+ if ($ns) {
+ printf($this->getLang('p_group_ns'), hsc(ltrim($who, '@')), hsc($ns), join(', ', $names));
+ } else {
+ printf($this->getLang('p_group_id'), hsc(ltrim($who, '@')), hsc($ID), join(', ', $names));
+ }
+ }
+ echo '</p>';
+
+ // add note if admin
+ if ($perm == AUTH_ADMIN) {
+ echo '<p>'.$this->getLang('p_isadmin').'</p>';
+ } elseif (is_null($current)) {
+ echo '<p>'.$this->getLang('p_inherited').'</p>';
+ }
+ }
+
+
+ /**
+ * Item formatter for the tree view
+ *
+ * User function for html_buildlist()
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ public function makeTreeItem($item)
+ {
+ $ret = '';
+ // what to display
+ if (!empty($item['label'])) {
+ $base = $item['label'];
+ } else {
+ $base = ':'.$item['id'];
+ $base = substr($base, strrpos($base, ':')+1);
+ }
+
+ // highlight?
+ if (($item['type']== $this->current_item['type'] && $item['id'] == $this->current_item['id'])) {
+ $cl = ' cur';
+ } else {
+ $cl = '';
+ }
+
+ // namespace or page?
+ if ($item['type']=='d') {
+ if ($item['open']) {
+ $img = DOKU_BASE.'lib/images/minus.gif';
+ $alt = '−';
+ } else {
+ $img = DOKU_BASE.'lib/images/plus.gif';
+ $alt = '+';
+ }
+ $ret .= '<img src="'.$img.'" alt="'.$alt.'" />';
+ $ret .= '<a href="'.
+ wl('', $this->getLinkOptions(array('ns'=> $item['id'], 'sectok'=>getSecurityToken()))).
+ '" class="idx_dir'.$cl.'">';
+ $ret .= $base;
+ $ret .= '</a>';
+ } else {
+ $ret .= '<a href="'.
+ wl('', $this->getLinkOptions(array('id'=> $item['id'], 'ns'=>'', 'sectok'=>getSecurityToken()))).
+ '" class="wikilink1'.$cl.'">';
+ $ret .= noNS($item['id']);
+ $ret .= '</a>';
+ }
+ return $ret;
+ }
+
+ /**
+ * List Item formatter
+ *
+ * @param array $item
+ * @return string
+ */
+ public function makeListItem($item)
+ {
+ return '<li class="level' . $item['level'] . ' ' .
+ ($item['open'] ? 'open' : 'closed') . '">';
+ }
+
+
+ /**
+ * Get current ACL settings as multidim array
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ public function initAclConfig()
+ {
+ global $AUTH_ACL;
+ global $conf;
+ $acl_config=array();
+ $usersgroups = array();
+
+ // get special users and groups
+ $this->specials[] = '@ALL';
+ $this->specials[] = '@'.$conf['defaultgroup'];
+ if ($conf['manager'] != '!!not set!!') {
+ $this->specials = array_merge(
+ $this->specials,
+ array_map(
+ 'trim',
+ explode(',', $conf['manager'])
+ )
+ );
+ }
+ $this->specials = array_filter($this->specials);
+ $this->specials = array_unique($this->specials);
+ sort($this->specials);
+
+ foreach ($AUTH_ACL as $line) {
+ $line = trim(preg_replace('/#.*$/', '', $line)); //ignore comments
+ if (!$line) continue;
+
+ $acl = preg_split('/[ \t]+/', $line);
+ //0 is pagename, 1 is user, 2 is acl
+
+ $acl[1] = rawurldecode($acl[1]);
+ $acl_config[$acl[0]][$acl[1]] = $acl[2];
+
+ // store non-special users and groups for later selection dialog
+ $ug = $acl[1];
+ if (in_array($ug, $this->specials)) continue;
+ $usersgroups[] = $ug;
+ }
+
+ $usersgroups = array_unique($usersgroups);
+ sort($usersgroups);
+ ksort($acl_config);
+
+ $this->acl = $acl_config;
+ $this->usersgroups = $usersgroups;
+ }
+
+ /**
+ * Display all currently set permissions in a table
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function printAclTable()
+ {
+ global $lang;
+ global $ID;
+
+ echo '<form action="'.wl().'" method="post" accept-charset="utf-8"><div class="no">'.NL;
+ if ($this->ns) {
+ echo '<input type="hidden" name="ns" value="'.hsc($this->ns).'" />'.NL;
+ } else {
+ echo '<input type="hidden" name="id" value="'.hsc($ID).'" />'.NL;
+ }
+ echo '<input type="hidden" name="acl_w" value="'.hsc($this->who).'" />'.NL;
+ echo '<input type="hidden" name="do" value="admin" />'.NL;
+ echo '<input type="hidden" name="page" value="acl" />'.NL;
+ echo '<input type="hidden" name="sectok" value="'.getSecurityToken().'" />'.NL;
+ echo '<div class="table">';
+ echo '<table class="inline">';
+ echo '<tr>';
+ echo '<th>'.$this->getLang('where').'</th>';
+ echo '<th>'.$this->getLang('who').'</th>';
+ echo '<th>'.$this->getLang('perm').'<sup><a id="fnt__1" class="fn_top" href="#fn__1">1)</a></sup></th>';
+ echo '<th>'.$lang['btn_delete'].'</th>';
+ echo '</tr>';
+ foreach ($this->acl as $where => $set) {
+ foreach ($set as $who => $perm) {
+ echo '<tr>';
+ echo '<td>';
+ if (substr($where, -1) == '*') {
+ echo '<span class="aclns">'.hsc($where).'</span>';
+ $ispage = false;
+ } else {
+ echo '<span class="aclpage">'.hsc($where).'</span>';
+ $ispage = true;
+ }
+ echo '</td>';
+
+ echo '<td>';
+ if ($who[0] == '@') {
+ echo '<span class="aclgroup">'.hsc($who).'</span>';
+ } else {
+ echo '<span class="acluser">'.hsc($who).'</span>';
+ }
+ echo '</td>';
+
+ echo '<td>';
+ echo $this->makeCheckboxes($perm, $ispage, 'acl['.$where.']['.$who.']');
+ echo '</td>';
+
+ echo '<td class="check">';
+ echo '<input type="checkbox" name="del['.hsc($where).'][]" value="'.hsc($who).'" />';
+ echo '</td>';
+ echo '</tr>';
+ }
+ }
+
+ echo '<tr>';
+ echo '<th class="action" colspan="4">';
+ echo '<button type="submit" name="cmd[update]">'.$lang['btn_update'].'</button>';
+ echo '</th>';
+ echo '</tr>';
+ echo '</table>';
+ echo '</div>';
+ echo '</div></form>'.NL;
+ }
+
+ /**
+ * Returns the permission which were set for exactly the given user/group
+ * and page/namespace. Returns null if no exact match is available
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function getExactPermisson()
+ {
+ global $ID;
+ if ($this->ns) {
+ if ($this->ns == '*') {
+ $check = '*';
+ } else {
+ $check = $this->ns.':*';
+ }
+ } else {
+ $check = $ID;
+ }
+
+ if (isset($this->acl[$check][$this->who])) {
+ return $this->acl[$check][$this->who];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * adds new acl-entry to conf/acl.auth.php
+ *
+ * @author Frank Schubert <frank@schokilade.de>
+ */
+ public function addOrUpdateACL($acl_scope, $acl_user, $acl_level)
+ {
+ global $config_cascade;
+
+ // first make sure we won't end up with 2 lines matching this user and scope. See issue #1115
+ $this->deleteACL($acl_scope, $acl_user);
+ $acl_user = auth_nameencode($acl_user, true);
+
+ // max level for pagenames is edit
+ if (strpos($acl_scope, '*') === false) {
+ if ($acl_level > AUTH_EDIT) $acl_level = AUTH_EDIT;
+ }
+
+ $new_acl = "$acl_scope\t$acl_user\t$acl_level\n";
+
+ return io_saveFile($config_cascade['acl']['default'], $new_acl, true);
+ }
+
+ /**
+ * remove acl-entry from conf/acl.auth.php
+ *
+ * @author Frank Schubert <frank@schokilade.de>
+ */
+ public function deleteACL($acl_scope, $acl_user)
+ {
+ global $config_cascade;
+ $acl_user = auth_nameencode($acl_user, true);
+
+ $acl_pattern = '^'.preg_quote($acl_scope, '/').'[ \t]+'.$acl_user.'[ \t]+[0-8].*$';
+
+ return io_deleteFromFile($config_cascade['acl']['default'], "/$acl_pattern/", true);
+ }
+
+ /**
+ * print the permission radio boxes
+ *
+ * @author Frank Schubert <frank@schokilade.de>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function makeCheckboxes($setperm, $ispage, $name)
+ {
+ global $lang;
+
+ static $label = 0; //number labels
+ $ret = '';
+
+ if ($ispage && $setperm > AUTH_EDIT) $setperm = AUTH_EDIT;
+
+ foreach (array(AUTH_NONE,AUTH_READ,AUTH_EDIT,AUTH_CREATE,AUTH_UPLOAD,AUTH_DELETE) as $perm) {
+ $label += 1;
+
+ //general checkbox attributes
+ $atts = array( 'type' => 'radio',
+ 'id' => 'pbox'.$label,
+ 'name' => $name,
+ 'value' => $perm );
+ //dynamic attributes
+ if (!is_null($setperm) && $setperm == $perm) $atts['checked'] = 'checked';
+ if ($ispage && $perm > AUTH_EDIT) {
+ $atts['disabled'] = 'disabled';
+ $class = ' class="disabled"';
+ } else {
+ $class = '';
+ }
+
+ //build code
+ $ret .= '<label for="pbox'.$label.'"'.$class.'>';
+ $ret .= '<input '.buildAttributes($atts).' />&#160;';
+ $ret .= $this->getLang('acl_perm'.$perm);
+ $ret .= '</label>'.NL;
+ }
+ return $ret;
+ }
+
+ /**
+ * Print a user/group selector (reusing already used users and groups)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function makeSelect()
+ {
+ $inlist = false;
+ $usel = '';
+ $gsel = '';
+
+ if ($this->who &&
+ !in_array($this->who, $this->usersgroups) &&
+ !in_array($this->who, $this->specials)) {
+ if ($this->who[0] == '@') {
+ $gsel = ' selected="selected"';
+ } else {
+ $usel = ' selected="selected"';
+ }
+ } else {
+ $inlist = true;
+ }
+
+ echo '<select name="acl_t" class="edit">'.NL;
+ echo ' <option value="__g__" class="aclgroup"'.$gsel.'>'.$this->getLang('acl_group').'</option>'.NL;
+ echo ' <option value="__u__" class="acluser"'.$usel.'>'.$this->getLang('acl_user').'</option>'.NL;
+ if (!empty($this->specials)) {
+ echo ' <optgroup label="&#160;">'.NL;
+ foreach ($this->specials as $ug) {
+ if ($ug == $this->who) {
+ $sel = ' selected="selected"';
+ $inlist = true;
+ } else {
+ $sel = '';
+ }
+
+ if ($ug[0] == '@') {
+ echo ' <option value="'.hsc($ug).'" class="aclgroup"'.$sel.'>'.hsc($ug).'</option>'.NL;
+ } else {
+ echo ' <option value="'.hsc($ug).'" class="acluser"'.$sel.'>'.hsc($ug).'</option>'.NL;
+ }
+ }
+ echo ' </optgroup>'.NL;
+ }
+ if (!empty($this->usersgroups)) {
+ echo ' <optgroup label="&#160;">'.NL;
+ foreach ($this->usersgroups as $ug) {
+ if ($ug == $this->who) {
+ $sel = ' selected="selected"';
+ $inlist = true;
+ } else {
+ $sel = '';
+ }
+
+ if ($ug[0] == '@') {
+ echo ' <option value="'.hsc($ug).'" class="aclgroup"'.$sel.'>'.hsc($ug).'</option>'.NL;
+ } else {
+ echo ' <option value="'.hsc($ug).'" class="acluser"'.$sel.'>'.hsc($ug).'</option>'.NL;
+ }
+ }
+ echo ' </optgroup>'.NL;
+ }
+ echo '</select>'.NL;
+ return $inlist;
+ }
+}
diff --git a/platform/www/lib/plugins/acl/admin.svg b/platform/www/lib/plugins/acl/admin.svg
new file mode 100644
index 0000000..b5cf001
--- /dev/null
+++ b/platform/www/lib/plugins/acl/admin.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M22 18v4h-4v-3h-3v-3h-3l-2.26-2.26c-.55.17-1.13.26-1.74.26a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6c0 .61-.09 1.19-.26 1.74L22 18M7 5a2 2 0 0 0-2 2 2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/plugins/acl/lang/en/help.txt b/platform/www/lib/plugins/acl/lang/en/help.txt
new file mode 100644
index 0000000..e865bbb
--- /dev/null
+++ b/platform/www/lib/plugins/acl/lang/en/help.txt
@@ -0,0 +1,9 @@
+=== Quick Help: ===
+
+On this page you can add and remove permissions for namespaces and pages in your wiki.
+ * The left pane displays all available namespaces and pages.
+ * The form above allows you to see and modify the permissions of a selected user or group.
+ * In the table below all currently set access control rules are shown. You can use it to quickly delete or change multiple rules.
+
+Reading the [[doku>acl|official documentation on ACL]] might help you to fully understand how access control works in DokuWiki.
+
diff --git a/platform/www/lib/plugins/acl/lang/en/lang.php b/platform/www/lib/plugins/acl/lang/en/lang.php
new file mode 100644
index 0000000..0c86489
--- /dev/null
+++ b/platform/www/lib/plugins/acl/lang/en/lang.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * english language file
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Anika Henke <anika@selfthinker.org>
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+
+$lang['admin_acl'] = 'Access Control List Management';
+$lang['acl_group'] = 'Group:';
+$lang['acl_user'] = 'User:';
+$lang['acl_perms'] = 'Permissions for';
+$lang['page'] = 'Page';
+$lang['namespace'] = 'Namespace';
+
+$lang['btn_select'] = 'Select';
+
+$lang['p_user_id'] = 'User <b class="acluser">%s</b> currently has the following permissions on page <b class="aclpage">%s</b>: <i>%s</i>.';
+$lang['p_user_ns'] = 'User <b class="acluser">%s</b> currently has the following permissions in namespace <b class="aclns">%s</b>: <i>%s</i>.';
+$lang['p_group_id'] = 'Members of group <b class="aclgroup">%s</b> currently have the following permissions on page <b class="aclpage">%s</b>: <i>%s</i>.';
+$lang['p_group_ns'] = 'Members of group <b class="aclgroup">%s</b> currently have the following permissions in namespace <b class="aclns">%s</b>: <i>%s</i>.';
+
+$lang['p_choose_id'] = 'Please <b>enter a user or group</b> in the form above to view or edit the permissions set for the page <b class="aclpage">%s</b>.';
+$lang['p_choose_ns'] = 'Please <b>enter a user or group</b> in the form above to view or edit the permissions set for the namespace <b class="aclns">%s</b>.';
+
+
+$lang['p_inherited'] = 'Note: Those permissions were not set explicitly but were inherited from other groups or higher namespaces.';
+$lang['p_isadmin'] = 'Note: The selected group or user has always full permissions because it is configured as superuser.';
+$lang['p_include'] = 'Higher permissions include lower ones. Create, Upload and Delete permissions only apply to namespaces, not pages.';
+
+$lang['current'] = 'Current ACL Rules';
+$lang['where'] = 'Page/Namespace';
+$lang['who'] = 'User/Group';
+$lang['perm'] = 'Permissions';
+
+$lang['acl_perm0'] = 'None';
+$lang['acl_perm1'] = 'Read';
+$lang['acl_perm2'] = 'Edit';
+$lang['acl_perm4'] = 'Create';
+$lang['acl_perm8'] = 'Upload';
+$lang['acl_perm16'] = 'Delete';
+$lang['acl_new'] = 'Add new Entry';
+$lang['acl_mod'] = 'Modify Entry';
+//Setup VIM: ex: et ts=2 :
diff --git a/platform/www/lib/plugins/acl/pix/group.png b/platform/www/lib/plugins/acl/pix/group.png
new file mode 100644
index 0000000..348d4e5
--- /dev/null
+++ b/platform/www/lib/plugins/acl/pix/group.png
Binary files differ
diff --git a/platform/www/lib/plugins/acl/pix/ns.png b/platform/www/lib/plugins/acl/pix/ns.png
new file mode 100644
index 0000000..77e03b1
--- /dev/null
+++ b/platform/www/lib/plugins/acl/pix/ns.png
Binary files differ
diff --git a/platform/www/lib/plugins/acl/pix/page.png b/platform/www/lib/plugins/acl/pix/page.png
new file mode 100644
index 0000000..b1b7ebe
--- /dev/null
+++ b/platform/www/lib/plugins/acl/pix/page.png
Binary files differ
diff --git a/platform/www/lib/plugins/acl/pix/user.png b/platform/www/lib/plugins/acl/pix/user.png
new file mode 100644
index 0000000..8d5d1c2
--- /dev/null
+++ b/platform/www/lib/plugins/acl/pix/user.png
Binary files differ
diff --git a/platform/www/lib/plugins/acl/plugin.info.txt b/platform/www/lib/plugins/acl/plugin.info.txt
new file mode 100644
index 0000000..1b2c82c
--- /dev/null
+++ b/platform/www/lib/plugins/acl/plugin.info.txt
@@ -0,0 +1,7 @@
+base acl
+author Andreas Gohr
+email andi@splitbrain.org
+date 2015-07-25
+name ACL Manager
+desc Manage Page Access Control Lists
+url http://dokuwiki.org/plugin:acl
diff --git a/platform/www/lib/plugins/acl/remote.php b/platform/www/lib/plugins/acl/remote.php
new file mode 100644
index 0000000..8d19add
--- /dev/null
+++ b/platform/www/lib/plugins/acl/remote.php
@@ -0,0 +1,102 @@
+<?php
+
+use dokuwiki\Remote\AccessDeniedException;
+
+/**
+ * Class remote_plugin_acl
+ */
+class remote_plugin_acl extends DokuWiki_Remote_Plugin
+{
+
+ /**
+ * Returns details about the remote plugin methods
+ *
+ * @return array Information about all provided methods. {@see dokuwiki\Remote\RemoteAPI}
+ */
+ public function _getMethods()
+ {
+ return array(
+ 'listAcls' => array(
+ 'args' => array(),
+ 'return' => 'Array of ACLs {scope, user, permission}',
+ 'name' => 'listAcls',
+ 'doc' => 'Get the list of all ACLs',
+ ),'addAcl' => array(
+ 'args' => array('string','string','int'),
+ 'return' => 'int',
+ 'name' => 'addAcl',
+ 'doc' => 'Adds a new ACL rule.'
+ ), 'delAcl' => array(
+ 'args' => array('string','string'),
+ 'return' => 'int',
+ 'name' => 'delAcl',
+ 'doc' => 'Delete an existing ACL rule.'
+ ),
+ );
+ }
+
+ /**
+ * List all ACL config entries
+ *
+ * @throws AccessDeniedException
+ * @return dictionary {Scope: ACL}, where ACL = dictionnary {user/group: permissions_int}
+ */
+ public function listAcls()
+ {
+ if (!auth_isadmin()) {
+ throw new AccessDeniedException(
+ 'You are not allowed to access ACLs, superuser permission is required',
+ 114
+ );
+ }
+ /** @var admin_plugin_acl $apa */
+ $apa = plugin_load('admin', 'acl');
+ $apa->initAclConfig();
+ return $apa->acl;
+ }
+
+ /**
+ * Add a new entry to ACL config
+ *
+ * @param string $scope
+ * @param string $user
+ * @param int $level see also inc/auth.php
+ * @throws AccessDeniedException
+ * @return bool
+ */
+ public function addAcl($scope, $user, $level)
+ {
+ if (!auth_isadmin()) {
+ throw new AccessDeniedException(
+ 'You are not allowed to access ACLs, superuser permission is required',
+ 114
+ );
+ }
+
+ /** @var admin_plugin_acl $apa */
+ $apa = plugin_load('admin', 'acl');
+ return $apa->addOrUpdateACL($scope, $user, $level);
+ }
+
+ /**
+ * Remove an entry from ACL config
+ *
+ * @param string $scope
+ * @param string $user
+ * @throws AccessDeniedException
+ * @return bool
+ */
+ public function delAcl($scope, $user)
+ {
+ if (!auth_isadmin()) {
+ throw new AccessDeniedException(
+ 'You are not allowed to access ACLs, superuser permission is required',
+ 114
+ );
+ }
+
+ /** @var admin_plugin_acl $apa */
+ $apa = plugin_load('admin', 'acl');
+ return $apa->deleteACL($scope, $user);
+ }
+}
diff --git a/platform/www/lib/plugins/acl/script.js b/platform/www/lib/plugins/acl/script.js
new file mode 100644
index 0000000..95621a2
--- /dev/null
+++ b/platform/www/lib/plugins/acl/script.js
@@ -0,0 +1,121 @@
+/**
+ * ACL Manager AJAX enhancements
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+var dw_acl = {
+ /**
+ * Initialize the object and attach the event handlers
+ */
+ init: function () {
+ var $tree;
+
+ //FIXME only one underscore!!
+ if (jQuery('#acl_manager').length === 0) {
+ return;
+ }
+
+ jQuery('#acl__user select').on('change', dw_acl.userselhandler);
+ jQuery('#acl__user button').on('click', dw_acl.loadinfo);
+
+ $tree = jQuery('#acl__tree');
+ $tree.dw_tree({toggle_selector: 'img',
+ load_data: function (show_sublist, $clicky) {
+ // get the enclosed link and the edit form
+ var $frm = jQuery('#acl__detail form');
+
+ jQuery.post(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ jQuery.extend(dw_acl.parseatt($clicky.parent().find('a')[0].search),
+ {call: 'plugin_acl',
+ ajax: 'tree',
+ current_ns: $frm.find('input[name=ns]').val(),
+ current_id: $frm.find('input[name=id]').val()}),
+ show_sublist,
+ 'html'
+ );
+ },
+
+ toggle_display: function ($clicky, opening) {
+ $clicky.attr('src',
+ DOKU_BASE + 'lib/images/' +
+ (opening ? 'minus' : 'plus') + '.gif');
+ }});
+ $tree.delegate('a', 'click', dw_acl.treehandler);
+ },
+
+ /**
+ * Handle user dropdown
+ *
+ * Hides or shows the user/group entry box depending on what was selected in the
+ * dropdown element
+ */
+ userselhandler: function () {
+ // make entry field visible/invisible
+ jQuery('#acl__user input').toggle(this.value === '__g__' ||
+ this.value === '__u__');
+ dw_acl.loadinfo();
+ },
+
+ /**
+ * Load the current permission info and edit form
+ */
+ loadinfo: function () {
+ jQuery('#acl__info')
+ .attr('role', 'alert')
+ .html('<img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="..." />')
+ .load(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ jQuery('#acl__detail form').serialize() + '&call=plugin_acl&ajax=info'
+ );
+ return false;
+ },
+
+ /**
+ * parse URL attributes into a associative array
+ *
+ * @todo put into global script lib?
+ */
+ parseatt: function (str) {
+ if (str[0] === '?') {
+ str = str.substr(1);
+ }
+ var attributes = {};
+ var all = str.split('&');
+ for (var i = 0; i < all.length; i++) {
+ var att = all[i].split('=');
+ attributes[att[0]] = decodeURIComponent(att[1]);
+ }
+ return attributes;
+ },
+
+ /**
+ * Handles clicks to the tree nodes
+ */
+ treehandler: function () {
+ var $link, $frm;
+
+ $link = jQuery(this);
+
+ // remove highlighting
+ jQuery('#acl__tree a.cur').removeClass('cur');
+
+ // add new highlighting
+ $link.addClass('cur');
+
+ // set new page to detail form
+ $frm = jQuery('#acl__detail form');
+ if ($link.hasClass('wikilink1')) {
+ $frm.find('input[name=ns]').val('');
+ $frm.find('input[name=id]').val(dw_acl.parseatt($link[0].search).id);
+ } 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;
+ }
+};
+
+jQuery(dw_acl.init);
diff --git a/platform/www/lib/plugins/acl/style.css b/platform/www/lib/plugins/acl/style.css
new file mode 100644
index 0000000..4233cd3
--- /dev/null
+++ b/platform/www/lib/plugins/acl/style.css
@@ -0,0 +1,135 @@
+#acl__tree {
+ font-size: 90%;
+ width: 25%;
+ height: 300px;
+ float: left;
+ overflow: auto;
+ border: 1px solid __border__;
+ text-align: left;
+}
+[dir=rtl] #acl__tree {
+ float: right;
+ text-align: right;
+}
+
+#acl__tree a.cur {
+ background-color: __highlight__;
+ font-weight: bold;
+}
+
+#acl__tree ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+#acl__tree li {
+ padding-left: 1em;
+ list-style-image: none;
+}
+[dir=rtl] #acl__tree li {
+ padding-left: 0em;
+ padding-right: 1em;
+}
+
+#acl__tree ul img {
+ margin-right: 0.25em;
+ cursor: pointer;
+}
+[dir=rtl] #acl__tree ul img {
+ margin-left: 0.25em;
+ margin-right: 0em;
+}
+
+#acl__detail {
+ width: 73%;
+ height: 300px;
+ float: right;
+ overflow: auto;
+}
+[dir=rtl] #acl__detail {
+ float: left;
+}
+
+#acl__detail fieldset {
+ width: 90%;
+}
+
+#acl__detail div#acl__user {
+ border: 1px solid __border__;
+ padding: 0.5em;
+ margin-bottom: 0.6em;
+}
+
+#acl_manager table.inline {
+ width: 100%;
+ margin: 0;
+}
+
+#acl_manager table .check {
+ text-align: center;
+}
+
+#acl_manager table .action {
+ text-align: right;
+}
+
+#acl_manager .aclgroup {
+ background: transparent url(pix/group.png) 0px 1px no-repeat;
+ padding: 1px 0px 1px 18px;
+}
+[dir=rtl] #acl_manager .aclgroup {
+ background: transparent url(pix/group.png) right 1px no-repeat;
+ padding: 1px 18px 1px 0px;
+}
+
+#acl_manager .acluser {
+ background: transparent url(pix/user.png) 0px 1px no-repeat;
+ padding: 1px 0px 1px 18px;
+}
+[dir=rtl] #acl_manager .acluser {
+ background: transparent url(pix/user.png) right 1px no-repeat;
+ padding: 1px 18px 1px 0px;
+}
+
+#acl_manager .aclpage {
+ background: transparent url(pix/page.png) 0px 1px no-repeat;
+ padding: 1px 0px 1px 18px;
+}
+[dir=rtl] #acl_manager .aclpage {
+ background: transparent url(pix/page.png) right 1px no-repeat;
+ padding: 1px 18px 1px 0px;
+}
+
+#acl_manager .aclns {
+ background: transparent url(pix/ns.png) 0px 1px no-repeat;
+ padding: 1px 0px 1px 18px;
+}
+[dir=rtl] #acl_manager .aclns {
+ background: transparent url(pix/ns.png) right 1px no-repeat;
+ padding: 1px 18px 1px 0px;
+}
+
+#acl_manager label.disabled {
+ opacity: .5;
+ cursor: auto;
+}
+
+#acl_manager label {
+ text-align: left;
+ font-weight: normal;
+ display: inline;
+}
+
+#acl_manager table {
+ margin-left: 10%;
+ width: 80%;
+}
+
+#acl_manager table tr {
+ background-color: inherit;
+}
+
+#acl_manager table tr:hover {
+ background-color: __background_alt__;
+}
diff --git a/platform/www/lib/plugins/action.php b/platform/www/lib/plugins/action.php
new file mode 100644
index 0000000..a3cbec7
--- /dev/null
+++ b/platform/www/lib/plugins/action.php
@@ -0,0 +1,2 @@
+<?php
+dbg_deprecated('Autoloading. Do not require() files yourself.');
diff --git a/platform/www/lib/plugins/admin.php b/platform/www/lib/plugins/admin.php
new file mode 100644
index 0000000..a3cbec7
--- /dev/null
+++ b/platform/www/lib/plugins/admin.php
@@ -0,0 +1,2 @@
+<?php
+dbg_deprecated('Autoloading. Do not require() files yourself.');
diff --git a/platform/www/lib/plugins/auth.php b/platform/www/lib/plugins/auth.php
new file mode 100644
index 0000000..a3cbec7
--- /dev/null
+++ b/platform/www/lib/plugins/auth.php
@@ -0,0 +1,2 @@
+<?php
+dbg_deprecated('Autoloading. Do not require() files yourself.');
diff --git a/platform/www/lib/plugins/authad/action.php b/platform/www/lib/plugins/authad/action.php
new file mode 100644
index 0000000..a9fc01c
--- /dev/null
+++ b/platform/www/lib/plugins/authad/action.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * DokuWiki Plugin addomain (Action Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+/**
+ * Class action_plugin_addomain
+ */
+class action_plugin_authad extends DokuWiki_Action_Plugin
+{
+
+ /**
+ * Registers a callback function for a given event
+ */
+ public function register(Doku_Event_Handler $controller)
+ {
+
+ $controller->register_hook('AUTH_LOGIN_CHECK', 'BEFORE', $this, 'handleAuthLoginCheck');
+ $controller->register_hook('HTML_LOGINFORM_OUTPUT', 'BEFORE', $this, 'handleHtmlLoginformOutput');
+ }
+
+ /**
+ * Adds the selected domain as user postfix when attempting a login
+ *
+ * @param Doku_Event $event
+ * @param array $param
+ */
+ public function handleAuthLoginCheck(Doku_Event $event, $param)
+ {
+ global $INPUT;
+
+ /** @var auth_plugin_authad $auth */
+ global $auth;
+ if (!is_a($auth, 'auth_plugin_authad')) return; // AD not even used
+
+ if ($INPUT->str('dom')) {
+ $usr = $auth->cleanUser($event->data['user']);
+ $dom = $auth->getUserDomain($usr);
+ if (!$dom) {
+ $usr = "$usr@".$INPUT->str('dom');
+ }
+ $INPUT->post->set('u', $usr);
+ $event->data['user'] = $usr;
+ }
+ }
+
+ /**
+ * Shows a domain selection in the login form when more than one domain is configured
+ *
+ * @param Doku_Event $event
+ * @param array $param
+ */
+ public function handleHtmlLoginformOutput(Doku_Event $event, $param)
+ {
+ global $INPUT;
+ /** @var auth_plugin_authad $auth */
+ global $auth;
+ if (!is_a($auth, 'auth_plugin_authad')) return; // AD not even used
+ $domains = $auth->getConfiguredDomains();
+ if (count($domains) <= 1) return; // no choice at all
+
+ /** @var Doku_Form $form */
+ $form =& $event->data;
+
+ // any default?
+ $dom = '';
+ if ($INPUT->has('u')) {
+ $usr = $auth->cleanUser($INPUT->str('u'));
+ $dom = $auth->getUserDomain($usr);
+
+ // update user field value
+ if ($dom) {
+ $usr = $auth->getUserName($usr);
+ $pos = $form->findElementByAttribute('name', 'u');
+ $ele =& $form->getElementAt($pos);
+ $ele['value'] = $usr;
+ }
+ }
+
+ // add select box
+ $element = form_makeListboxField('dom', $domains, $dom, $this->getLang('domain'), '', 'block');
+ $pos = $form->findElementByAttribute('name', 'p');
+ $form->insertElement($pos + 1, $element);
+ }
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/authad/adLDAP/adLDAP.php b/platform/www/lib/plugins/authad/adLDAP/adLDAP.php
new file mode 100644
index 0000000..c84a4f4
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/adLDAP.php
@@ -0,0 +1,949 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 169 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+ */
+
+/**
+* Main adLDAP class
+*
+* Can be initialised using $adldap = new adLDAP();
+*
+* Something to keep in mind is that Active Directory is a permissions
+* based directory. If you bind as a domain user, you can't fetch as
+* much information on other users as you could as a domain admin.
+*
+* Before asking questions, please read the Documentation at
+* http://adldap.sourceforge.net/wiki/doku.php?id=api
+*/
+require_once(dirname(__FILE__) . '/collections/adLDAPCollection.php');
+require_once(dirname(__FILE__) . '/classes/adLDAPGroups.php');
+require_once(dirname(__FILE__) . '/classes/adLDAPUsers.php');
+require_once(dirname(__FILE__) . '/classes/adLDAPFolders.php');
+require_once(dirname(__FILE__) . '/classes/adLDAPUtils.php');
+require_once(dirname(__FILE__) . '/classes/adLDAPContacts.php');
+require_once(dirname(__FILE__) . '/classes/adLDAPExchange.php');
+require_once(dirname(__FILE__) . '/classes/adLDAPComputers.php');
+
+class adLDAP {
+
+ /**
+ * Define the different types of account in AD
+ */
+ const ADLDAP_NORMAL_ACCOUNT = 805306368;
+ const ADLDAP_WORKSTATION_TRUST = 805306369;
+ const ADLDAP_INTERDOMAIN_TRUST = 805306370;
+ const ADLDAP_SECURITY_GLOBAL_GROUP = 268435456;
+ const ADLDAP_DISTRIBUTION_GROUP = 268435457;
+ const ADLDAP_SECURITY_LOCAL_GROUP = 536870912;
+ const ADLDAP_DISTRIBUTION_LOCAL_GROUP = 536870913;
+ const ADLDAP_FOLDER = 'OU';
+ const ADLDAP_CONTAINER = 'CN';
+
+ /**
+ * The default port for LDAP non-SSL connections
+ */
+ const ADLDAP_LDAP_PORT = '389';
+ /**
+ * The default port for LDAPS SSL connections
+ */
+ const ADLDAP_LDAPS_PORT = '636';
+
+ /**
+ * The account suffix for your domain, can be set when the class is invoked
+ *
+ * @var string
+ */
+ protected $accountSuffix = "@mydomain.local";
+
+ /**
+ * The base dn for your domain
+ *
+ * If this is set to null then adLDAP will attempt to obtain this automatically from the rootDSE
+ *
+ * @var string
+ */
+ protected $baseDn = "DC=mydomain,DC=local";
+
+ /**
+ * Port used to talk to the domain controllers.
+ *
+ * @var int
+ */
+ protected $adPort = self::ADLDAP_LDAP_PORT;
+
+ /**
+ * Array of domain controllers. Specifiy multiple controllers if you
+ * would like the class to balance the LDAP queries amongst multiple servers
+ *
+ * @var array
+ */
+ protected $domainControllers = array("dc01.mydomain.local");
+
+ /**
+ * Optional account with higher privileges for searching
+ * This should be set to a domain admin account
+ *
+ * @var string
+ * @var string
+ */
+ protected $adminUsername = NULL;
+ protected $adminPassword = NULL;
+
+ /**
+ * AD does not return the primary group. http://support.microsoft.com/?kbid=321360
+ * This tweak will resolve the real primary group.
+ * Setting to false will fudge "Domain Users" and is much faster. Keep in mind though that if
+ * someone's primary group is NOT domain users, this is obviously going to mess up the results
+ *
+ * @var bool
+ */
+ protected $realPrimaryGroup = true;
+
+ /**
+ * Use SSL (LDAPS), your server needs to be setup, please see
+ * http://adldap.sourceforge.net/wiki/doku.php?id=ldap_over_ssl
+ *
+ * @var bool
+ */
+ protected $useSSL = false;
+
+ /**
+ * Use TLS
+ * If you wish to use TLS you should ensure that $useSSL is set to false and vice-versa
+ *
+ * @var bool
+ */
+ protected $useTLS = false;
+
+ /**
+ * Use SSO
+ * To indicate to adLDAP to reuse password set by the brower through NTLM or Kerberos
+ *
+ * @var bool
+ */
+ protected $useSSO = false;
+
+ /**
+ * When querying group memberships, do it recursively
+ * eg. User Fred is a member of Group A, which is a member of Group B, which is a member of Group C
+ * user_ingroup("Fred","C") will returns true with this option turned on, false if turned off
+ *
+ * @var bool
+ */
+ protected $recursiveGroups = true;
+
+ // You should not need to edit anything below this line
+ //******************************************************************************************
+
+ /**
+ * Connection and bind default variables
+ *
+ * @var mixed
+ * @var mixed
+ */
+ protected $ldapConnection;
+ protected $ldapBind;
+
+ /**
+ * Get the active LDAP Connection
+ *
+ * @return resource
+ */
+ public function getLdapConnection() {
+ if ($this->ldapConnection) {
+ return $this->ldapConnection;
+ }
+ return false;
+ }
+
+ /**
+ * Get the bind status
+ *
+ * @return bool
+ */
+ public function getLdapBind() {
+ return $this->ldapBind;
+ }
+
+ /**
+ * Get the current base DN
+ *
+ * @return string
+ */
+ public function getBaseDn() {
+ return $this->baseDn;
+ }
+
+ /**
+ * The group class
+ *
+ * @var adLDAPGroups
+ */
+ protected $groupClass;
+
+ /**
+ * Get the group class interface
+ *
+ * @return adLDAPGroups
+ */
+ public function group() {
+ if (!$this->groupClass) {
+ $this->groupClass = new adLDAPGroups($this);
+ }
+ return $this->groupClass;
+ }
+
+ /**
+ * The user class
+ *
+ * @var adLDAPUsers
+ */
+ protected $userClass;
+
+ /**
+ * Get the userclass interface
+ *
+ * @return adLDAPUsers
+ */
+ public function user() {
+ if (!$this->userClass) {
+ $this->userClass = new adLDAPUsers($this);
+ }
+ return $this->userClass;
+ }
+
+ /**
+ * The folders class
+ *
+ * @var adLDAPFolders
+ */
+ protected $folderClass;
+
+ /**
+ * Get the folder class interface
+ *
+ * @return adLDAPFolders
+ */
+ public function folder() {
+ if (!$this->folderClass) {
+ $this->folderClass = new adLDAPFolders($this);
+ }
+ return $this->folderClass;
+ }
+
+ /**
+ * The utils class
+ *
+ * @var adLDAPUtils
+ */
+ protected $utilClass;
+
+ /**
+ * Get the utils class interface
+ *
+ * @return adLDAPUtils
+ */
+ public function utilities() {
+ if (!$this->utilClass) {
+ $this->utilClass = new adLDAPUtils($this);
+ }
+ return $this->utilClass;
+ }
+
+ /**
+ * The contacts class
+ *
+ * @var adLDAPContacts
+ */
+ protected $contactClass;
+
+ /**
+ * Get the contacts class interface
+ *
+ * @return adLDAPContacts
+ */
+ public function contact() {
+ if (!$this->contactClass) {
+ $this->contactClass = new adLDAPContacts($this);
+ }
+ return $this->contactClass;
+ }
+
+ /**
+ * The exchange class
+ *
+ * @var adLDAPExchange
+ */
+ protected $exchangeClass;
+
+ /**
+ * Get the exchange class interface
+ *
+ * @return adLDAPExchange
+ */
+ public function exchange() {
+ if (!$this->exchangeClass) {
+ $this->exchangeClass = new adLDAPExchange($this);
+ }
+ return $this->exchangeClass;
+ }
+
+ /**
+ * The computers class
+ *
+ * @var adLDAPComputers
+ */
+ protected $computersClass;
+
+ /**
+ * Get the computers class interface
+ *
+ * @return adLDAPComputers
+ */
+ public function computer() {
+ if (!$this->computerClass) {
+ $this->computerClass = new adLDAPComputers($this);
+ }
+ return $this->computerClass;
+ }
+
+ /**
+ * Getters and Setters
+ */
+
+ /**
+ * Set the account suffix
+ *
+ * @param string $accountSuffix
+ * @return void
+ */
+ public function setAccountSuffix($accountSuffix)
+ {
+ $this->accountSuffix = $accountSuffix;
+ }
+
+ /**
+ * Get the account suffix
+ *
+ * @return string
+ */
+ public function getAccountSuffix()
+ {
+ return $this->accountSuffix;
+ }
+
+ /**
+ * Set the domain controllers array
+ *
+ * @param array $domainControllers
+ * @return void
+ */
+ public function setDomainControllers(array $domainControllers)
+ {
+ $this->domainControllers = $domainControllers;
+ }
+
+ /**
+ * Get the list of domain controllers
+ *
+ * @return void
+ */
+ public function getDomainControllers()
+ {
+ return $this->domainControllers;
+ }
+
+ /**
+ * Sets the port number your domain controller communicates over
+ *
+ * @param int $adPort
+ */
+ public function setPort($adPort)
+ {
+ $this->adPort = $adPort;
+ }
+
+ /**
+ * Gets the port number your domain controller communicates over
+ *
+ * @return int
+ */
+ public function getPort()
+ {
+ return $this->adPort;
+ }
+
+ /**
+ * Set the username of an account with higher priviledges
+ *
+ * @param string $adminUsername
+ * @return void
+ */
+ public function setAdminUsername($adminUsername)
+ {
+ $this->adminUsername = $adminUsername;
+ }
+
+ /**
+ * Get the username of the account with higher priviledges
+ *
+ * This will throw an exception for security reasons
+ */
+ public function getAdminUsername()
+ {
+ throw new adLDAPException('For security reasons you cannot access the domain administrator account details');
+ }
+
+ /**
+ * Set the password of an account with higher priviledges
+ *
+ * @param string $adminPassword
+ * @return void
+ */
+ public function setAdminPassword($adminPassword)
+ {
+ $this->adminPassword = $adminPassword;
+ }
+
+ /**
+ * Get the password of the account with higher priviledges
+ *
+ * This will throw an exception for security reasons
+ */
+ public function getAdminPassword()
+ {
+ throw new adLDAPException('For security reasons you cannot access the domain administrator account details');
+ }
+
+ /**
+ * Set whether to detect the true primary group
+ *
+ * @param bool $realPrimaryGroup
+ * @return void
+ */
+ public function setRealPrimaryGroup($realPrimaryGroup)
+ {
+ $this->realPrimaryGroup = $realPrimaryGroup;
+ }
+
+ /**
+ * Get the real primary group setting
+ *
+ * @return bool
+ */
+ public function getRealPrimaryGroup()
+ {
+ return $this->realPrimaryGroup;
+ }
+
+ /**
+ * Set whether to use SSL
+ *
+ * @param bool $useSSL
+ * @return void
+ */
+ public function setUseSSL($useSSL)
+ {
+ $this->useSSL = $useSSL;
+ // Set the default port correctly
+ if($this->useSSL) {
+ $this->setPort(self::ADLDAP_LDAPS_PORT);
+ }
+ else {
+ $this->setPort(self::ADLDAP_LDAP_PORT);
+ }
+ }
+
+ /**
+ * Get the SSL setting
+ *
+ * @return bool
+ */
+ public function getUseSSL()
+ {
+ return $this->useSSL;
+ }
+
+ /**
+ * Set whether to use TLS
+ *
+ * @param bool $useTLS
+ * @return void
+ */
+ public function setUseTLS($useTLS)
+ {
+ $this->useTLS = $useTLS;
+ }
+
+ /**
+ * Get the TLS setting
+ *
+ * @return bool
+ */
+ public function getUseTLS()
+ {
+ return $this->useTLS;
+ }
+
+ /**
+ * Set whether to use SSO
+ * Requires ldap_sasl_bind support. Be sure --with-ldap-sasl is used when configuring PHP otherwise this function will be undefined.
+ *
+ * @param bool $useSSO
+ * @return void
+ */
+ public function setUseSSO($useSSO)
+ {
+ if ($useSSO === true && !$this->ldapSaslSupported()) {
+ throw new adLDAPException('No LDAP SASL support for PHP. See: http://php.net/ldap_sasl_bind');
+ }
+ $this->useSSO = $useSSO;
+ }
+
+ /**
+ * Get the SSO setting
+ *
+ * @return bool
+ */
+ public function getUseSSO()
+ {
+ return $this->useSSO;
+ }
+
+ /**
+ * Set whether to lookup recursive groups
+ *
+ * @param bool $recursiveGroups
+ * @return void
+ */
+ public function setRecursiveGroups($recursiveGroups)
+ {
+ $this->recursiveGroups = $recursiveGroups;
+ }
+
+ /**
+ * Get the recursive groups setting
+ *
+ * @return bool
+ */
+ public function getRecursiveGroups()
+ {
+ return $this->recursiveGroups;
+ }
+
+ /**
+ * Default Constructor
+ *
+ * Tries to bind to the AD domain over LDAP or LDAPs
+ *
+ * @param array $options Array of options to pass to the constructor
+ * @throws Exception - if unable to bind to Domain Controller
+ * @return bool
+ */
+ function __construct($options = array()) {
+ // You can specifically overide any of the default configuration options setup above
+ if (count($options) > 0) {
+ if (array_key_exists("account_suffix",$options)){ $this->accountSuffix = $options["account_suffix"]; }
+ if (array_key_exists("base_dn",$options)){ $this->baseDn = $options["base_dn"]; }
+ if (array_key_exists("domain_controllers",$options)){
+ if (!is_array($options["domain_controllers"])) {
+ throw new adLDAPException('[domain_controllers] option must be an array');
+ }
+ $this->domainControllers = $options["domain_controllers"];
+ }
+ if (array_key_exists("admin_username",$options)){ $this->adminUsername = $options["admin_username"]; }
+ if (array_key_exists("admin_password",$options)){ $this->adminPassword = $options["admin_password"]; }
+ if (array_key_exists("real_primarygroup",$options)){ $this->realPrimaryGroup = $options["real_primarygroup"]; }
+ if (array_key_exists("use_ssl",$options)){ $this->setUseSSL($options["use_ssl"]); }
+ if (array_key_exists("use_tls",$options)){ $this->useTLS = $options["use_tls"]; }
+ if (array_key_exists("recursive_groups",$options)){ $this->recursiveGroups = $options["recursive_groups"]; }
+ if (array_key_exists("ad_port",$options)){ $this->setPort($options["ad_port"]); }
+ if (array_key_exists("sso",$options)) {
+ $this->setUseSSO($options["sso"]);
+ if (!$this->ldapSaslSupported()) {
+ $this->setUseSSO(false);
+ }
+ }
+ }
+
+ if ($this->ldapSupported() === false) {
+ throw new adLDAPException('No LDAP support for PHP. See: http://php.net/ldap');
+ }
+
+ return $this->connect();
+ }
+
+ /**
+ * Default Destructor
+ *
+ * Closes the LDAP connection
+ *
+ * @return void
+ */
+ function __destruct() {
+ $this->close();
+ }
+
+ /**
+ * Connects and Binds to the Domain Controller
+ *
+ * @return bool
+ */
+ public function connect()
+ {
+ // Connect to the AD/LDAP server as the username/password
+ $domainController = $this->randomController();
+ if ($this->useSSL) {
+ $this->ldapConnection = ldap_connect("ldaps://" . $domainController, $this->adPort);
+ } else {
+ $this->ldapConnection = ldap_connect($domainController, $this->adPort);
+ }
+
+ // Set some ldap options for talking to AD
+ ldap_set_option($this->ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3);
+ ldap_set_option($this->ldapConnection, LDAP_OPT_REFERRALS, 0);
+
+ if ($this->useTLS) {
+ ldap_start_tls($this->ldapConnection);
+ }
+
+ // Bind as a domain admin if they've set it up
+ if ($this->adminUsername !== NULL && $this->adminPassword !== NULL) {
+ $this->ldapBind = @ldap_bind($this->ldapConnection, $this->adminUsername . $this->accountSuffix, $this->adminPassword);
+ if (!$this->ldapBind) {
+ if ($this->useSSL && !$this->useTLS) {
+ // If you have problems troubleshooting, remove the @ character from the ldapldapBind command above to get the actual error message
+ throw new adLDAPException('Bind to Active Directory failed. Either the LDAPs connection failed or the login credentials are incorrect. AD said: ' . $this->getLastError());
+ }
+ else {
+ throw new adLDAPException('Bind to Active Directory failed. Check the login credentials and/or server details. AD said: ' . $this->getLastError());
+ }
+ }
+ }
+ if ($this->useSSO && $_SERVER['REMOTE_USER'] && $this->adminUsername === null && $_SERVER['KRB5CCNAME']) {
+ putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']);
+ $this->ldapBind = @ldap_sasl_bind($this->ldapConnection, NULL, NULL, "GSSAPI");
+ if (!$this->ldapBind){
+ throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError());
+ }
+ else {
+ return true;
+ }
+ }
+
+
+ if ($this->baseDn == NULL) {
+ $this->baseDn = $this->findBaseDn();
+ }
+
+ return true;
+ }
+
+ /**
+ * Closes the LDAP connection
+ *
+ * @return void
+ */
+ public function close() {
+ if ($this->ldapConnection) {
+ @ldap_close($this->ldapConnection);
+ }
+ }
+
+ /**
+ * Validate a user's login credentials
+ *
+ * @param string $username A user's AD username
+ * @param string $password A user's AD password
+ * @param bool optional $preventRebind
+ * @return bool
+ */
+ public function authenticate($username, $password, $preventRebind = false) {
+ // Prevent null binding
+ if ($username === NULL || $password === NULL) { return false; }
+ if (empty($username) || empty($password)) { return false; }
+
+ // Allow binding over SSO for Kerberos
+ if ($this->useSSO && $_SERVER['REMOTE_USER'] && $_SERVER['REMOTE_USER'] == $username && $this->adminUsername === NULL && $_SERVER['KRB5CCNAME']) {
+ putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']);
+ $this->ldapBind = @ldap_sasl_bind($this->ldapConnection, NULL, NULL, "GSSAPI");
+ if (!$this->ldapBind) {
+ throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError());
+ }
+ else {
+ return true;
+ }
+ }
+
+ // Bind as the user
+ $ret = true;
+ $this->ldapBind = @ldap_bind($this->ldapConnection, $username . $this->accountSuffix, $password);
+ if (!$this->ldapBind){
+ $ret = false;
+ }
+
+ // Cnce we've checked their details, kick back into admin mode if we have it
+ if ($this->adminUsername !== NULL && !$preventRebind) {
+ $this->ldapBind = @ldap_bind($this->ldapConnection, $this->adminUsername . $this->accountSuffix , $this->adminPassword);
+ if (!$this->ldapBind){
+ // This should never happen in theory
+ throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError());
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Find the Base DN of your domain controller
+ *
+ * @return string
+ */
+ public function findBaseDn()
+ {
+ $namingContext = $this->getRootDse(array('defaultnamingcontext'));
+ return $namingContext[0]['defaultnamingcontext'][0];
+ }
+
+ /**
+ * Get the RootDSE properties from a domain controller
+ *
+ * @param array $attributes The attributes you wish to query e.g. defaultnamingcontext
+ * @return array
+ */
+ public function getRootDse($attributes = array("*", "+")) {
+ if (!$this->ldapBind){ return (false); }
+
+ $sr = @ldap_read($this->ldapConnection, NULL, 'objectClass=*', $attributes);
+ $entries = @ldap_get_entries($this->ldapConnection, $sr);
+ return $entries;
+ }
+
+ /**
+ * Get last error from Active Directory
+ *
+ * This function gets the last message from Active Directory
+ * This may indeed be a 'Success' message but if you get an unknown error
+ * it might be worth calling this function to see what errors were raised
+ *
+ * return string
+ */
+ public function getLastError() {
+ return @ldap_error($this->ldapConnection);
+ }
+
+ /**
+ * Detect LDAP support in php
+ *
+ * @return bool
+ */
+ protected function ldapSupported()
+ {
+ if (!function_exists('ldap_connect')) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Detect ldap_sasl_bind support in PHP
+ *
+ * @return bool
+ */
+ protected function ldapSaslSupported()
+ {
+ if (!function_exists('ldap_sasl_bind')) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Schema
+ *
+ * @param array $attributes Attributes to be queried
+ * @return array
+ */
+ public function adldap_schema($attributes){
+
+ // LDAP doesn't like NULL attributes, only set them if they have values
+ // If you wish to remove an attribute you should set it to a space
+ // TO DO: Adapt user_modify to use ldap_mod_delete to remove a NULL attribute
+ $mod=array();
+
+ // Check every attribute to see if it contains 8bit characters and then UTF8 encode them
+ array_walk($attributes, array($this, 'encode8bit'));
+
+ if ($attributes["address_city"]){ $mod["l"][0]=$attributes["address_city"]; }
+ if ($attributes["address_code"]){ $mod["postalCode"][0]=$attributes["address_code"]; }
+ //if ($attributes["address_country"]){ $mod["countryCode"][0]=$attributes["address_country"]; } // use country codes?
+ if ($attributes["address_country"]){ $mod["c"][0]=$attributes["address_country"]; }
+ if ($attributes["address_pobox"]){ $mod["postOfficeBox"][0]=$attributes["address_pobox"]; }
+ if ($attributes["address_state"]){ $mod["st"][0]=$attributes["address_state"]; }
+ if ($attributes["address_street"]){ $mod["streetAddress"][0]=$attributes["address_street"]; }
+ if ($attributes["company"]){ $mod["company"][0]=$attributes["company"]; }
+ if ($attributes["change_password"]){ $mod["pwdLastSet"][0]=0; }
+ if ($attributes["department"]){ $mod["department"][0]=$attributes["department"]; }
+ if ($attributes["description"]){ $mod["description"][0]=$attributes["description"]; }
+ if ($attributes["display_name"]){ $mod["displayName"][0]=$attributes["display_name"]; }
+ if ($attributes["email"]){ $mod["mail"][0]=$attributes["email"]; }
+ if ($attributes["expires"]){ $mod["accountExpires"][0]=$attributes["expires"]; } //unix epoch format?
+ if ($attributes["firstname"]){ $mod["givenName"][0]=$attributes["firstname"]; }
+ if ($attributes["home_directory"]){ $mod["homeDirectory"][0]=$attributes["home_directory"]; }
+ if ($attributes["home_drive"]){ $mod["homeDrive"][0]=$attributes["home_drive"]; }
+ if ($attributes["initials"]){ $mod["initials"][0]=$attributes["initials"]; }
+ if ($attributes["logon_name"]){ $mod["userPrincipalName"][0]=$attributes["logon_name"]; }
+ if ($attributes["manager"]){ $mod["manager"][0]=$attributes["manager"]; } //UNTESTED ***Use DistinguishedName***
+ if ($attributes["office"]){ $mod["physicalDeliveryOfficeName"][0]=$attributes["office"]; }
+ if ($attributes["password"]){ $mod["unicodePwd"][0]=$this->user()->encodePassword($attributes["password"]); }
+ if ($attributes["profile_path"]){ $mod["profilepath"][0]=$attributes["profile_path"]; }
+ if ($attributes["script_path"]){ $mod["scriptPath"][0]=$attributes["script_path"]; }
+ if ($attributes["surname"]){ $mod["sn"][0]=$attributes["surname"]; }
+ if ($attributes["title"]){ $mod["title"][0]=$attributes["title"]; }
+ if ($attributes["telephone"]){ $mod["telephoneNumber"][0]=$attributes["telephone"]; }
+ if ($attributes["mobile"]){ $mod["mobile"][0]=$attributes["mobile"]; }
+ if ($attributes["pager"]){ $mod["pager"][0]=$attributes["pager"]; }
+ if ($attributes["ipphone"]){ $mod["ipphone"][0]=$attributes["ipphone"]; }
+ if ($attributes["web_page"]){ $mod["wWWHomePage"][0]=$attributes["web_page"]; }
+ if ($attributes["fax"]){ $mod["facsimileTelephoneNumber"][0]=$attributes["fax"]; }
+ if ($attributes["enabled"]){ $mod["userAccountControl"][0]=$attributes["enabled"]; }
+ if ($attributes["homephone"]){ $mod["homephone"][0]=$attributes["homephone"]; }
+
+ // Distribution List specific schema
+ if ($attributes["group_sendpermission"]){ $mod["dlMemSubmitPerms"][0]=$attributes["group_sendpermission"]; }
+ if ($attributes["group_rejectpermission"]){ $mod["dlMemRejectPerms"][0]=$attributes["group_rejectpermission"]; }
+
+ // Exchange Schema
+ if ($attributes["exchange_homemdb"]){ $mod["homeMDB"][0]=$attributes["exchange_homemdb"]; }
+ if ($attributes["exchange_mailnickname"]){ $mod["mailNickname"][0]=$attributes["exchange_mailnickname"]; }
+ if ($attributes["exchange_proxyaddress"]){ $mod["proxyAddresses"][0]=$attributes["exchange_proxyaddress"]; }
+ if ($attributes["exchange_usedefaults"]){ $mod["mDBUseDefaults"][0]=$attributes["exchange_usedefaults"]; }
+ if ($attributes["exchange_policyexclude"]){ $mod["msExchPoliciesExcluded"][0]=$attributes["exchange_policyexclude"]; }
+ if ($attributes["exchange_policyinclude"]){ $mod["msExchPoliciesIncluded"][0]=$attributes["exchange_policyinclude"]; }
+ if ($attributes["exchange_addressbook"]){ $mod["showInAddressBook"][0]=$attributes["exchange_addressbook"]; }
+ if ($attributes["exchange_altrecipient"]){ $mod["altRecipient"][0]=$attributes["exchange_altrecipient"]; }
+ if ($attributes["exchange_deliverandredirect"]){ $mod["deliverAndRedirect"][0]=$attributes["exchange_deliverandredirect"]; }
+
+ // This schema is designed for contacts
+ if ($attributes["exchange_hidefromlists"]){ $mod["msExchHideFromAddressLists"][0]=$attributes["exchange_hidefromlists"]; }
+ if ($attributes["contact_email"]){ $mod["targetAddress"][0]=$attributes["contact_email"]; }
+
+ //echo ("<pre>"); print_r($mod);
+ /*
+ // modifying a name is a bit fiddly
+ if ($attributes["firstname"] && $attributes["surname"]){
+ $mod["cn"][0]=$attributes["firstname"]." ".$attributes["surname"];
+ $mod["displayname"][0]=$attributes["firstname"]." ".$attributes["surname"];
+ $mod["name"][0]=$attributes["firstname"]." ".$attributes["surname"];
+ }
+ */
+
+ if (count($mod)==0){ return (false); }
+ return ($mod);
+ }
+
+ /**
+ * Convert 8bit characters e.g. accented characters to UTF8 encoded characters
+ */
+ protected function encode8Bit(&$item, $key) {
+ $encode = false;
+ if (is_string($item)) {
+ for ($i=0; $i<strlen($item); $i++) {
+ if (ord($item[$i]) >> 7) {
+ $encode = true;
+ }
+ }
+ }
+ if ($encode === true && $key != 'password') {
+ $item = utf8_encode($item);
+ }
+ }
+
+ /**
+ * Select a random domain controller from your domain controller array
+ *
+ * @return string
+ */
+ protected function randomController()
+ {
+ mt_srand(doubleval(microtime()) * 100000000); // For older PHP versions
+ /*if (sizeof($this->domainControllers) > 1) {
+ $adController = $this->domainControllers[array_rand($this->domainControllers)];
+ // Test if the controller is responding to pings
+ $ping = $this->pingController($adController);
+ if ($ping === false) {
+ // Find the current key in the domain controllers array
+ $key = array_search($adController, $this->domainControllers);
+ // Remove it so that we don't end up in a recursive loop
+ unset($this->domainControllers[$key]);
+ // Select a new controller
+ return $this->randomController();
+ }
+ else {
+ return ($adController);
+ }
+ } */
+ return $this->domainControllers[array_rand($this->domainControllers)];
+ }
+
+ /**
+ * Test basic connectivity to controller
+ *
+ * @return bool
+ */
+ protected function pingController($host) {
+ $port = $this->adPort;
+ fsockopen($host, $port, $errno, $errstr, 10);
+ if ($errno > 0) {
+ return false;
+ }
+ return true;
+ }
+
+}
+
+/**
+* adLDAP Exception Handler
+*
+* Exceptions of this type are thrown on bind failure or when SSL is required but not configured
+* Example:
+* try {
+* $adldap = new adLDAP();
+* }
+* catch (adLDAPException $e) {
+* echo $e;
+* exit();
+* }
+*/
+class adLDAPException extends Exception {}
diff --git a/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPComputers.php b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPComputers.php
new file mode 100644
index 0000000..aabd88f
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPComputers.php
@@ -0,0 +1,153 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @subpackage Computers
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 97 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+ */
+require_once(dirname(__FILE__) . '/../adLDAP.php');
+require_once(dirname(__FILE__) . '/../collections/adLDAPComputerCollection.php');
+
+/**
+* COMPUTER MANAGEMENT FUNCTIONS
+*/
+class adLDAPComputers {
+
+ /**
+ * The current adLDAP connection via dependency injection
+ *
+ * @var adLDAP
+ */
+ protected $adldap;
+
+ public function __construct(adLDAP $adldap) {
+ $this->adldap = $adldap;
+ }
+
+ /**
+ * Get information about a specific computer. Returned in a raw array format from AD
+ *
+ * @param string $computerName The name of the computer
+ * @param array $fields Attributes to return
+ * @return array
+ */
+ public function info($computerName, $fields = NULL)
+ {
+ if ($computerName === NULL) { return false; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ $filter = "(&(objectClass=computer)(cn=" . $computerName . "))";
+ if ($fields === NULL) {
+ $fields = array("memberof","cn","displayname","dnshostname","distinguishedname","objectcategory","operatingsystem","operatingsystemservicepack","operatingsystemversion");
+ }
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ return $entries;
+ }
+
+ /**
+ * Find information about the computers. Returned in a raw array format from AD
+ *
+ * @param string $computerName The name of the computer
+ * @param array $fields Array of parameters to query
+ * @return mixed
+ */
+ public function infoCollection($computerName, $fields = NULL)
+ {
+ if ($computerName === NULL) { return false; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ $info = $this->info($computerName, $fields);
+
+ if ($info !== false) {
+ $collection = new adLDAPComputerCollection($info, $this->adldap);
+ return $collection;
+ }
+ return false;
+ }
+
+ /**
+ * Check if a computer is in a group
+ *
+ * @param string $computerName The name of the computer
+ * @param string $group The group to check
+ * @param bool $recursive Whether to check recursively
+ * @return array
+ */
+ public function inGroup($computerName, $group, $recursive = NULL)
+ {
+ if ($computerName === NULL) { return false; }
+ if ($group === NULL) { return false; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+ if ($recursive === NULL) { $recursive = $this->adldap->getRecursiveGroups(); } // use the default option if they haven't set it
+
+ //get a list of the groups
+ $groups = $this->groups($computerName, array("memberof"), $recursive);
+
+ //return true if the specified group is in the group list
+ if (in_array($group, $groups)){
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the groups a computer is in
+ *
+ * @param string $computerName The name of the computer
+ * @param bool $recursive Whether to check recursively
+ * @return array
+ */
+ public function groups($computerName, $recursive = NULL)
+ {
+ if ($computerName === NULL) { return false; }
+ if ($recursive === NULL) { $recursive = $this->adldap->getRecursiveGroups(); } //use the default option if they haven't set it
+ if (!$this->adldap->getLdapBind()){ return false; }
+
+ //search the directory for their information
+ $info = @$this->info($computerName, array("memberof", "primarygroupid"));
+ $groups = $this->adldap->utilities()->niceNames($info[0]["memberof"]); //presuming the entry returned is our guy (unique usernames)
+
+ if ($recursive === true) {
+ foreach ($groups as $id => $groupName){
+ $extraGroups = $this->adldap->group()->recursiveGroups($groupName);
+ $groups = array_merge($groups, $extraGroups);
+ }
+ }
+
+ return $groups;
+ }
+
+}
+?> \ No newline at end of file
diff --git a/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPContacts.php b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPContacts.php
new file mode 100644
index 0000000..42a0d75
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPContacts.php
@@ -0,0 +1,294 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @subpackage Contacts
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 97 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+ */
+
+require_once(dirname(__FILE__) . '/../adLDAP.php');
+require_once(dirname(__FILE__) . '/../collections/adLDAPContactCollection.php');
+
+class adLDAPContacts {
+ /**
+ * The current adLDAP connection via dependency injection
+ *
+ * @var adLDAP
+ */
+ protected $adldap;
+
+ public function __construct(adLDAP $adldap) {
+ $this->adldap = $adldap;
+ }
+
+ //*****************************************************************************************************************
+ // CONTACT FUNCTIONS
+ // * Still work to do in this area, and new functions to write
+
+ /**
+ * Create a contact
+ *
+ * @param array $attributes The attributes to set to the contact
+ * @return bool
+ */
+ public function create($attributes)
+ {
+ // Check for compulsory fields
+ if (!array_key_exists("display_name", $attributes)) { return "Missing compulsory field [display_name]"; }
+ if (!array_key_exists("email", $attributes)) { return "Missing compulsory field [email]"; }
+ if (!array_key_exists("container", $attributes)) { return "Missing compulsory field [container]"; }
+ if (!is_array($attributes["container"])) { return "Container attribute must be an array."; }
+
+ // Translate the schema
+ $add = $this->adldap->adldap_schema($attributes);
+
+ // Additional stuff only used for adding contacts
+ $add["cn"][0] = $attributes["display_name"];
+ $add["objectclass"][0] = "top";
+ $add["objectclass"][1] = "person";
+ $add["objectclass"][2] = "organizationalPerson";
+ $add["objectclass"][3] = "contact";
+ if (!isset($attributes['exchange_hidefromlists'])) {
+ $add["msExchHideFromAddressLists"][0] = "TRUE";
+ }
+
+ // Determine the container
+ $attributes["container"] = array_reverse($attributes["container"]);
+ $container= "OU=" . implode(",OU=", $attributes["container"]);
+
+ // Add the entry
+ $result = @ldap_add($this->adldap->getLdapConnection(), "CN=" . $this->adldap->utilities()->escapeCharacters($add["cn"][0]) . ", " . $container . "," . $this->adldap->getBaseDn(), $add);
+ if ($result != true) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determine the list of groups a contact is a member of
+ *
+ * @param string $distinguisedname The full DN of a contact
+ * @param bool $recursive Recursively check groups
+ * @return array
+ */
+ public function groups($distinguishedName, $recursive = NULL)
+ {
+ if ($distinguishedName === NULL) { return false; }
+ if ($recursive === NULL) { $recursive = $this->adldap->getRecursiveGroups(); } //use the default option if they haven't set it
+ if (!$this->adldap->getLdapBind()){ return false; }
+
+ // Search the directory for their information
+ $info = @$this->info($distinguishedName, array("memberof", "primarygroupid"));
+ $groups = $this->adldap->utilities()->niceNames($info[0]["memberof"]); //presuming the entry returned is our contact
+
+ if ($recursive === true){
+ foreach ($groups as $id => $groupName){
+ $extraGroups = $this->adldap->group()->recursiveGroups($groupName);
+ $groups = array_merge($groups, $extraGroups);
+ }
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Get contact information. Returned in a raw array format from AD
+ *
+ * @param string $distinguisedname The full DN of a contact
+ * @param array $fields Attributes to be returned
+ * @return array
+ */
+ public function info($distinguishedName, $fields = NULL)
+ {
+ if ($distinguishedName === NULL) { return false; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ $filter = "distinguishedName=" . $distinguishedName;
+ if ($fields === NULL) {
+ $fields = array("distinguishedname", "mail", "memberof", "department", "displayname", "telephonenumber", "primarygroupid", "objectsid");
+ }
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ if ($entries[0]['count'] >= 1) {
+ // AD does not return the primary group in the ldap query, we may need to fudge it
+ if ($this->adldap->getRealPrimaryGroup() && isset($entries[0]["primarygroupid"][0]) && isset($entries[0]["primarygroupid"][0])){
+ //$entries[0]["memberof"][]=$this->group_cn($entries[0]["primarygroupid"][0]);
+ $entries[0]["memberof"][] = $this->adldap->group()->getPrimaryGroup($entries[0]["primarygroupid"][0], $entries[0]["objectsid"][0]);
+ } else {
+ $entries[0]["memberof"][] = "CN=Domain Users,CN=Users," . $this->adldap->getBaseDn();
+ }
+ }
+
+ $entries[0]["memberof"]["count"]++;
+ return $entries;
+ }
+
+ /**
+ * Find information about the contacts. Returned in a raw array format from AD
+ *
+ * @param string $distinguishedName The full DN of a contact
+ * @param array $fields Array of parameters to query
+ * @return mixed
+ */
+ public function infoCollection($distinguishedName, $fields = NULL)
+ {
+ if ($distinguishedName === NULL) { return false; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ $info = $this->info($distinguishedName, $fields);
+
+ if ($info !== false) {
+ $collection = new adLDAPContactCollection($info, $this->adldap);
+ return $collection;
+ }
+ return false;
+ }
+
+ /**
+ * Determine if a contact is a member of a group
+ *
+ * @param string $distinguisedName The full DN of a contact
+ * @param string $group The group name to query
+ * @param bool $recursive Recursively check groups
+ * @return bool
+ */
+ public function inGroup($distinguisedName, $group, $recursive = NULL)
+ {
+ if ($distinguisedName === NULL) { return false; }
+ if ($group === NULL) { return false; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+ if ($recursive === NULL) { $recursive = $this->adldap->getRecursiveGroups(); } //use the default option if they haven't set it
+
+ // Get a list of the groups
+ $groups = $this->groups($distinguisedName, array("memberof"), $recursive);
+
+ // Return true if the specified group is in the group list
+ if (in_array($group, $groups)){
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Modify a contact
+ *
+ * @param string $distinguishedName The contact to query
+ * @param array $attributes The attributes to modify. Note if you set the enabled attribute you must not specify any other attributes
+ * @return bool
+ */
+ public function modify($distinguishedName, $attributes) {
+ if ($distinguishedName === NULL) { return "Missing compulsory field [distinguishedname]"; }
+
+ // Translate the update to the LDAP schema
+ $mod = $this->adldap->adldap_schema($attributes);
+
+ // Check to see if this is an enabled status update
+ if (!$mod) {
+ return false;
+ }
+
+ // Do the update
+ $result = ldap_modify($this->adldap->getLdapConnection(), $distinguishedName, $mod);
+ if ($result == false) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Delete a contact
+ *
+ * @param string $distinguishedName The contact dn to delete (please be careful here!)
+ * @return array
+ */
+ public function delete($distinguishedName)
+ {
+ $result = $this->folder()->delete($distinguishedName);
+ if ($result != true) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return a list of all contacts
+ *
+ * @param bool $includeDescription Include a description of a contact
+ * @param string $search The search parameters
+ * @param bool $sorted Whether to sort the results
+ * @return array
+ */
+ public function all($includeDescription = false, $search = "*", $sorted = true) {
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ // Perform the search and grab all their details
+ $filter = "(&(objectClass=contact)(cn=" . $search . "))";
+ $fields = array("displayname","distinguishedname");
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ $usersArray = array();
+ for ($i=0; $i<$entries["count"]; $i++){
+ if ($includeDescription && strlen($entries[$i]["displayname"][0])>0){
+ $usersArray[$entries[$i]["distinguishedname"][0]] = $entries[$i]["displayname"][0];
+ } elseif ($includeDescription){
+ $usersArray[$entries[$i]["distinguishedname"][0]] = $entries[$i]["distinguishedname"][0];
+ } else {
+ array_push($usersArray, $entries[$i]["distinguishedname"][0]);
+ }
+ }
+ if ($sorted) {
+ asort($usersArray);
+ }
+ return $usersArray;
+ }
+
+ /**
+ * Mail enable a contact
+ * Allows email to be sent to them through Exchange
+ *
+ * @param string $distinguishedname The contact to mail enable
+ * @param string $emailaddress The email address to allow emails to be sent through
+ * @param string $mailnickname The mailnickname for the contact in Exchange. If NULL this will be set to the display name
+ * @return bool
+ */
+ public function contactMailEnable($distinguishedName, $emailAddress, $mailNickname = NULL){
+ return $this->adldap->exchange()->contactMailEnable($distinguishedName, $emailAddress, $mailNickname);
+ }
+
+
+}
+?>
diff --git a/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPExchange.php b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPExchange.php
new file mode 100644
index 0000000..d70aac7
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPExchange.php
@@ -0,0 +1,390 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @subpackage Exchange
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 97 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+ */
+require_once(dirname(__FILE__) . '/../adLDAP.php');
+
+/**
+* MICROSOFT EXCHANGE FUNCTIONS
+*/
+class adLDAPExchange {
+ /**
+ * The current adLDAP connection via dependency injection
+ *
+ * @var adLDAP
+ */
+ protected $adldap;
+
+ public function __construct(adLDAP $adldap) {
+ $this->adldap = $adldap;
+ }
+
+ /**
+ * Create an Exchange account
+ *
+ * @param string $username The username of the user to add the Exchange account to
+ * @param array $storageGroup The mailbox, Exchange Storage Group, for the user account, this must be a full CN
+ * If the storage group has a different base_dn to the adLDAP configuration, set it using $base_dn
+ * @param string $emailAddress The primary email address to add to this user
+ * @param string $mailNickname The mail nick name. If mail nickname is blank, the username will be used
+ * @param bool $mdbUseDefaults Indicates whether the store should use the default quota, rather than the per-mailbox quota.
+ * @param string $baseDn Specify an alternative base_dn for the Exchange storage group
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return bool
+ */
+ public function createMailbox($username, $storageGroup, $emailAddress, $mailNickname=NULL, $useDefaults=TRUE, $baseDn=NULL, $isGUID=false)
+ {
+ if ($username === NULL){ return "Missing compulsory field [username]"; }
+ if ($storageGroup === NULL) { return "Missing compulsory array [storagegroup]"; }
+ if (!is_array($storageGroup)) { return "[storagegroup] must be an array"; }
+ if ($emailAddress === NULL) { return "Missing compulsory field [emailAddress]"; }
+
+ if ($baseDn === NULL) {
+ $baseDn = $this->adldap->getBaseDn();
+ }
+
+ $container = "CN=" . implode(",CN=", $storageGroup);
+
+ if ($mailNickname === NULL) {
+ $mailNickname = $username;
+ }
+ $mdbUseDefaults = $this->adldap->utilities()->boolToString($useDefaults);
+
+ $attributes = array(
+ 'exchange_homemdb'=>$container.",".$baseDn,
+ 'exchange_proxyaddress'=>'SMTP:' . $emailAddress,
+ 'exchange_mailnickname'=>$mailNickname,
+ 'exchange_usedefaults'=>$mdbUseDefaults
+ );
+ $result = $this->adldap->user()->modify($username, $attributes, $isGUID);
+ if ($result == false) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Add an X400 address to Exchange
+ * See http://tools.ietf.org/html/rfc1685 for more information.
+ * An X400 Address looks similar to this X400:c=US;a= ;p=Domain;o=Organization;s=Doe;g=John;
+ *
+ * @param string $username The username of the user to add the X400 to to
+ * @param string $country Country
+ * @param string $admd Administration Management Domain
+ * @param string $pdmd Private Management Domain (often your AD domain)
+ * @param string $org Organization
+ * @param string $surname Surname
+ * @param string $givenName Given name
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return bool
+ */
+ public function addX400($username, $country, $admd, $pdmd, $org, $surname, $givenName, $isGUID=false)
+ {
+ if ($username === NULL){ return "Missing compulsory field [username]"; }
+
+ $proxyValue = 'X400:';
+
+ // Find the dn of the user
+ $user = $this->adldap->user()->info($username, array("cn","proxyaddresses"), $isGUID);
+ if ($user[0]["dn"] === NULL) { return false; }
+ $userDn = $user[0]["dn"];
+
+ // We do not have to demote an email address from the default so we can just add the new proxy address
+ $attributes['exchange_proxyaddress'] = $proxyValue . 'c=' . $country . ';a=' . $admd . ';p=' . $pdmd . ';o=' . $org . ';s=' . $surname . ';g=' . $givenName . ';';
+
+ // Translate the update to the LDAP schema
+ $add = $this->adldap->adldap_schema($attributes);
+
+ if (!$add) { return false; }
+
+ // Do the update
+ // Take out the @ to see any errors, usually this error might occur because the address already
+ // exists in the list of proxyAddresses
+ $result = @ldap_mod_add($this->adldap->getLdapConnection(), $userDn, $add);
+ if ($result == false) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Add an address to Exchange
+ *
+ * @param string $username The username of the user to add the Exchange account to
+ * @param string $emailAddress The email address to add to this user
+ * @param bool $default Make this email address the default address, this is a bit more intensive as we have to demote any existing default addresses
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return bool
+ */
+ public function addAddress($username, $emailAddress, $default = FALSE, $isGUID = false)
+ {
+ if ($username === NULL) { return "Missing compulsory field [username]"; }
+ if ($emailAddress === NULL) { return "Missing compulsory fields [emailAddress]"; }
+
+ $proxyValue = 'smtp:';
+ if ($default === true) {
+ $proxyValue = 'SMTP:';
+ }
+
+ // Find the dn of the user
+ $user = $this->adldap->user()->info($username, array("cn","proxyaddresses"), $isGUID);
+ if ($user[0]["dn"] === NULL){ return false; }
+ $userDn = $user[0]["dn"];
+
+ // We need to scan existing proxy addresses and demote the default one
+ if (is_array($user[0]["proxyaddresses"]) && $default === true) {
+ $modAddresses = array();
+ for ($i=0;$i<sizeof($user[0]['proxyaddresses']);$i++) {
+ if (strstr($user[0]['proxyaddresses'][$i], 'SMTP:') !== false) {
+ $user[0]['proxyaddresses'][$i] = str_replace('SMTP:', 'smtp:', $user[0]['proxyaddresses'][$i]);
+ }
+ if ($user[0]['proxyaddresses'][$i] != '') {
+ $modAddresses['proxyAddresses'][$i] = $user[0]['proxyaddresses'][$i];
+ }
+ }
+ $modAddresses['proxyAddresses'][(sizeof($user[0]['proxyaddresses'])-1)] = 'SMTP:' . $emailAddress;
+
+ $result = @ldap_mod_replace($this->adldap->getLdapConnection(), $userDn, $modAddresses);
+ if ($result == false) {
+ return false;
+ }
+
+ return true;
+ }
+ else {
+ // We do not have to demote an email address from the default so we can just add the new proxy address
+ $attributes['exchange_proxyaddress'] = $proxyValue . $emailAddress;
+
+ // Translate the update to the LDAP schema
+ $add = $this->adldap->adldap_schema($attributes);
+
+ if (!$add) {
+ return false;
+ }
+
+ // Do the update
+ // Take out the @ to see any errors, usually this error might occur because the address already
+ // exists in the list of proxyAddresses
+ $result = @ldap_mod_add($this->adldap->getLdapConnection(), $userDn,$add);
+ if ($result == false) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * Remove an address to Exchange
+ * If you remove a default address the account will no longer have a default,
+ * we recommend changing the default address first
+ *
+ * @param string $username The username of the user to add the Exchange account to
+ * @param string $emailAddress The email address to add to this user
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return bool
+ */
+ public function deleteAddress($username, $emailAddress, $isGUID=false)
+ {
+ if ($username === NULL) { return "Missing compulsory field [username]"; }
+ if ($emailAddress === NULL) { return "Missing compulsory fields [emailAddress]"; }
+
+ // Find the dn of the user
+ $user = $this->adldap->user()->info($username, array("cn","proxyaddresses"), $isGUID);
+ if ($user[0]["dn"] === NULL) { return false; }
+ $userDn = $user[0]["dn"];
+
+ if (is_array($user[0]["proxyaddresses"])) {
+ $mod = array();
+ for ($i=0;$i<sizeof($user[0]['proxyaddresses']);$i++) {
+ if (strstr($user[0]['proxyaddresses'][$i], 'SMTP:') !== false && $user[0]['proxyaddresses'][$i] == 'SMTP:' . $emailAddress) {
+ $mod['proxyAddresses'][0] = 'SMTP:' . $emailAddress;
+ }
+ elseif (strstr($user[0]['proxyaddresses'][$i], 'smtp:') !== false && $user[0]['proxyaddresses'][$i] == 'smtp:' . $emailAddress) {
+ $mod['proxyAddresses'][0] = 'smtp:' . $emailAddress;
+ }
+ }
+
+ $result = @ldap_mod_del($this->adldap->getLdapConnection(), $userDn,$mod);
+ if ($result == false) {
+ return false;
+ }
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ /**
+ * Change the default address
+ *
+ * @param string $username The username of the user to add the Exchange account to
+ * @param string $emailAddress The email address to make default
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return bool
+ */
+ public function primaryAddress($username, $emailAddress, $isGUID = false)
+ {
+ if ($username === NULL) { return "Missing compulsory field [username]"; }
+ if ($emailAddress === NULL) { return "Missing compulsory fields [emailAddress]"; }
+
+ // Find the dn of the user
+ $user = $this->adldap->user()->info($username, array("cn","proxyaddresses"), $isGUID);
+ if ($user[0]["dn"] === NULL){ return false; }
+ $userDn = $user[0]["dn"];
+
+ if (is_array($user[0]["proxyaddresses"])) {
+ $modAddresses = array();
+ for ($i=0;$i<sizeof($user[0]['proxyaddresses']);$i++) {
+ if (strstr($user[0]['proxyaddresses'][$i], 'SMTP:') !== false) {
+ $user[0]['proxyaddresses'][$i] = str_replace('SMTP:', 'smtp:', $user[0]['proxyaddresses'][$i]);
+ }
+ if ($user[0]['proxyaddresses'][$i] == 'smtp:' . $emailAddress) {
+ $user[0]['proxyaddresses'][$i] = str_replace('smtp:', 'SMTP:', $user[0]['proxyaddresses'][$i]);
+ }
+ if ($user[0]['proxyaddresses'][$i] != '') {
+ $modAddresses['proxyAddresses'][$i] = $user[0]['proxyaddresses'][$i];
+ }
+ }
+
+ $result = @ldap_mod_replace($this->adldap->getLdapConnection(), $userDn, $modAddresses);
+ if ($result == false) {
+ return false;
+ }
+
+ return true;
+ }
+
+ }
+
+ /**
+ * Mail enable a contact
+ * Allows email to be sent to them through Exchange
+ *
+ * @param string $distinguishedName The contact to mail enable
+ * @param string $emailAddress The email address to allow emails to be sent through
+ * @param string $mailNickname The mailnickname for the contact in Exchange. If NULL this will be set to the display name
+ * @return bool
+ */
+ public function contactMailEnable($distinguishedName, $emailAddress, $mailNickname = NULL)
+ {
+ if ($distinguishedName === NULL) { return "Missing compulsory field [distinguishedName]"; }
+ if ($emailAddress === NULL) { return "Missing compulsory field [emailAddress]"; }
+
+ if ($mailNickname !== NULL) {
+ // Find the dn of the user
+ $user = $this->adldap->contact()->info($distinguishedName, array("cn","displayname"));
+ if ($user[0]["displayname"] === NULL) { return false; }
+ $mailNickname = $user[0]['displayname'][0];
+ }
+
+ $attributes = array("email"=>$emailAddress,"contact_email"=>"SMTP:" . $emailAddress,"exchange_proxyaddress"=>"SMTP:" . $emailAddress,"exchange_mailnickname" => $mailNickname);
+
+ // Translate the update to the LDAP schema
+ $mod = $this->adldap->adldap_schema($attributes);
+
+ // Check to see if this is an enabled status update
+ if (!$mod) { return false; }
+
+ // Do the update
+ $result = ldap_modify($this->adldap->getLdapConnection(), $distinguishedName, $mod);
+ if ($result == false) { return false; }
+
+ return true;
+ }
+
+ /**
+ * Returns a list of Exchange Servers in the ConfigurationNamingContext of the domain
+ *
+ * @param array $attributes An array of the AD attributes you wish to return
+ * @return array
+ */
+ public function servers($attributes = array('cn','distinguishedname','serialnumber'))
+ {
+ if (!$this->adldap->getLdapBind()){ return false; }
+
+ $configurationNamingContext = $this->adldap->getRootDse(array('configurationnamingcontext'));
+ $sr = @ldap_search($this->adldap->getLdapConnection(), $configurationNamingContext[0]['configurationnamingcontext'][0],'(&(objectCategory=msExchExchangeServer))', $attributes);
+ $entries = @ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+ return $entries;
+ }
+
+ /**
+ * Returns a list of Storage Groups in Exchange for a given mail server
+ *
+ * @param string $exchangeServer The full DN of an Exchange server. You can use exchange_servers() to find the DN for your server
+ * @param array $attributes An array of the AD attributes you wish to return
+ * @param bool $recursive If enabled this will automatically query the databases within a storage group
+ * @return array
+ */
+ public function storageGroups($exchangeServer, $attributes = array('cn','distinguishedname'), $recursive = NULL)
+ {
+ if (!$this->adldap->getLdapBind()){ return false; }
+ if ($exchangeServer === NULL) { return "Missing compulsory field [exchangeServer]"; }
+ if ($recursive === NULL) { $recursive = $this->adldap->getRecursiveGroups(); }
+
+ $filter = '(&(objectCategory=msExchStorageGroup))';
+ $sr = @ldap_search($this->adldap->getLdapConnection(), $exchangeServer, $filter, $attributes);
+ $entries = @ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ if ($recursive === true) {
+ for ($i=0; $i<$entries['count']; $i++) {
+ $entries[$i]['msexchprivatemdb'] = $this->storageDatabases($entries[$i]['distinguishedname'][0]);
+ }
+ }
+
+ return $entries;
+ }
+
+ /**
+ * Returns a list of Databases within any given storage group in Exchange for a given mail server
+ *
+ * @param string $storageGroup The full DN of an Storage Group. You can use exchange_storage_groups() to find the DN
+ * @param array $attributes An array of the AD attributes you wish to return
+ * @return array
+ */
+ public function storageDatabases($storageGroup, $attributes = array('cn','distinguishedname','displayname')) {
+ if (!$this->adldap->getLdapBind()){ return false; }
+ if ($storageGroup === NULL) { return "Missing compulsory field [storageGroup]"; }
+
+ $filter = '(&(objectCategory=msExchPrivateMDB))';
+ $sr = @ldap_search($this->adldap->getLdapConnection(), $storageGroup, $filter, $attributes);
+ $entries = @ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+ return $entries;
+ }
+}
+?> \ No newline at end of file
diff --git a/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPFolders.php b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPFolders.php
new file mode 100644
index 0000000..67b1474
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPFolders.php
@@ -0,0 +1,179 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @subpackage Folders
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 97 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+ */
+require_once(dirname(__FILE__) . '/../adLDAP.php');
+
+/**
+* FOLDER / OU MANAGEMENT FUNCTIONS
+*/
+class adLDAPFolders {
+ /**
+ * The current adLDAP connection via dependency injection
+ *
+ * @var adLDAP
+ */
+ protected $adldap;
+
+ public function __construct(adLDAP $adldap) {
+ $this->adldap = $adldap;
+ }
+
+ /**
+ * Delete a distinguished name from Active Directory
+ * You should never need to call this yourself, just use the wrapper functions user_delete and contact_delete
+ *
+ * @param string $dn The distinguished name to delete
+ * @return bool
+ */
+ public function delete($dn){
+ $result = ldap_delete($this->adldap->getLdapConnection(), $dn);
+ if ($result != true) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns a folder listing for a specific OU
+ * See http://adldap.sourceforge.net/wiki/doku.php?id=api_folder_functions
+ *
+ * @param array $folderName An array to the OU you wish to list.
+ * If set to NULL will list the root, strongly recommended to set
+ * $recursive to false in that instance!
+ * @param string $dnType The type of record to list. This can be ADLDAP_FOLDER or ADLDAP_CONTAINER.
+ * @param bool $recursive Recursively search sub folders
+ * @param bool $type Specify a type of object to search for
+ * @return array
+ */
+ public function listing($folderName = NULL, $dnType = adLDAP::ADLDAP_FOLDER, $recursive = NULL, $type = NULL)
+ {
+ if ($recursive === NULL) { $recursive = $this->adldap->getRecursiveGroups(); } //use the default option if they haven't set it
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ $filter = '(&';
+ if ($type !== NULL) {
+ switch ($type) {
+ case 'contact':
+ $filter .= '(objectClass=contact)';
+ break;
+ case 'computer':
+ $filter .= '(objectClass=computer)';
+ break;
+ case 'group':
+ $filter .= '(objectClass=group)';
+ break;
+ case 'folder':
+ $filter .= '(objectClass=organizationalUnit)';
+ break;
+ case 'container':
+ $filter .= '(objectClass=container)';
+ break;
+ case 'domain':
+ $filter .= '(objectClass=builtinDomain)';
+ break;
+ default:
+ $filter .= '(objectClass=user)';
+ break;
+ }
+ }
+ else {
+ $filter .= '(objectClass=*)';
+ }
+ // If the folder name is null then we will search the root level of AD
+ // This requires us to not have an OU= part, just the base_dn
+ $searchOu = $this->adldap->getBaseDn();
+ if (is_array($folderName)) {
+ $ou = $dnType . "=" . implode("," . $dnType . "=", $folderName);
+ $filter .= '(!(distinguishedname=' . $ou . ',' . $this->adldap->getBaseDn() . ')))';
+ $searchOu = $ou . ',' . $this->adldap->getBaseDn();
+ }
+ else {
+ $filter .= '(!(distinguishedname=' . $this->adldap->getBaseDn() . ')))';
+ }
+
+ if ($recursive === true) {
+ $sr = ldap_search($this->adldap->getLdapConnection(), $searchOu, $filter, array('objectclass', 'distinguishedname', 'samaccountname'));
+ $entries = @ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+ if (is_array($entries)) {
+ return $entries;
+ }
+ }
+ else {
+ $sr = ldap_list($this->adldap->getLdapConnection(), $searchOu, $filter, array('objectclass', 'distinguishedname', 'samaccountname'));
+ $entries = @ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+ if (is_array($entries)) {
+ return $entries;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Create an organizational unit
+ *
+ * @param array $attributes Default attributes of the ou
+ * @return bool
+ */
+ public function create($attributes)
+ {
+ if (!is_array($attributes)){ return "Attributes must be an array"; }
+ if (!is_array($attributes["container"])) { return "Container attribute must be an array."; }
+ if (!array_key_exists("ou_name",$attributes)) { return "Missing compulsory field [ou_name]"; }
+ if (!array_key_exists("container",$attributes)) { return "Missing compulsory field [container]"; }
+
+ $attributes["container"] = array_reverse($attributes["container"]);
+
+ $add=array();
+ $add["objectClass"] = "organizationalUnit";
+ $add["OU"] = $attributes['ou_name'];
+ $containers = "";
+ if (count($attributes['container']) > 0) {
+ $containers = "OU=" . implode(",OU=", $attributes["container"]) . ",";
+ }
+
+ $containers = "OU=" . implode(",OU=", $attributes["container"]);
+ $result = ldap_add($this->adldap->getLdapConnection(), "OU=" . $add["OU"] . ", " . $containers . $this->adldap->getBaseDn(), $add);
+ if ($result != true) {
+ return false;
+ }
+
+ return true;
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPGroups.php b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPGroups.php
new file mode 100644
index 0000000..94bc048
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPGroups.php
@@ -0,0 +1,631 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @subpackage Groups
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 97 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+ */
+require_once(dirname(__FILE__) . '/../adLDAP.php');
+require_once(dirname(__FILE__) . '/../collections/adLDAPGroupCollection.php');
+
+/**
+* GROUP FUNCTIONS
+*/
+class adLDAPGroups {
+ /**
+ * The current adLDAP connection via dependency injection
+ *
+ * @var adLDAP
+ */
+ protected $adldap;
+
+ public function __construct(adLDAP $adldap) {
+ $this->adldap = $adldap;
+ }
+
+ /**
+ * Add a group to a group
+ *
+ * @param string $parent The parent group name
+ * @param string $child The child group name
+ * @return bool
+ */
+ public function addGroup($parent,$child){
+
+ // Find the parent group's dn
+ $parentGroup = $this->ginfo($parent, array("cn"));
+ if ($parentGroup[0]["dn"] === NULL){
+ return false;
+ }
+ $parentDn = $parentGroup[0]["dn"];
+
+ // Find the child group's dn
+ $childGroup = $this->info($child, array("cn"));
+ if ($childGroup[0]["dn"] === NULL){
+ return false;
+ }
+ $childDn = $childGroup[0]["dn"];
+
+ $add = array();
+ $add["member"] = $childDn;
+
+ $result = @ldap_mod_add($this->adldap->getLdapConnection(), $parentDn, $add);
+ if ($result == false) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Add a user to a group
+ *
+ * @param string $group The group to add the user to
+ * @param string $user The user to add to the group
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return bool
+ */
+ public function addUser($group, $user, $isGUID = false)
+ {
+ // Adding a user is a bit fiddly, we need to get the full DN of the user
+ // and add it using the full DN of the group
+
+ // Find the user's dn
+ $userDn = $this->adldap->user()->dn($user, $isGUID);
+ if ($userDn === false) {
+ return false;
+ }
+
+ // Find the group's dn
+ $groupInfo = $this->info($group, array("cn"));
+ if ($groupInfo[0]["dn"] === NULL) {
+ return false;
+ }
+ $groupDn = $groupInfo[0]["dn"];
+
+ $add = array();
+ $add["member"] = $userDn;
+
+ $result = @ldap_mod_add($this->adldap->getLdapConnection(), $groupDn, $add);
+ if ($result == false) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Add a contact to a group
+ *
+ * @param string $group The group to add the contact to
+ * @param string $contactDn The DN of the contact to add
+ * @return bool
+ */
+ public function addContact($group, $contactDn)
+ {
+ // To add a contact we take the contact's DN
+ // and add it using the full DN of the group
+
+ // Find the group's dn
+ $groupInfo = $this->info($group, array("cn"));
+ if ($groupInfo[0]["dn"] === NULL) {
+ return false;
+ }
+ $groupDn = $groupInfo[0]["dn"];
+
+ $add = array();
+ $add["member"] = $contactDn;
+
+ $result = @ldap_mod_add($this->adldap->getLdapConnection(), $groupDn, $add);
+ if ($result == false) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Create a group
+ *
+ * @param array $attributes Default attributes of the group
+ * @return bool
+ */
+ public function create($attributes)
+ {
+ if (!is_array($attributes)){ return "Attributes must be an array"; }
+ if (!array_key_exists("group_name", $attributes)){ return "Missing compulsory field [group_name]"; }
+ if (!array_key_exists("container", $attributes)){ return "Missing compulsory field [container]"; }
+ if (!array_key_exists("description", $attributes)){ return "Missing compulsory field [description]"; }
+ if (!is_array($attributes["container"])){ return "Container attribute must be an array."; }
+ $attributes["container"] = array_reverse($attributes["container"]);
+
+ //$member_array = array();
+ //$member_array[0] = "cn=user1,cn=Users,dc=yourdomain,dc=com";
+ //$member_array[1] = "cn=administrator,cn=Users,dc=yourdomain,dc=com";
+
+ $add = array();
+ $add["cn"] = $attributes["group_name"];
+ $add["samaccountname"] = $attributes["group_name"];
+ $add["objectClass"] = "Group";
+ $add["description"] = $attributes["description"];
+ //$add["member"] = $member_array; UNTESTED
+
+ $container = "OU=" . implode(",OU=", $attributes["container"]);
+ $result = ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"] . ", " . $container . "," . $this->adldap->getBaseDn(), $add);
+ if ($result != true) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Delete a group account
+ *
+ * @param string $group The group to delete (please be careful here!)
+ *
+ * @return array
+ */
+ public function delete($group) {
+ if (!$this->adldap->getLdapBind()){ return false; }
+ if ($group === null){ return "Missing compulsory field [group]"; }
+
+ $groupInfo = $this->info($group, array("*"));
+ $dn = $groupInfo[0]['distinguishedname'][0];
+ $result = $this->adldap->folder()->delete($dn);
+ if ($result !== true) {
+ return false;
+ } return true;
+ }
+
+ /**
+ * Remove a group from a group
+ *
+ * @param string $parent The parent group name
+ * @param string $child The child group name
+ * @return bool
+ */
+ public function removeGroup($parent , $child)
+ {
+
+ // Find the parent dn
+ $parentGroup = $this->info($parent, array("cn"));
+ if ($parentGroup[0]["dn"] === NULL) {
+ return false;
+ }
+ $parentDn = $parentGroup[0]["dn"];
+
+ // Find the child dn
+ $childGroup = $this->info($child, array("cn"));
+ if ($childGroup[0]["dn"] === NULL) {
+ return false;
+ }
+ $childDn = $childGroup[0]["dn"];
+
+ $del = array();
+ $del["member"] = $childDn;
+
+ $result = @ldap_mod_del($this->adldap->getLdapConnection(), $parentDn, $del);
+ if ($result == false) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Remove a user from a group
+ *
+ * @param string $group The group to remove a user from
+ * @param string $user The AD user to remove from the group
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return bool
+ */
+ public function removeUser($group, $user, $isGUID = false)
+ {
+
+ // Find the parent dn
+ $groupInfo = $this->info($group, array("cn"));
+ if ($groupInfo[0]["dn"] === NULL){
+ return false;
+ }
+ $groupDn = $groupInfo[0]["dn"];
+
+ // Find the users dn
+ $userDn = $this->adldap->user()->dn($user, $isGUID);
+ if ($userDn === false) {
+ return false;
+ }
+
+ $del = array();
+ $del["member"] = $userDn;
+
+ $result = @ldap_mod_del($this->adldap->getLdapConnection(), $groupDn, $del);
+ if ($result == false) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Remove a contact from a group
+ *
+ * @param string $group The group to remove a user from
+ * @param string $contactDn The DN of a contact to remove from the group
+ * @return bool
+ */
+ public function removeContact($group, $contactDn)
+ {
+
+ // Find the parent dn
+ $groupInfo = $this->info($group, array("cn"));
+ if ($groupInfo[0]["dn"] === NULL) {
+ return false;
+ }
+ $groupDn = $groupInfo[0]["dn"];
+
+ $del = array();
+ $del["member"] = $contactDn;
+
+ $result = @ldap_mod_del($this->adldap->getLdapConnection(), $groupDn, $del);
+ if ($result == false) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return a list of groups in a group
+ *
+ * @param string $group The group to query
+ * @param bool $recursive Recursively get groups
+ * @return array
+ */
+ public function inGroup($group, $recursive = NULL)
+ {
+ if (!$this->adldap->getLdapBind()){ return false; }
+ if ($recursive === NULL){ $recursive = $this->adldap->getRecursiveGroups(); } // Use the default option if they haven't set it
+
+ // Search the directory for the members of a group
+ $info = $this->info($group, array("member","cn"));
+ $groups = $info[0]["member"];
+ if (!is_array($groups)) {
+ return false;
+ }
+
+ $groupArray = array();
+
+ for ($i=0; $i<$groups["count"]; $i++){
+ $filter = "(&(objectCategory=group)(distinguishedName=" . $this->adldap->utilities()->ldapSlashes($groups[$i]) . "))";
+ $fields = array("samaccountname", "distinguishedname", "objectClass");
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ // not a person, look for a group
+ if ($entries['count'] == 0 && $recursive == true) {
+ $filter = "(&(objectCategory=group)(distinguishedName=" . $this->adldap->utilities()->ldapSlashes($groups[$i]) . "))";
+ $fields = array("distinguishedname");
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+ if (!isset($entries[0]['distinguishedname'][0])) {
+ continue;
+ }
+ $subGroups = $this->inGroup($entries[0]['distinguishedname'][0], $recursive);
+ if (is_array($subGroups)) {
+ $groupArray = array_merge($groupArray, $subGroups);
+ $groupArray = array_unique($groupArray);
+ }
+ continue;
+ }
+
+ $groupArray[] = $entries[0]['distinguishedname'][0];
+ }
+ return $groupArray;
+ }
+
+ /**
+ * Return a list of members in a group
+ *
+ * @param string $group The group to query
+ * @param bool $recursive Recursively get group members
+ * @return array
+ */
+ public function members($group, $recursive = NULL)
+ {
+ if (!$this->adldap->getLdapBind()){ return false; }
+ if ($recursive === NULL){ $recursive = $this->adldap->getRecursiveGroups(); } // Use the default option if they haven't set it
+ // Search the directory for the members of a group
+ $info = $this->info($group, array("member","cn"));
+ $users = $info[0]["member"];
+ if (!is_array($users)) {
+ return false;
+ }
+
+ $userArray = array();
+
+ for ($i=0; $i<$users["count"]; $i++){
+ $filter = "(&(objectCategory=person)(distinguishedName=" . $this->adldap->utilities()->ldapSlashes($users[$i]) . "))";
+ $fields = array("samaccountname", "distinguishedname", "objectClass");
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ // not a person, look for a group
+ if ($entries['count'] == 0 && $recursive == true) {
+ $filter = "(&(objectCategory=group)(distinguishedName=" . $this->adldap->utilities()->ldapSlashes($users[$i]) . "))";
+ $fields = array("samaccountname");
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+ if (!isset($entries[0]['samaccountname'][0])) {
+ continue;
+ }
+ $subUsers = $this->members($entries[0]['samaccountname'][0], $recursive);
+ if (is_array($subUsers)) {
+ $userArray = array_merge($userArray, $subUsers);
+ $userArray = array_unique($userArray);
+ }
+ continue;
+ }
+ else if ($entries['count'] == 0) {
+ continue;
+ }
+
+ if ((!isset($entries[0]['samaccountname'][0]) || $entries[0]['samaccountname'][0] === NULL) && $entries[0]['distinguishedname'][0] !== NULL) {
+ $userArray[] = $entries[0]['distinguishedname'][0];
+ }
+ else if ($entries[0]['samaccountname'][0] !== NULL) {
+ $userArray[] = $entries[0]['samaccountname'][0];
+ }
+ }
+ return $userArray;
+ }
+
+ /**
+ * Group Information. Returns an array of raw information about a group.
+ * The group name is case sensitive
+ *
+ * @param string $groupName The group name to retrieve info about
+ * @param array $fields Fields to retrieve
+ * @return array
+ */
+ public function info($groupName, $fields = NULL)
+ {
+ if ($groupName === NULL) { return false; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ if (stristr($groupName, '+')) {
+ $groupName = stripslashes($groupName);
+ }
+
+ $filter = "(&(objectCategory=group)(name=" . $this->adldap->utilities()->ldapSlashes($groupName) . "))";
+ if ($fields === NULL) {
+ $fields = array("member","memberof","cn","description","distinguishedname","objectcategory","samaccountname");
+ }
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ return $entries;
+ }
+
+ /**
+ * Group Information. Returns an collection
+ * The group name is case sensitive
+ *
+ * @param string $groupName The group name to retrieve info about
+ * @param array $fields Fields to retrieve
+ * @return adLDAPGroupCollection
+ */
+ public function infoCollection($groupName, $fields = NULL)
+ {
+ if ($groupName === NULL) { return false; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ $info = $this->info($groupName, $fields);
+ if ($info !== false) {
+ $collection = new adLDAPGroupCollection($info, $this->adldap);
+ return $collection;
+ }
+ return false;
+ }
+
+ /**
+ * Return a complete list of "groups in groups"
+ *
+ * @param string $group The group to get the list from
+ * @return array
+ */
+ public function recursiveGroups($group)
+ {
+ if ($group === NULL) { return false; }
+
+ $stack = array();
+ $processed = array();
+ $retGroups = array();
+
+ array_push($stack, $group); // Initial Group to Start with
+ while (count($stack) > 0) {
+ $parent = array_pop($stack);
+ array_push($processed, $parent);
+
+ $info = $this->info($parent, array("memberof"));
+
+ if (isset($info[0]["memberof"]) && is_array($info[0]["memberof"])) {
+ $groups = $info[0]["memberof"];
+ if ($groups) {
+ $groupNames = $this->adldap->utilities()->niceNames($groups);
+ $retGroups = array_merge($retGroups, $groupNames); //final groups to return
+ foreach ($groupNames as $id => $groupName) {
+ if (!in_array($groupName, $processed)) {
+ array_push($stack, $groupName);
+ }
+ }
+ }
+ }
+ }
+
+ return $retGroups;
+ }
+
+ /**
+ * Returns a complete list of the groups in AD based on a SAM Account Type
+ *
+ * @param string $sAMAaccountType The account type to return
+ * @param bool $includeDescription Whether to return a description
+ * @param string $search Search parameters
+ * @param bool $sorted Whether to sort the results
+ * @return array
+ */
+ public function search($sAMAaccountType = adLDAP::ADLDAP_SECURITY_GLOBAL_GROUP, $includeDescription = false, $search = "*", $sorted = true) {
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ $filter = '(&(objectCategory=group)';
+ if ($sAMAaccountType !== null) {
+ $filter .= '(samaccounttype='. $sAMAaccountType .')';
+ }
+ $filter .= '(cn=' . $search . '))';
+ // Perform the search and grab all their details
+ $fields = array("samaccountname", "description");
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ $groupsArray = array();
+ for ($i=0; $i<$entries["count"]; $i++){
+ if ($includeDescription && strlen($entries[$i]["description"][0]) > 0 ) {
+ $groupsArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["description"][0];
+ }
+ else if ($includeDescription){
+ $groupsArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["samaccountname"][0];
+ }
+ else {
+ array_push($groupsArray, $entries[$i]["samaccountname"][0]);
+ }
+ }
+ if ($sorted) {
+ asort($groupsArray);
+ }
+ return $groupsArray;
+ }
+
+ /**
+ * Returns a complete list of all groups in AD
+ *
+ * @param bool $includeDescription Whether to return a description
+ * @param string $search Search parameters
+ * @param bool $sorted Whether to sort the results
+ * @return array
+ */
+ public function all($includeDescription = false, $search = "*", $sorted = true){
+ $groupsArray = $this->search(null, $includeDescription, $search, $sorted);
+ return $groupsArray;
+ }
+
+ /**
+ * Returns a complete list of security groups in AD
+ *
+ * @param bool $includeDescription Whether to return a description
+ * @param string $search Search parameters
+ * @param bool $sorted Whether to sort the results
+ * @return array
+ */
+ public function allSecurity($includeDescription = false, $search = "*", $sorted = true){
+ $groupsArray = $this->search(adLDAP::ADLDAP_SECURITY_GLOBAL_GROUP, $includeDescription, $search, $sorted);
+ return $groupsArray;
+ }
+
+ /**
+ * Returns a complete list of distribution lists in AD
+ *
+ * @param bool $includeDescription Whether to return a description
+ * @param string $search Search parameters
+ * @param bool $sorted Whether to sort the results
+ * @return array
+ */
+ public function allDistribution($includeDescription = false, $search = "*", $sorted = true){
+ $groupsArray = $this->search(adLDAP::ADLDAP_DISTRIBUTION_GROUP, $includeDescription, $search, $sorted);
+ return $groupsArray;
+ }
+
+ /**
+ * Coping with AD not returning the primary group
+ * http://support.microsoft.com/?kbid=321360
+ *
+ * This is a re-write based on code submitted by Bruce which prevents the
+ * need to search each security group to find the true primary group
+ *
+ * @param string $gid Group ID
+ * @param string $usersid User's Object SID
+ * @return mixed
+ */
+ public function getPrimaryGroup($gid, $usersid)
+ {
+ if ($gid === NULL || $usersid === NULL) { return false; }
+ $sr = false;
+
+ $gsid = substr_replace($usersid, pack('V',$gid), strlen($usersid)-4,4);
+ $filter = '(objectsid=' . $this->adldap->utilities()->getTextSID($gsid).')';
+ $fields = array("samaccountname","distinguishedname");
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ if (isset($entries[0]['distinguishedname'][0])) {
+ return $entries[0]['distinguishedname'][0];
+ }
+ return false;
+ }
+
+ /**
+ * Coping with AD not returning the primary group
+ * http://support.microsoft.com/?kbid=321360
+ *
+ * For some reason it's not possible to search on primarygrouptoken=XXX
+ * If someone can show otherwise, I'd like to know about it :)
+ * this way is resource intensive and generally a pain in the @#%^
+ *
+ * @deprecated deprecated since version 3.1, see get get_primary_group
+ * @param string $gid Group ID
+ * @return string
+ */
+ public function cn($gid){
+ if ($gid === NULL) { return false; }
+ $sr = false;
+ $r = '';
+
+ $filter = "(&(objectCategory=group)(samaccounttype=" . adLDAP::ADLDAP_SECURITY_GLOBAL_GROUP . "))";
+ $fields = array("primarygrouptoken", "samaccountname", "distinguishedname");
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ for ($i=0; $i<$entries["count"]; $i++){
+ if ($entries[$i]["primarygrouptoken"][0] == $gid) {
+ $r = $entries[$i]["distinguishedname"][0];
+ $i = $entries["count"];
+ }
+ }
+
+ return $r;
+ }
+}
+?>
diff --git a/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPUsers.php b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPUsers.php
new file mode 100644
index 0000000..dc3ebd7
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPUsers.php
@@ -0,0 +1,682 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @subpackage User
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 97 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+ */
+require_once(dirname(__FILE__) . '/../adLDAP.php');
+require_once(dirname(__FILE__) . '/../collections/adLDAPUserCollection.php');
+
+/**
+* USER FUNCTIONS
+*/
+class adLDAPUsers {
+ /**
+ * The current adLDAP connection via dependency injection
+ *
+ * @var adLDAP
+ */
+ protected $adldap;
+
+ public function __construct(adLDAP $adldap) {
+ $this->adldap = $adldap;
+ }
+
+ /**
+ * Validate a user's login credentials
+ *
+ * @param string $username A user's AD username
+ * @param string $password A user's AD password
+ * @param bool optional $prevent_rebind
+ * @return bool
+ */
+ public function authenticate($username, $password, $preventRebind = false) {
+ return $this->adldap->authenticate($username, $password, $preventRebind);
+ }
+
+ /**
+ * Create a user
+ *
+ * If you specify a password here, this can only be performed over SSL
+ *
+ * @param array $attributes The attributes to set to the user account
+ * @return bool
+ */
+ public function create($attributes)
+ {
+ // Check for compulsory fields
+ if (!array_key_exists("username", $attributes)){ return "Missing compulsory field [username]"; }
+ if (!array_key_exists("firstname", $attributes)){ return "Missing compulsory field [firstname]"; }
+ if (!array_key_exists("surname", $attributes)){ return "Missing compulsory field [surname]"; }
+ if (!array_key_exists("email", $attributes)){ return "Missing compulsory field [email]"; }
+ if (!array_key_exists("container", $attributes)){ return "Missing compulsory field [container]"; }
+ if (!is_array($attributes["container"])){ return "Container attribute must be an array."; }
+
+ if (array_key_exists("password",$attributes) && (!$this->adldap->getUseSSL() && !$this->adldap->getUseTLS())){
+ throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.');
+ }
+
+ if (!array_key_exists("display_name", $attributes)) {
+ $attributes["display_name"] = $attributes["firstname"] . " " . $attributes["surname"];
+ }
+
+ // Translate the schema
+ $add = $this->adldap->adldap_schema($attributes);
+
+ // Additional stuff only used for adding accounts
+ $add["cn"][0] = $attributes["display_name"];
+ $add["samaccountname"][0] = $attributes["username"];
+ $add["objectclass"][0] = "top";
+ $add["objectclass"][1] = "person";
+ $add["objectclass"][2] = "organizationalPerson";
+ $add["objectclass"][3] = "user"; //person?
+ //$add["name"][0]=$attributes["firstname"]." ".$attributes["surname"];
+
+ // Set the account control attribute
+ $control_options = array("NORMAL_ACCOUNT");
+ if (!$attributes["enabled"]) {
+ $control_options[] = "ACCOUNTDISABLE";
+ }
+ $add["userAccountControl"][0] = $this->accountControl($control_options);
+
+ // Determine the container
+ $attributes["container"] = array_reverse($attributes["container"]);
+ $container = "OU=" . implode(", OU=",$attributes["container"]);
+
+ // Add the entry
+ $result = @ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"][0] . ", " . $container . "," . $this->adldap->getBaseDn(), $add);
+ if ($result != true) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Account control options
+ *
+ * @param array $options The options to convert to int
+ * @return int
+ */
+ protected function accountControl($options)
+ {
+ $val=0;
+
+ if (is_array($options)) {
+ if (in_array("SCRIPT",$options)){ $val=$val+1; }
+ if (in_array("ACCOUNTDISABLE",$options)){ $val=$val+2; }
+ if (in_array("HOMEDIR_REQUIRED",$options)){ $val=$val+8; }
+ if (in_array("LOCKOUT",$options)){ $val=$val+16; }
+ if (in_array("PASSWD_NOTREQD",$options)){ $val=$val+32; }
+ //PASSWD_CANT_CHANGE Note You cannot assign this permission by directly modifying the UserAccountControl attribute.
+ //For information about how to set the permission programmatically, see the "Property flag descriptions" section.
+ if (in_array("ENCRYPTED_TEXT_PWD_ALLOWED",$options)){ $val=$val+128; }
+ if (in_array("TEMP_DUPLICATE_ACCOUNT",$options)){ $val=$val+256; }
+ if (in_array("NORMAL_ACCOUNT",$options)){ $val=$val+512; }
+ if (in_array("INTERDOMAIN_TRUST_ACCOUNT",$options)){ $val=$val+2048; }
+ if (in_array("WORKSTATION_TRUST_ACCOUNT",$options)){ $val=$val+4096; }
+ if (in_array("SERVER_TRUST_ACCOUNT",$options)){ $val=$val+8192; }
+ if (in_array("DONT_EXPIRE_PASSWORD",$options)){ $val=$val+65536; }
+ if (in_array("MNS_LOGON_ACCOUNT",$options)){ $val=$val+131072; }
+ if (in_array("SMARTCARD_REQUIRED",$options)){ $val=$val+262144; }
+ if (in_array("TRUSTED_FOR_DELEGATION",$options)){ $val=$val+524288; }
+ if (in_array("NOT_DELEGATED",$options)){ $val=$val+1048576; }
+ if (in_array("USE_DES_KEY_ONLY",$options)){ $val=$val+2097152; }
+ if (in_array("DONT_REQ_PREAUTH",$options)){ $val=$val+4194304; }
+ if (in_array("PASSWORD_EXPIRED",$options)){ $val=$val+8388608; }
+ if (in_array("TRUSTED_TO_AUTH_FOR_DELEGATION",$options)){ $val=$val+16777216; }
+ }
+ return $val;
+ }
+
+ /**
+ * Delete a user account
+ *
+ * @param string $username The username to delete (please be careful here!)
+ * @param bool $isGUID Is the username a GUID or a samAccountName
+ * @return array
+ */
+ public function delete($username, $isGUID = false)
+ {
+ $userinfo = $this->info($username, array("*"), $isGUID);
+ $dn = $userinfo[0]['distinguishedname'][0];
+ $result = $this->adldap->folder()->delete($dn);
+ if ($result != true) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Groups the user is a member of
+ *
+ * @param string $username The username to query
+ * @param bool $recursive Recursive list of groups
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return array
+ */
+ public function groups($username, $recursive = NULL, $isGUID = false)
+ {
+ if ($username === NULL) { return false; }
+ if ($recursive === NULL) { $recursive = $this->adldap->getRecursiveGroups(); } // Use the default option if they haven't set it
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ // Search the directory for their information
+ $info = @$this->info($username, array("memberof", "primarygroupid"), $isGUID);
+ $groups = $this->adldap->utilities()->niceNames($info[0]["memberof"]); // Presuming the entry returned is our guy (unique usernames)
+
+ if ($recursive === true){
+ foreach ($groups as $id => $groupName){
+ $extraGroups = $this->adldap->group()->recursiveGroups($groupName);
+ $groups = array_merge($groups, $extraGroups);
+ }
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Find information about the users. Returned in a raw array format from AD
+ *
+ * @param string $username The username to query
+ * @param array $fields Array of parameters to query
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return array
+ */
+ public function info($username, $fields = NULL, $isGUID = false)
+ {
+ if ($username === NULL) { return false; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ if ($isGUID === true) {
+ $username = $this->adldap->utilities()->strGuidToHex($username);
+ $filter = "objectguid=" . $username;
+ }
+ else if (strstr($username, "@")) {
+ $filter = "userPrincipalName=" . $username;
+ }
+ else {
+ $filter = "samaccountname=" . $username;
+ }
+ $filter = "(&(objectCategory=person)({$filter}))";
+ if ($fields === NULL) {
+ $fields = array("samaccountname","mail","memberof","department","displayname","telephonenumber","primarygroupid","objectsid");
+ }
+ if (!in_array("objectsid", $fields)) {
+ $fields[] = "objectsid";
+ }
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ if (isset($entries[0])) {
+ if ($entries[0]['count'] >= 1) {
+ if (in_array("memberof", $fields)) {
+ // AD does not return the primary group in the ldap query, we may need to fudge it
+ if ($this->adldap->getRealPrimaryGroup() && isset($entries[0]["primarygroupid"][0]) && isset($entries[0]["objectsid"][0])){
+ //$entries[0]["memberof"][]=$this->group_cn($entries[0]["primarygroupid"][0]);
+ $entries[0]["memberof"][] = $this->adldap->group()->getPrimaryGroup($entries[0]["primarygroupid"][0], $entries[0]["objectsid"][0]);
+ } else {
+ $entries[0]["memberof"][] = "CN=Domain Users,CN=Users," . $this->adldap->getBaseDn();
+ }
+ if (!isset($entries[0]["memberof"]["count"])) {
+ $entries[0]["memberof"]["count"] = 0;
+ }
+ $entries[0]["memberof"]["count"]++;
+ }
+ }
+
+ return $entries;
+ }
+ return false;
+ }
+
+ /**
+ * Find information about the users. Returned in a raw array format from AD
+ *
+ * @param string $username The username to query
+ * @param array $fields Array of parameters to query
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return mixed
+ */
+ public function infoCollection($username, $fields = NULL, $isGUID = false)
+ {
+ if ($username === NULL) { return false; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ $info = $this->info($username, $fields, $isGUID);
+
+ if ($info !== false) {
+ $collection = new adLDAPUserCollection($info, $this->adldap);
+ return $collection;
+ }
+ return false;
+ }
+
+ /**
+ * Determine if a user is in a specific group
+ *
+ * @param string $username The username to query
+ * @param string $group The name of the group to check against
+ * @param bool $recursive Check groups recursively
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return bool
+ */
+ public function inGroup($username, $group, $recursive = NULL, $isGUID = false)
+ {
+ if ($username === NULL) { return false; }
+ if ($group === NULL) { return false; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+ if ($recursive === NULL) { $recursive = $this->adldap->getRecursiveGroups(); } // Use the default option if they haven't set it
+
+ // Get a list of the groups
+ $groups = $this->groups($username, $recursive, $isGUID);
+
+ // Return true if the specified group is in the group list
+ if (in_array($group, $groups)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine a user's password expiry date
+ *
+ * @param string $username The username to query
+ * @param book $isGUID Is the username passed a GUID or a samAccountName
+ * @requires bcmath http://php.net/manual/en/book.bc.php
+ * @return array
+ */
+ public function passwordExpiry($username, $isGUID = false)
+ {
+ if ($username === NULL) { return "Missing compulsory field [username]"; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+ if (!function_exists('bcmod')) { throw new adLDAPException("Missing function support [bcmod] http://php.net/manual/en/book.bc.php"); };
+
+ $userInfo = $this->info($username, array("pwdlastset", "useraccountcontrol"), $isGUID);
+ $pwdLastSet = $userInfo[0]['pwdlastset'][0];
+ $status = array();
+
+ if ($userInfo[0]['useraccountcontrol'][0] == '66048') {
+ // Password does not expire
+ return "Does not expire";
+ }
+ if ($pwdLastSet === '0') {
+ // Password has already expired
+ return "Password has expired";
+ }
+
+ // Password expiry in AD can be calculated from TWO values:
+ // - User's own pwdLastSet attribute: stores the last time the password was changed
+ // - Domain's maxPwdAge attribute: how long passwords last in the domain
+ //
+ // Although Microsoft chose to use a different base and unit for time measurements.
+ // This function will convert them to Unix timestamps
+ $sr = ldap_read($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), 'objectclass=*', array('maxPwdAge'));
+ if (!$sr) {
+ return false;
+ }
+ $info = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+ $maxPwdAge = $info[0]['maxpwdage'][0];
+
+
+ // See MSDN: http://msdn.microsoft.com/en-us/library/ms974598.aspx
+ //
+ // pwdLastSet contains the number of 100 nanosecond intervals since January 1, 1601 (UTC),
+ // stored in a 64 bit integer.
+ //
+ // The number of seconds between this date and Unix epoch is 11644473600.
+ //
+ // maxPwdAge is stored as a large integer that represents the number of 100 nanosecond
+ // intervals from the time the password was set before the password expires.
+ //
+ // We also need to scale this to seconds but also this value is a _negative_ quantity!
+ //
+ // If the low 32 bits of maxPwdAge are equal to 0 passwords do not expire
+ //
+ // Unfortunately the maths involved are too big for PHP integers, so I've had to require
+ // BCMath functions to work with arbitrary precision numbers.
+ if (bcmod($maxPwdAge, 4294967296) === '0') {
+ return "Domain does not expire passwords";
+ }
+
+ // Add maxpwdage and pwdlastset and we get password expiration time in Microsoft's
+ // time units. Because maxpwd age is negative we need to subtract it.
+ $pwdExpire = bcsub($pwdLastSet, $maxPwdAge);
+
+ // Convert MS's time to Unix time
+ $status['expiryts'] = bcsub(bcdiv($pwdExpire, '10000000'), '11644473600');
+ $status['expiryformat'] = date('Y-m-d H:i:s', bcsub(bcdiv($pwdExpire, '10000000'), '11644473600'));
+
+ return $status;
+ }
+
+ /**
+ * Modify a user
+ *
+ * @param string $username The username to query
+ * @param array $attributes The attributes to modify. Note if you set the enabled attribute you must not specify any other attributes
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return bool
+ */
+ public function modify($username, $attributes, $isGUID = false)
+ {
+ if ($username === NULL) { return "Missing compulsory field [username]"; }
+ if (array_key_exists("password", $attributes) && !$this->adldap->getUseSSL() && !$this->adldap->getUseTLS()) {
+ throw new adLDAPException('SSL/TLS must be configured on your webserver and enabled in the class to set passwords.');
+ }
+
+ // Find the dn of the user
+ $userDn = $this->dn($username, $isGUID);
+ if ($userDn === false) {
+ return false;
+ }
+
+ // Translate the update to the LDAP schema
+ $mod = $this->adldap->adldap_schema($attributes);
+
+ // Check to see if this is an enabled status update
+ if (!$mod && !array_key_exists("enabled", $attributes)){
+ return false;
+ }
+
+ // Set the account control attribute (only if specified)
+ if (array_key_exists("enabled", $attributes)){
+ if ($attributes["enabled"]){
+ $controlOptions = array("NORMAL_ACCOUNT");
+ }
+ else {
+ $controlOptions = array("NORMAL_ACCOUNT", "ACCOUNTDISABLE");
+ }
+ $mod["userAccountControl"][0] = $this->accountControl($controlOptions);
+ }
+
+ // Do the update
+ $result = @ldap_modify($this->adldap->getLdapConnection(), $userDn, $mod);
+ if ($result == false) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Disable a user account
+ *
+ * @param string $username The username to disable
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return bool
+ */
+ public function disable($username, $isGUID = false)
+ {
+ if ($username === NULL) { return "Missing compulsory field [username]"; }
+ $attributes = array("enabled" => 0);
+ $result = $this->modify($username, $attributes, $isGUID);
+ if ($result == false) { return false; }
+
+ return true;
+ }
+
+ /**
+ * Enable a user account
+ *
+ * @param string $username The username to enable
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return bool
+ */
+ public function enable($username, $isGUID = false)
+ {
+ if ($username === NULL) { return "Missing compulsory field [username]"; }
+ $attributes = array("enabled" => 1);
+ $result = $this->modify($username, $attributes, $isGUID);
+ if ($result == false) { return false; }
+
+ return true;
+ }
+
+ /**
+ * Set the password of a user - This must be performed over SSL
+ *
+ * @param string $username The username to modify
+ * @param string $password The new password
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return bool
+ */
+ public function password($username, $password, $isGUID = false)
+ {
+ if ($username === NULL) { return false; }
+ if ($password === NULL) { return false; }
+ if (!$this->adldap->getLdapBind()) { return false; }
+ if (!$this->adldap->getUseSSL() && !$this->adldap->getUseTLS()) {
+ throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.');
+ }
+
+ $userDn = $this->dn($username, $isGUID);
+ if ($userDn === false) {
+ return false;
+ }
+
+ $add=array();
+ $add["unicodePwd"][0] = $this->encodePassword($password);
+
+ $result = @ldap_mod_replace($this->adldap->getLdapConnection(), $userDn, $add);
+ if ($result === false){
+ $err = ldap_errno($this->adldap->getLdapConnection());
+ if ($err) {
+ $msg = 'Error ' . $err . ': ' . ldap_err2str($err) . '.';
+ if($err == 53) {
+ $msg .= ' Your password might not match the password policy.';
+ }
+ throw new adLDAPException($msg);
+ }
+ else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Encode a password for transmission over LDAP
+ *
+ * @param string $password The password to encode
+ * @return string
+ */
+ public function encodePassword($password)
+ {
+ $password="\"".$password."\"";
+ $encoded="";
+ for ($i=0; $i <strlen($password); $i++){ $encoded.="{$password{$i}}\000"; }
+ return $encoded;
+ }
+
+ /**
+ * Obtain the user's distinguished name based on their userid
+ *
+ *
+ * @param string $username The username
+ * @param bool $isGUID Is the username passed a GUID or a samAccountName
+ * @return string
+ */
+ public function dn($username, $isGUID=false)
+ {
+ $user = $this->info($username, array("cn"), $isGUID);
+ if ($user[0]["dn"] === NULL) {
+ return false;
+ }
+ $userDn = $user[0]["dn"];
+ return $userDn;
+ }
+
+ /**
+ * Return a list of all users in AD
+ *
+ * @param bool $includeDescription Return a description of the user
+ * @param string $search Search parameter
+ * @param bool $sorted Sort the user accounts
+ * @return array
+ */
+ public function all($includeDescription = false, $search = "*", $sorted = true)
+ {
+ if (!$this->adldap->getLdapBind()) { return false; }
+
+ // Perform the search and grab all their details
+ $filter = "(&(objectClass=user)(samaccounttype=" . adLDAP::ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)(cn=" . $search . "))";
+ $fields = array("samaccountname","displayname");
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ $usersArray = array();
+ for ($i=0; $i<$entries["count"]; $i++){
+ if ($includeDescription && strlen($entries[$i]["displayname"][0])>0){
+ $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["displayname"][0];
+ } elseif ($includeDescription){
+ $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["samaccountname"][0];
+ } else {
+ array_push($usersArray, $entries[$i]["samaccountname"][0]);
+ }
+ }
+ if ($sorted) {
+ asort($usersArray);
+ }
+ return $usersArray;
+ }
+
+ /**
+ * Converts a username (samAccountName) to a GUID
+ *
+ * @param string $username The username to query
+ * @return string
+ */
+ public function usernameToGuid($username)
+ {
+ if (!$this->adldap->getLdapBind()){ return false; }
+ if ($username === null){ return "Missing compulsory field [username]"; }
+
+ $filter = "samaccountname=" . $username;
+ $fields = array("objectGUID");
+ $sr = @ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ if (ldap_count_entries($this->adldap->getLdapConnection(), $sr) > 0) {
+ $entry = @ldap_first_entry($this->adldap->getLdapConnection(), $sr);
+ $guid = @ldap_get_values_len($this->adldap->getLdapConnection(), $entry, 'objectGUID');
+ $strGUID = $this->adldap->utilities()->binaryToText($guid[0]);
+ return $strGUID;
+ }
+ return false;
+ }
+
+ /**
+ * Return a list of all users in AD that have a specific value in a field
+ *
+ * @param bool $includeDescription Return a description of the user
+ * @param string $searchField Field to search search for
+ * @param string $searchFilter Value to search for in the specified field
+ * @param bool $sorted Sort the user accounts
+ * @return array
+ */
+ public function find($includeDescription = false, $searchField = false, $searchFilter = false, $sorted = true){
+ if (!$this->adldap->getLdapBind()){ return false; }
+
+ // Perform the search and grab all their details
+ $searchParams = "";
+ if ($searchField) {
+ $searchParams = "(" . $searchField . "=" . $searchFilter . ")";
+ }
+ $filter = "(&(objectClass=user)(samaccounttype=" . adLDAP::ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)" . $searchParams . ")";
+ $fields = array("samaccountname","displayname");
+ $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields);
+ $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr);
+
+ $usersArray = array();
+ for ($i=0; $i < $entries["count"]; $i++) {
+ if ($includeDescription && strlen($entries[$i]["displayname"][0]) > 0) {
+ $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["displayname"][0];
+ }
+ else if ($includeDescription) {
+ $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["samaccountname"][0];
+ }
+ else {
+ array_push($usersArray, $entries[$i]["samaccountname"][0]);
+ }
+ }
+ if ($sorted){
+ asort($usersArray);
+ }
+ return ($usersArray);
+ }
+
+ /**
+ * Move a user account to a different OU
+ *
+ * @param string $username The username to move (please be careful here!)
+ * @param array $container The container or containers to move the user to (please be careful here!).
+ * accepts containers in 1. parent 2. child order
+ * @return array
+ */
+ public function move($username, $container)
+ {
+ if (!$this->adldap->getLdapBind()) { return false; }
+ if ($username === null) { return "Missing compulsory field [username]"; }
+ if ($container === null) { return "Missing compulsory field [container]"; }
+ if (!is_array($container)) { return "Container must be an array"; }
+
+ $userInfo = $this->info($username, array("*"));
+ $dn = $userInfo[0]['distinguishedname'][0];
+ $newRDn = "cn=" . $username;
+ $container = array_reverse($container);
+ $newContainer = "ou=" . implode(",ou=",$container);
+ $newBaseDn = strtolower($newContainer) . "," . $this->adldap->getBaseDn();
+ $result = @ldap_rename($this->adldap->getLdapConnection(), $dn, $newRDn, $newBaseDn, true);
+ if ($result !== true) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Get the last logon time of any user as a Unix timestamp
+ *
+ * @param string $username
+ * @return long $unixTimestamp
+ */
+ public function getLastLogon($username) {
+ if (!$this->adldap->getLdapBind()) { return false; }
+ if ($username === null) { return "Missing compulsory field [username]"; }
+ $userInfo = $this->info($username, array("lastLogonTimestamp"));
+ $lastLogon = adLDAPUtils::convertWindowsTimeToUnixTime($userInfo[0]['lastLogonTimestamp'][0]);
+ return $lastLogon;
+ }
+
+}
+?>
diff --git a/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPUtils.php b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPUtils.php
new file mode 100644
index 0000000..6f94fe2
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/classes/adLDAPUtils.php
@@ -0,0 +1,268 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @subpackage Utils
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 97 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+ */
+require_once(dirname(__FILE__) . '/../adLDAP.php');
+
+/**
+* UTILITY FUNCTIONS
+*/
+class adLDAPUtils {
+ const ADLDAP_VERSION = '4.0.4';
+
+ /**
+ * The current adLDAP connection via dependency injection
+ *
+ * @var adLDAP
+ */
+ protected $adldap;
+
+ public function __construct(adLDAP $adldap) {
+ $this->adldap = $adldap;
+ }
+
+
+ /**
+ * Take an LDAP query and return the nice names, without all the LDAP prefixes (eg. CN, DN)
+ *
+ * @param array $groups
+ * @return array
+ */
+ public function niceNames($groups)
+ {
+
+ $groupArray = array();
+ for ($i=0; $i<$groups["count"]; $i++){ // For each group
+ $line = $groups[$i];
+
+ if (strlen($line)>0) {
+ // More presumptions, they're all prefixed with CN=
+ // so we ditch the first three characters and the group
+ // name goes up to the first comma
+ $bits=explode(",", $line);
+ $groupArray[] = substr($bits[0], 3, (strlen($bits[0])-3));
+ }
+ }
+ return $groupArray;
+ }
+
+ /**
+ * Escape characters for use in an ldap_create function
+ *
+ * @param string $str
+ * @return string
+ */
+ public function escapeCharacters($str) {
+ $str = str_replace(",", "\,", $str);
+ return $str;
+ }
+
+ /**
+ * Escape strings for the use in LDAP filters
+ *
+ * DEVELOPERS SHOULD BE DOING PROPER FILTERING IF THEY'RE ACCEPTING USER INPUT
+ * Ported from Perl's Net::LDAP::Util escape_filter_value
+ *
+ * @param string $str The string the parse
+ * @author Port by Andreas Gohr <andi@splitbrain.org>
+ * @return string
+ */
+ public function ldapSlashes($str) {
+ // see https://github.com/adldap/adLDAP/issues/22
+ return preg_replace_callback(
+ '/([\x00-\x1F\*\(\)\\\\])/',
+ function ($matches) {
+ return "\\".join("", unpack("H2", $matches[1]));
+ },
+ $str
+ );
+ }
+ /**
+ * Converts a string GUID to a hexdecimal value so it can be queried
+ *
+ * @param string $strGUID A string representation of a GUID
+ * @return string
+ */
+ public function strGuidToHex($strGUID)
+ {
+ $strGUID = str_replace('-', '', $strGUID);
+
+ $octet_str = '\\' . substr($strGUID, 6, 2);
+ $octet_str .= '\\' . substr($strGUID, 4, 2);
+ $octet_str .= '\\' . substr($strGUID, 2, 2);
+ $octet_str .= '\\' . substr($strGUID, 0, 2);
+ $octet_str .= '\\' . substr($strGUID, 10, 2);
+ $octet_str .= '\\' . substr($strGUID, 8, 2);
+ $octet_str .= '\\' . substr($strGUID, 14, 2);
+ $octet_str .= '\\' . substr($strGUID, 12, 2);
+ //$octet_str .= '\\' . substr($strGUID, 16, strlen($strGUID));
+ for ($i=16; $i<=(strlen($strGUID)-2); $i++) {
+ if (($i % 2) == 0) {
+ $octet_str .= '\\' . substr($strGUID, $i, 2);
+ }
+ }
+
+ return $octet_str;
+ }
+
+ /**
+ * Convert a binary SID to a text SID
+ *
+ * @param string $binsid A Binary SID
+ * @return string
+ */
+ public function getTextSID($binsid) {
+ $hex_sid = bin2hex($binsid);
+ $rev = hexdec(substr($hex_sid, 0, 2));
+ $subcount = hexdec(substr($hex_sid, 2, 2));
+ $auth = hexdec(substr($hex_sid, 4, 12));
+ $result = "$rev-$auth";
+
+ for ($x=0;$x < $subcount; $x++) {
+ $subauth[$x] =
+ hexdec($this->littleEndian(substr($hex_sid, 16 + ($x * 8), 8)));
+ $result .= "-" . $subauth[$x];
+ }
+
+ // Cheat by tacking on the S-
+ return 'S-' . $result;
+ }
+
+ /**
+ * Converts a little-endian hex number to one that hexdec() can convert
+ *
+ * @param string $hex A hex code
+ * @return string
+ */
+ public function littleEndian($hex)
+ {
+ $result = '';
+ for ($x = strlen($hex) - 2; $x >= 0; $x = $x - 2) {
+ $result .= substr($hex, $x, 2);
+ }
+ return $result;
+ }
+
+ /**
+ * Converts a binary attribute to a string
+ *
+ * @param string $bin A binary LDAP attribute
+ * @return string
+ */
+ public function binaryToText($bin)
+ {
+ $hex_guid = bin2hex($bin);
+ $hex_guid_to_guid_str = '';
+ for($k = 1; $k <= 4; ++$k) {
+ $hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
+ }
+ $hex_guid_to_guid_str .= '-';
+ for($k = 1; $k <= 2; ++$k) {
+ $hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
+ }
+ $hex_guid_to_guid_str .= '-';
+ for($k = 1; $k <= 2; ++$k) {
+ $hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
+ }
+ $hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
+ $hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
+ return strtoupper($hex_guid_to_guid_str);
+ }
+
+ /**
+ * Converts a binary GUID to a string GUID
+ *
+ * @param string $binaryGuid The binary GUID attribute to convert
+ * @return string
+ */
+ public function decodeGuid($binaryGuid)
+ {
+ if ($binaryGuid === null){ return "Missing compulsory field [binaryGuid]"; }
+
+ $strGUID = $this->binaryToText($binaryGuid);
+ return $strGUID;
+ }
+
+ /**
+ * Convert a boolean value to a string
+ * You should never need to call this yourself
+ *
+ * @param bool $bool Boolean value
+ * @return string
+ */
+ public function boolToStr($bool)
+ {
+ return ($bool) ? 'TRUE' : 'FALSE';
+ }
+
+ /**
+ * Convert 8bit characters e.g. accented characters to UTF8 encoded characters
+ */
+ public function encode8Bit(&$item, $key) {
+ $encode = false;
+ if (is_string($item)) {
+ for ($i=0; $i<strlen($item); $i++) {
+ if (ord($item[$i]) >> 7) {
+ $encode = true;
+ }
+ }
+ }
+ if ($encode === true && $key != 'password') {
+ $item = utf8_encode($item);
+ }
+ }
+
+ /**
+ * Get the current class version number
+ *
+ * @return string
+ */
+ public function getVersion() {
+ return self::ADLDAP_VERSION;
+ }
+
+ /**
+ * Round a Windows timestamp down to seconds and remove the seconds between 1601-01-01 and 1970-01-01
+ *
+ * @param long $windowsTime
+ * @return long $unixTime
+ */
+ public static function convertWindowsTimeToUnixTime($windowsTime) {
+ $unixTime = round($windowsTime / 10000000) - 11644477200;
+ return $unixTime;
+ }
+}
+
+?>
diff --git a/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPCollection.php b/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPCollection.php
new file mode 100644
index 0000000..433d39f
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPCollection.php
@@ -0,0 +1,137 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @subpackage Collection
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 97 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+*/
+
+abstract class adLDAPCollection
+{
+ /**
+ * The current adLDAP connection via dependency injection
+ *
+ * @var adLDAP
+ */
+ protected $adldap;
+
+ /**
+ * The current object being modifed / called
+ *
+ * @var mixed
+ */
+ protected $currentObject;
+
+ /**
+ * The raw info array from Active Directory
+ *
+ * @var array
+ */
+ protected $info;
+
+ public function __construct($info, adLDAP $adldap)
+ {
+ $this->setInfo($info);
+ $this->adldap = $adldap;
+ }
+
+ /**
+ * Set the raw info array from Active Directory
+ *
+ * @param array $info
+ */
+ public function setInfo(array $info)
+ {
+ if ($this->info && sizeof($info) >= 1) {
+ unset($this->info);
+ }
+ $this->info = $info;
+ }
+
+ /**
+ * Magic get method to retrieve data from the raw array in a formatted way
+ *
+ * @param string $attribute
+ * @return mixed
+ */
+ public function __get($attribute)
+ {
+ if (isset($this->info[0]) && is_array($this->info[0])) {
+ foreach ($this->info[0] as $keyAttr => $valueAttr) {
+ if (strtolower($keyAttr) == strtolower($attribute)) {
+ if ($this->info[0][strtolower($attribute)]['count'] == 1) {
+ return $this->info[0][strtolower($attribute)][0];
+ }
+ else {
+ $array = array();
+ foreach ($this->info[0][strtolower($attribute)] as $key => $value) {
+ if ((string)$key != 'count') {
+ $array[$key] = $value;
+ }
+ }
+ return $array;
+ }
+ }
+ }
+ }
+ else {
+ return NULL;
+ }
+ }
+
+ /**
+ * Magic set method to update an attribute
+ *
+ * @param string $attribute
+ * @param string $value
+ * @return bool
+ */
+ abstract public function __set($attribute, $value);
+
+ /**
+ * Magic isset method to check for the existence of an attribute
+ *
+ * @param string $attribute
+ * @return bool
+ */
+ public function __isset($attribute) {
+ if (isset($this->info[0]) && is_array($this->info[0])) {
+ foreach ($this->info[0] as $keyAttr => $valueAttr) {
+ if (strtolower($keyAttr) == strtolower($attribute)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
+?>
diff --git a/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPComputerCollection.php b/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPComputerCollection.php
new file mode 100644
index 0000000..09f82ca
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPComputerCollection.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @subpackage ComputerCollection
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 97 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+*/
+
+class adLDAPComputerCollection extends adLDAPCollection
+{
+
+ public function __set($attribute, $value)
+ {
+
+ }
+}
+?>
diff --git a/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPContactCollection.php b/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPContactCollection.php
new file mode 100644
index 0000000..a9efad5
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPContactCollection.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @subpackage ContactCollection
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 97 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+*/
+
+class adLDAPContactCollection extends adLDAPCollection
+{
+
+ public function __set($attribute, $value)
+ {
+
+ }
+}
+?>
diff --git a/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPGroupCollection.php b/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPGroupCollection.php
new file mode 100644
index 0000000..ef4af8d
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPGroupCollection.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @subpackage GroupCollection
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 97 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+*/
+
+class adLDAPGroupCollection extends adLDAPCollection
+{
+
+ public function __set($attribute, $value)
+ {
+
+ }
+}
+?>
diff --git a/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPUserCollection.php b/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPUserCollection.php
new file mode 100644
index 0000000..63fce5f
--- /dev/null
+++ b/platform/www/lib/plugins/authad/adLDAP/collections/adLDAPUserCollection.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
+ * Version 4.0.4
+ *
+ * PHP Version 5 with SSL and LDAP support
+ *
+ * Written by Scott Barnett, Richard Hyland
+ * email: scott@wiggumworld.com, adldap@richardhyland.com
+ * http://adldap.sourceforge.net/
+ *
+ * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ *
+ * We'd appreciate any improvements or additions to be submitted back
+ * to benefit the entire community :)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * @category ToolsAndUtilities
+ * @package adLDAP
+ * @subpackage UserCollection
+ * @author Scott Barnett, Richard Hyland
+ * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
+ * @revision $Revision: 97 $
+ * @version 4.0.4
+ * @link http://adldap.sourceforge.net/
+*/
+
+class adLDAPUserCollection extends adLDAPCollection
+{
+
+ public function __set($attribute, $value)
+ {
+
+ }
+}
+?>
diff --git a/platform/www/lib/plugins/authad/auth.php b/platform/www/lib/plugins/authad/auth.php
new file mode 100644
index 0000000..27a6b22
--- /dev/null
+++ b/platform/www/lib/plugins/authad/auth.php
@@ -0,0 +1,786 @@
+<?php
+
+/**
+ * Active Directory authentication backend for DokuWiki
+ *
+ * This makes authentication with a Active Directory server much easier
+ * than when using the normal LDAP backend by utilizing the adLDAP library
+ *
+ * Usage:
+ * Set DokuWiki's local.protected.php auth setting to read
+ *
+ * $conf['authtype'] = 'authad';
+ *
+ * $conf['plugin']['authad']['account_suffix'] = '@my.domain.org';
+ * $conf['plugin']['authad']['base_dn'] = 'DC=my,DC=domain,DC=org';
+ * $conf['plugin']['authad']['domain_controllers'] = 'srv1.domain.org,srv2.domain.org';
+ *
+ * //optional:
+ * $conf['plugin']['authad']['sso'] = 1;
+ * $conf['plugin']['authad']['admin_username'] = 'root';
+ * $conf['plugin']['authad']['admin_password'] = 'pass';
+ * $conf['plugin']['authad']['real_primarygroup'] = 1;
+ * $conf['plugin']['authad']['use_ssl'] = 1;
+ * $conf['plugin']['authad']['use_tls'] = 1;
+ * $conf['plugin']['authad']['debug'] = 1;
+ * // warn user about expiring password this many days in advance:
+ * $conf['plugin']['authad']['expirywarn'] = 5;
+ *
+ * // get additional information to the userinfo array
+ * // add a list of comma separated ldap contact fields.
+ * $conf['plugin']['authad']['additional'] = 'field1,field2';
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author James Van Lommel <jamesvl@gmail.com>
+ * @link http://www.nosq.com/blog/2005/08/ldap-activedirectory-and-dokuwiki/
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Jan Schumann <js@schumann-it.com>
+ */
+class auth_plugin_authad extends DokuWiki_Auth_Plugin
+{
+
+ /**
+ * @var array hold connection data for a specific AD domain
+ */
+ protected $opts = array();
+
+ /**
+ * @var array open connections for each AD domain, as adLDAP objects
+ */
+ protected $adldap = array();
+
+ /**
+ * @var bool message state
+ */
+ protected $msgshown = false;
+
+ /**
+ * @var array user listing cache
+ */
+ protected $users = array();
+
+ /**
+ * @var array filter patterns for listing users
+ */
+ protected $pattern = array();
+
+ protected $grpsusers = array();
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ global $INPUT;
+ parent::__construct();
+
+ require_once(DOKU_PLUGIN.'authad/adLDAP/adLDAP.php');
+ require_once(DOKU_PLUGIN.'authad/adLDAP/classes/adLDAPUtils.php');
+
+ // we load the config early to modify it a bit here
+ $this->loadConfig();
+
+ // additional information fields
+ if (isset($this->conf['additional'])) {
+ $this->conf['additional'] = str_replace(' ', '', $this->conf['additional']);
+ $this->conf['additional'] = explode(',', $this->conf['additional']);
+ } else $this->conf['additional'] = array();
+
+ // ldap extension is needed
+ if (!function_exists('ldap_connect')) {
+ if ($this->conf['debug'])
+ msg("AD Auth: PHP LDAP extension not found.", -1);
+ $this->success = false;
+ return;
+ }
+
+ // Prepare SSO
+ if (!empty($_SERVER['REMOTE_USER'])) {
+ // make sure the right encoding is used
+ if ($this->getConf('sso_charset')) {
+ $_SERVER['REMOTE_USER'] = iconv($this->getConf('sso_charset'), 'UTF-8', $_SERVER['REMOTE_USER']);
+ } elseif (!\dokuwiki\Utf8\Clean::isUtf8($_SERVER['REMOTE_USER'])) {
+ $_SERVER['REMOTE_USER'] = utf8_encode($_SERVER['REMOTE_USER']);
+ }
+
+ // trust the incoming user
+ if ($this->conf['sso']) {
+ $_SERVER['REMOTE_USER'] = $this->cleanUser($_SERVER['REMOTE_USER']);
+
+ // we need to simulate a login
+ if (empty($_COOKIE[DOKU_COOKIE])) {
+ $INPUT->set('u', $_SERVER['REMOTE_USER']);
+ $INPUT->set('p', 'sso_only');
+ }
+ }
+ }
+
+ // other can do's are changed in $this->_loadServerConfig() base on domain setup
+ $this->cando['modName'] = (bool)$this->conf['update_name'];
+ $this->cando['modMail'] = (bool)$this->conf['update_mail'];
+ $this->cando['getUserCount'] = true;
+ }
+
+ /**
+ * Load domain config on capability check
+ *
+ * @param string $cap
+ * @return bool
+ */
+ public function canDo($cap)
+ {
+ //capabilities depend on config, which may change depending on domain
+ $domain = $this->getUserDomain($_SERVER['REMOTE_USER']);
+ $this->loadServerConfig($domain);
+ return parent::canDo($cap);
+ }
+
+ /**
+ * Check user+password [required auth function]
+ *
+ * Checks if the given user exists and the given
+ * plaintext password is correct by trying to bind
+ * to the LDAP server
+ *
+ * @author James Van Lommel <james@nosq.com>
+ * @param string $user
+ * @param string $pass
+ * @return bool
+ */
+ public function checkPass($user, $pass)
+ {
+ if ($_SERVER['REMOTE_USER'] &&
+ $_SERVER['REMOTE_USER'] == $user &&
+ $this->conf['sso']
+ ) return true;
+
+ $adldap = $this->initAdLdap($this->getUserDomain($user));
+ if (!$adldap) return false;
+
+ try {
+ return $adldap->authenticate($this->getUserName($user), $pass);
+ } catch (adLDAPException $e) {
+ // shouldn't really happen
+ return false;
+ }
+ }
+
+ /**
+ * Return user info [required auth function]
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ *
+ * name string full name of the user
+ * mail string email address of the user
+ * grps array list of groups the user is in
+ *
+ * This AD specific function returns the following
+ * addional fields:
+ *
+ * dn string distinguished name (DN)
+ * uid string samaccountname
+ * lastpwd int timestamp of the date when the password was set
+ * expires true if the password expires
+ * expiresin int seconds until the password expires
+ * any fields specified in the 'additional' config option
+ *
+ * @author James Van Lommel <james@nosq.com>
+ * @param string $user
+ * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin
+ * @return array
+ */
+ public function getUserData($user, $requireGroups = true)
+ {
+ global $conf;
+ global $lang;
+ global $ID;
+ $adldap = $this->initAdLdap($this->getUserDomain($user));
+ if (!$adldap) return array();
+
+ if ($user == '') return array();
+
+ $fields = array('mail', 'displayname', 'samaccountname', 'lastpwd', 'pwdlastset', 'useraccountcontrol');
+
+ // add additional fields to read
+ $fields = array_merge($fields, $this->conf['additional']);
+ $fields = array_unique($fields);
+ $fields = array_filter($fields);
+
+ //get info for given user
+ $result = $adldap->user()->info($this->getUserName($user), $fields);
+ if ($result == false) {
+ return array();
+ }
+
+ //general user info
+ $info = array();
+ $info['name'] = $result[0]['displayname'][0];
+ $info['mail'] = $result[0]['mail'][0];
+ $info['uid'] = $result[0]['samaccountname'][0];
+ $info['dn'] = $result[0]['dn'];
+ //last password set (Windows counts from January 1st 1601)
+ $info['lastpwd'] = $result[0]['pwdlastset'][0] / 10000000 - 11644473600;
+ //will it expire?
+ $info['expires'] = !($result[0]['useraccountcontrol'][0] & 0x10000); //ADS_UF_DONT_EXPIRE_PASSWD
+
+ // additional information
+ foreach ($this->conf['additional'] as $field) {
+ if (isset($result[0][strtolower($field)])) {
+ $info[$field] = $result[0][strtolower($field)][0];
+ }
+ }
+
+ // handle ActiveDirectory memberOf
+ $info['grps'] = $adldap->user()->groups($this->getUserName($user), (bool) $this->opts['recursive_groups']);
+
+ if (is_array($info['grps'])) {
+ foreach ($info['grps'] as $ndx => $group) {
+ $info['grps'][$ndx] = $this->cleanGroup($group);
+ }
+ }
+
+ // always add the default group to the list of groups
+ if (!is_array($info['grps']) || !in_array($conf['defaultgroup'], $info['grps'])) {
+ $info['grps'][] = $conf['defaultgroup'];
+ }
+
+ // add the user's domain to the groups
+ $domain = $this->getUserDomain($user);
+ if ($domain && !in_array("domain-$domain", (array) $info['grps'])) {
+ $info['grps'][] = $this->cleanGroup("domain-$domain");
+ }
+
+ // check expiry time
+ if ($info['expires'] && $this->conf['expirywarn']) {
+ try {
+ $expiry = $adldap->user()->passwordExpiry($user);
+ if (is_array($expiry)) {
+ $info['expiresat'] = $expiry['expiryts'];
+ $info['expiresin'] = round(($info['expiresat'] - time())/(24*60*60));
+
+ // if this is the current user, warn him (once per request only)
+ if (($_SERVER['REMOTE_USER'] == $user) &&
+ ($info['expiresin'] <= $this->conf['expirywarn']) &&
+ !$this->msgshown
+ ) {
+ $msg = sprintf($this->getLang('authpwdexpire'), $info['expiresin']);
+ if ($this->canDo('modPass')) {
+ $url = wl($ID, array('do'=> 'profile'));
+ $msg .= ' <a href="'.$url.'">'.$lang['btn_profile'].'</a>';
+ }
+ msg($msg);
+ $this->msgshown = true;
+ }
+ }
+ } catch (adLDAPException $e) {
+ // ignore. should usually not happen
+ }
+ }
+
+ return $info;
+ }
+
+ /**
+ * Make AD group names usable by DokuWiki.
+ *
+ * Removes backslashes ('\'), pound signs ('#'), and converts spaces to underscores.
+ *
+ * @author James Van Lommel (jamesvl@gmail.com)
+ * @param string $group
+ * @return string
+ */
+ public function cleanGroup($group)
+ {
+ $group = str_replace('\\', '', $group);
+ $group = str_replace('#', '', $group);
+ $group = preg_replace('[\s]', '_', $group);
+ $group = \dokuwiki\Utf8\PhpString::strtolower(trim($group));
+ return $group;
+ }
+
+ /**
+ * Sanitize user names
+ *
+ * Normalizes domain parts, does not modify the user name itself (unlike cleanGroup)
+ *
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ * @param string $user
+ * @return string
+ */
+ public function cleanUser($user)
+ {
+ $domain = '';
+
+ // get NTLM or Kerberos domain part
+ list($dom, $user) = explode('\\', $user, 2);
+ if (!$user) $user = $dom;
+ if ($dom) $domain = $dom;
+ list($user, $dom) = explode('@', $user, 2);
+ if ($dom) $domain = $dom;
+
+ // clean up both
+ $domain = \dokuwiki\Utf8\PhpString::strtolower(trim($domain));
+ $user = \dokuwiki\Utf8\PhpString::strtolower(trim($user));
+
+ // is this a known, valid domain or do we work without account suffix? if not discard
+ if (!is_array($this->conf[$domain]) && $this->conf['account_suffix'] !== '') {
+ $domain = '';
+ }
+
+ // reattach domain
+ if ($domain) $user = "$user@$domain";
+ return $user;
+ }
+
+ /**
+ * Most values in LDAP are case-insensitive
+ *
+ * @return bool
+ */
+ public function isCaseSensitive()
+ {
+ return false;
+ }
+
+ /**
+ * Create a Search-String useable by adLDAPUsers::all($includeDescription = false, $search = "*", $sorted = true)
+ *
+ * @param array $filter
+ * @return string
+ */
+ protected function constructSearchString($filter)
+ {
+ if (!$filter) {
+ return '*';
+ }
+ $adldapUtils = new adLDAPUtils($this->initAdLdap(null));
+ $result = '*';
+ if (isset($filter['name'])) {
+ $result .= ')(displayname=*' . $adldapUtils->ldapSlashes($filter['name']) . '*';
+ unset($filter['name']);
+ }
+
+ if (isset($filter['user'])) {
+ $result .= ')(samAccountName=*' . $adldapUtils->ldapSlashes($filter['user']) . '*';
+ unset($filter['user']);
+ }
+
+ if (isset($filter['mail'])) {
+ $result .= ')(mail=*' . $adldapUtils->ldapSlashes($filter['mail']) . '*';
+ unset($filter['mail']);
+ }
+ return $result;
+ }
+
+ /**
+ * Return a count of the number of user which meet $filter criteria
+ *
+ * @param array $filter $filter array of field/pattern pairs, empty array for no filter
+ * @return int number of users
+ */
+ public function getUserCount($filter = array())
+ {
+ $adldap = $this->initAdLdap(null);
+ if (!$adldap) {
+ dbglog("authad/auth.php getUserCount(): _adldap not set.");
+ return -1;
+ }
+ if ($filter == array()) {
+ $result = $adldap->user()->all();
+ } else {
+ $searchString = $this->constructSearchString($filter);
+ $result = $adldap->user()->all(false, $searchString);
+ if (isset($filter['grps'])) {
+ $this->users = array_fill_keys($result, false);
+ /** @var admin_plugin_usermanager $usermanager */
+ $usermanager = plugin_load("admin", "usermanager", false);
+ $usermanager->setLastdisabled(true);
+ if (!isset($this->grpsusers[$this->filterToString($filter)])) {
+ $this->fillGroupUserArray($filter, $usermanager->getStart() + 3*$usermanager->getPagesize());
+ } elseif (count($this->grpsusers[$this->filterToString($filter)]) <
+ $usermanager->getStart() + 3*$usermanager->getPagesize()
+ ) {
+ $this->fillGroupUserArray(
+ $filter,
+ $usermanager->getStart() +
+ 3*$usermanager->getPagesize() -
+ count($this->grpsusers[$this->filterToString($filter)])
+ );
+ }
+ $result = $this->grpsusers[$this->filterToString($filter)];
+ } else {
+ /** @var admin_plugin_usermanager $usermanager */
+ $usermanager = plugin_load("admin", "usermanager", false);
+ $usermanager->setLastdisabled(false);
+ }
+ }
+
+ if (!$result) {
+ return 0;
+ }
+ return count($result);
+ }
+
+ /**
+ *
+ * create a unique string for each filter used with a group
+ *
+ * @param array $filter
+ * @return string
+ */
+ protected function filterToString($filter)
+ {
+ $result = '';
+ if (isset($filter['user'])) {
+ $result .= 'user-' . $filter['user'];
+ }
+ if (isset($filter['name'])) {
+ $result .= 'name-' . $filter['name'];
+ }
+ if (isset($filter['mail'])) {
+ $result .= 'mail-' . $filter['mail'];
+ }
+ if (isset($filter['grps'])) {
+ $result .= 'grps-' . $filter['grps'];
+ }
+ return $result;
+ }
+
+ /**
+ * Create an array of $numberOfAdds users passing a certain $filter, including belonging
+ * to a certain group and save them to a object-wide array. If the array
+ * already exists try to add $numberOfAdds further users to it.
+ *
+ * @param array $filter
+ * @param int $numberOfAdds additional number of users requested
+ * @return int number of Users actually add to Array
+ */
+ protected function fillGroupUserArray($filter, $numberOfAdds)
+ {
+ if (isset($this->grpsusers[$this->filterToString($filter)])) {
+ $actualstart = count($this->grpsusers[$this->filterToString($filter)]);
+ } else {
+ $this->grpsusers[$this->filterToString($filter)] = [];
+ $actualstart = 0;
+ }
+
+ $i=0;
+ $count = 0;
+ $this->constructPattern($filter);
+ foreach ($this->users as $user => &$info) {
+ if ($i++ < $actualstart) {
+ continue;
+ }
+ if ($info === false) {
+ $info = $this->getUserData($user);
+ }
+ if ($this->filter($user, $info)) {
+ $this->grpsusers[$this->filterToString($filter)][$user] = $info;
+ if (($numberOfAdds > 0) && (++$count >= $numberOfAdds)) break;
+ }
+ }
+ return $count;
+ }
+
+ /**
+ * Bulk retrieval of user data
+ *
+ * @author Dominik Eckelmann <dokuwiki@cosmocode.de>
+ *
+ * @param int $start index of first user to be returned
+ * @param int $limit max number of users to be returned
+ * @param array $filter array of field/pattern pairs, null for no filter
+ * @return array userinfo (refer getUserData for internal userinfo details)
+ */
+ public function retrieveUsers($start = 0, $limit = 0, $filter = array())
+ {
+ $adldap = $this->initAdLdap(null);
+ if (!$adldap) return array();
+
+ //if (!$this->users) {
+ //get info for given user
+ $result = $adldap->user()->all(false, $this->constructSearchString($filter));
+ if (!$result) return array();
+ $this->users = array_fill_keys($result, false);
+ //}
+
+ $i = 0;
+ $count = 0;
+ $result = array();
+
+ if (!isset($filter['grps'])) {
+ /** @var admin_plugin_usermanager $usermanager */
+ $usermanager = plugin_load("admin", "usermanager", false);
+ $usermanager->setLastdisabled(false);
+ $this->constructPattern($filter);
+ foreach ($this->users as $user => &$info) {
+ if ($i++ < $start) {
+ continue;
+ }
+ if ($info === false) {
+ $info = $this->getUserData($user);
+ }
+ $result[$user] = $info;
+ if (($limit > 0) && (++$count >= $limit)) break;
+ }
+ } else {
+ /** @var admin_plugin_usermanager $usermanager */
+ $usermanager = plugin_load("admin", "usermanager", false);
+ $usermanager->setLastdisabled(true);
+ if (!isset($this->grpsusers[$this->filterToString($filter)]) ||
+ count($this->grpsusers[$this->filterToString($filter)]) < ($start+$limit)
+ ) {
+ if(!isset($this->grpsusers[$this->filterToString($filter)])) {
+ $this->grpsusers[$this->filterToString($filter)] = [];
+ }
+
+ $this->fillGroupUserArray(
+ $filter,
+ $start+$limit - count($this->grpsusers[$this->filterToString($filter)]) +1
+ );
+ }
+ if (!$this->grpsusers[$this->filterToString($filter)]) return array();
+ foreach ($this->grpsusers[$this->filterToString($filter)] as $user => &$info) {
+ if ($i++ < $start) {
+ continue;
+ }
+ $result[$user] = $info;
+ if (($limit > 0) && (++$count >= $limit)) break;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Modify user data
+ *
+ * @param string $user nick of the user to be changed
+ * @param array $changes array of field/value pairs to be changed
+ * @return bool
+ */
+ public function modifyUser($user, $changes)
+ {
+ $return = true;
+ $adldap = $this->initAdLdap($this->getUserDomain($user));
+ if (!$adldap) {
+ msg($this->getLang('connectfail'), -1);
+ return false;
+ }
+
+ // password changing
+ if (isset($changes['pass'])) {
+ try {
+ $return = $adldap->user()->password($this->getUserName($user), $changes['pass']);
+ } catch (adLDAPException $e) {
+ if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
+ $return = false;
+ }
+ if (!$return) msg($this->getLang('passchangefail'), -1);
+ }
+
+ // changing user data
+ $adchanges = array();
+ if (isset($changes['name'])) {
+ // get first and last name
+ $parts = explode(' ', $changes['name']);
+ $adchanges['surname'] = array_pop($parts);
+ $adchanges['firstname'] = join(' ', $parts);
+ $adchanges['display_name'] = $changes['name'];
+ }
+ if (isset($changes['mail'])) {
+ $adchanges['email'] = $changes['mail'];
+ }
+ if (count($adchanges)) {
+ try {
+ $return = $return & $adldap->user()->modify($this->getUserName($user), $adchanges);
+ } catch (adLDAPException $e) {
+ if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
+ $return = false;
+ }
+ if (!$return) msg($this->getLang('userchangefail'), -1);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Initialize the AdLDAP library and connect to the server
+ *
+ * When you pass null as domain, it will reuse any existing domain.
+ * Eg. the one of the logged in user. It falls back to the default
+ * domain if no current one is available.
+ *
+ * @param string|null $domain The AD domain to use
+ * @return adLDAP|bool true if a connection was established
+ */
+ protected function initAdLdap($domain)
+ {
+ if (is_null($domain) && is_array($this->opts)) {
+ $domain = $this->opts['domain'];
+ }
+
+ $this->opts = $this->loadServerConfig((string) $domain);
+ if (isset($this->adldap[$domain])) return $this->adldap[$domain];
+
+ // connect
+ try {
+ $this->adldap[$domain] = new adLDAP($this->opts);
+ return $this->adldap[$domain];
+ } catch (Exception $e) {
+ if ($this->conf['debug']) {
+ msg('AD Auth: '.$e->getMessage(), -1);
+ }
+ $this->success = false;
+ $this->adldap[$domain] = null;
+ }
+ return false;
+ }
+
+ /**
+ * Get the domain part from a user
+ *
+ * @param string $user
+ * @return string
+ */
+ public function getUserDomain($user)
+ {
+ list(, $domain) = explode('@', $user, 2);
+ return $domain;
+ }
+
+ /**
+ * Get the user part from a user
+ *
+ * When an account suffix is set, we strip the domain part from the user
+ *
+ * @param string $user
+ * @return string
+ */
+ public function getUserName($user)
+ {
+ if ($this->conf['account_suffix'] !== '') {
+ list($user) = explode('@', $user, 2);
+ }
+ return $user;
+ }
+
+ /**
+ * Fetch the configuration for the given AD domain
+ *
+ * @param string $domain current AD domain
+ * @return array
+ */
+ protected function loadServerConfig($domain)
+ {
+ // prepare adLDAP standard configuration
+ $opts = $this->conf;
+
+ $opts['domain'] = $domain;
+
+ // add possible domain specific configuration
+ if ($domain && is_array($this->conf[$domain])) foreach ($this->conf[$domain] as $key => $val) {
+ $opts[$key] = $val;
+ }
+
+ // handle multiple AD servers
+ $opts['domain_controllers'] = explode(',', $opts['domain_controllers']);
+ $opts['domain_controllers'] = array_map('trim', $opts['domain_controllers']);
+ $opts['domain_controllers'] = array_filter($opts['domain_controllers']);
+
+ // compatibility with old option name
+ if (empty($opts['admin_username']) && !empty($opts['ad_username'])) {
+ $opts['admin_username'] = $opts['ad_username'];
+ }
+ if (empty($opts['admin_password']) && !empty($opts['ad_password'])) {
+ $opts['admin_password'] = $opts['ad_password'];
+ }
+ $opts['admin_password'] = conf_decodeString($opts['admin_password']); // deobfuscate
+
+ // we can change the password if SSL is set
+ if ($opts['use_ssl'] || $opts['use_tls']) {
+ $this->cando['modPass'] = true;
+ } else {
+ $this->cando['modPass'] = false;
+ }
+
+ // adLDAP expects empty user/pass as NULL, we're less strict FS#2781
+ if (empty($opts['admin_username'])) $opts['admin_username'] = null;
+ if (empty($opts['admin_password'])) $opts['admin_password'] = null;
+
+ // user listing needs admin priviledges
+ if (!empty($opts['admin_username']) && !empty($opts['admin_password'])) {
+ $this->cando['getUsers'] = true;
+ } else {
+ $this->cando['getUsers'] = false;
+ }
+
+ return $opts;
+ }
+
+ /**
+ * Returns a list of configured domains
+ *
+ * The default domain has an empty string as key
+ *
+ * @return array associative array(key => domain)
+ */
+ public function getConfiguredDomains()
+ {
+ $domains = array();
+ if (empty($this->conf['account_suffix'])) return $domains; // not configured yet
+
+ // add default domain, using the name from account suffix
+ $domains[''] = ltrim($this->conf['account_suffix'], '@');
+
+ // find additional domains
+ foreach ($this->conf as $key => $val) {
+ if (is_array($val) && isset($val['account_suffix'])) {
+ $domains[$key] = ltrim($val['account_suffix'], '@');
+ }
+ }
+ ksort($domains);
+
+ return $domains;
+ }
+
+ /**
+ * Check provided user and userinfo for matching patterns
+ *
+ * The patterns are set up with $this->_constructPattern()
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ *
+ * @param string $user
+ * @param array $info
+ * @return bool
+ */
+ protected function filter($user, $info)
+ {
+ foreach ($this->pattern as $item => $pattern) {
+ if ($item == 'user') {
+ if (!preg_match($pattern, $user)) return false;
+ } elseif ($item == 'grps') {
+ if (!count(preg_grep($pattern, $info['grps']))) return false;
+ } else {
+ if (!preg_match($pattern, $info[$item])) return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Create a pattern for $this->_filter()
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ *
+ * @param array $filter
+ */
+ protected function constructPattern($filter)
+ {
+ $this->pattern = array();
+ foreach ($filter as $item => $pattern) {
+ $this->pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters
+ }
+ }
+}
diff --git a/platform/www/lib/plugins/authad/conf/default.php b/platform/www/lib/plugins/authad/conf/default.php
new file mode 100644
index 0000000..84094cc
--- /dev/null
+++ b/platform/www/lib/plugins/authad/conf/default.php
@@ -0,0 +1,18 @@
+<?php
+
+$conf['account_suffix'] = '';
+$conf['base_dn'] = '';
+$conf['domain_controllers'] = '';
+$conf['sso'] = 0;
+$conf['sso_charset'] = '';
+$conf['admin_username'] = '';
+$conf['admin_password'] = '';
+$conf['real_primarygroup'] = 0;
+$conf['use_ssl'] = 0;
+$conf['use_tls'] = 0;
+$conf['debug'] = 0;
+$conf['expirywarn'] = 0;
+$conf['additional'] = '';
+$conf['update_name'] = 0;
+$conf['update_mail'] = 0;
+$conf['recursive_groups'] = 0;
diff --git a/platform/www/lib/plugins/authad/conf/metadata.php b/platform/www/lib/plugins/authad/conf/metadata.php
new file mode 100644
index 0000000..945474c
--- /dev/null
+++ b/platform/www/lib/plugins/authad/conf/metadata.php
@@ -0,0 +1,18 @@
+<?php
+
+$meta['account_suffix'] = array('string','_caution' => 'danger');
+$meta['base_dn'] = array('string','_caution' => 'danger');
+$meta['domain_controllers'] = array('string','_caution' => 'danger');
+$meta['sso'] = array('onoff','_caution' => 'danger');
+$meta['sso_charset'] = array('string','_caution' => 'danger');
+$meta['admin_username'] = array('string','_caution' => 'danger');
+$meta['admin_password'] = array('password','_caution' => 'danger','_code' => 'base64');
+$meta['real_primarygroup'] = array('onoff','_caution' => 'danger');
+$meta['use_ssl'] = array('onoff','_caution' => 'danger');
+$meta['use_tls'] = array('onoff','_caution' => 'danger');
+$meta['debug'] = array('onoff','_caution' => 'security');
+$meta['expirywarn'] = array('numeric', '_min'=>0,'_caution' => 'danger');
+$meta['additional'] = array('string','_caution' => 'danger');
+$meta['update_name'] = array('onoff','_caution' => 'danger');
+$meta['update_mail'] = array('onoff','_caution' => 'danger');
+$meta['recursive_groups'] = array('onoff','_caution' => 'danger');
diff --git a/platform/www/lib/plugins/authad/lang/en/lang.php b/platform/www/lib/plugins/authad/lang/en/lang.php
new file mode 100644
index 0000000..3e8a9e2
--- /dev/null
+++ b/platform/www/lib/plugins/authad/lang/en/lang.php
@@ -0,0 +1,15 @@
+<?php
+/**
+ * English language file for addomain plugin
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+$lang['domain'] = 'Logon Domain';
+$lang['authpwdexpire'] = 'Your password will expire in %d days, you should change it soon.';
+$lang['passchangefail'] = 'Failed to change the password. Maybe the password policy was not met?';
+$lang['userchangefail'] = 'Failed to change user attributes. Maybe your account does not have permissions to make changes?';
+$lang['connectfail'] = 'Failed to connect to Active Directory server.';
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/authad/lang/en/settings.php b/platform/www/lib/plugins/authad/lang/en/settings.php
new file mode 100644
index 0000000..3de9a72
--- /dev/null
+++ b/platform/www/lib/plugins/authad/lang/en/settings.php
@@ -0,0 +1,18 @@
+<?php
+
+$lang['account_suffix'] = 'Your account suffix. Eg. <code>@my.domain.org</code>';
+$lang['base_dn'] = 'Your base DN. Eg. <code>DC=my,DC=domain,DC=org</code>';
+$lang['domain_controllers'] = 'A comma separated list of Domain controllers. Eg. <code>srv1.domain.org,srv2.domain.org</code>';
+$lang['admin_username'] = 'A privileged Active Directory user with access to all other user\'s data. Optional, but needed for certain actions like sending subscription mails.';
+$lang['admin_password'] = 'The password of the above user.';
+$lang['sso'] = 'Should Single-Sign-On via Kerberos or NTLM be used?';
+$lang['sso_charset'] = 'The charset your webserver will pass the Kerberos or NTLM username in. Empty for UTF-8 or latin-1. Requires the iconv extension.';
+$lang['real_primarygroup'] = 'Should the real primary group be resolved instead of assuming "Domain Users" (slower).';
+$lang['use_ssl'] = 'Use SSL connection? If used, do not enable TLS below.';
+$lang['use_tls'] = 'Use TLS connection? If used, do not enable SSL above.';
+$lang['debug'] = 'Display additional debugging output on errors?';
+$lang['expirywarn'] = 'Days in advance to warn user about expiring password. 0 to disable.';
+$lang['additional'] = 'A comma separated list of additional AD attributes to fetch from user data. Used by some plugins.';
+$lang['update_name'] = 'Allow users to update their AD display name?';
+$lang['update_mail'] = 'Allow users to update their email address?';
+$lang['recursive_groups'] = 'Resolve nested groups to their respective members (slower).';
diff --git a/platform/www/lib/plugins/authad/plugin.info.txt b/platform/www/lib/plugins/authad/plugin.info.txt
new file mode 100644
index 0000000..57e1387
--- /dev/null
+++ b/platform/www/lib/plugins/authad/plugin.info.txt
@@ -0,0 +1,7 @@
+base authad
+author Andreas Gohr
+email andi@splitbrain.org
+date 2015-07-13
+name Active Directory Auth Plugin
+desc Provides user authentication against a Microsoft Active Directory
+url http://www.dokuwiki.org/plugin:authad
diff --git a/platform/www/lib/plugins/authldap/auth.php b/platform/www/lib/plugins/authldap/auth.php
new file mode 100644
index 0000000..68d1dad
--- /dev/null
+++ b/platform/www/lib/plugins/authldap/auth.php
@@ -0,0 +1,698 @@
+<?php
+
+/**
+ * LDAP authentication backend
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakaic.co.uk>
+ * @author Jan Schumann <js@schumann-it.com>
+ */
+class auth_plugin_authldap extends DokuWiki_Auth_Plugin
+{
+ /* @var resource $con holds the LDAP connection */
+ protected $con = null;
+
+ /* @var int $bound What type of connection does already exist? */
+ protected $bound = 0; // 0: anonymous, 1: user, 2: superuser
+
+ /* @var array $users User data cache */
+ protected $users = null;
+
+ /* @var array $pattern User filter pattern */
+ protected $pattern = null;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ parent::__construct();
+
+ // ldap extension is needed
+ if (!function_exists('ldap_connect')) {
+ $this->debug("LDAP err: PHP LDAP extension not found.", -1, __LINE__, __FILE__);
+ $this->success = false;
+ return;
+ }
+
+ // Add the capabilities to change the password
+ $this->cando['modPass'] = $this->getConf('modPass');
+ }
+
+ /**
+ * Check user+password
+ *
+ * Checks if the given user exists and the given
+ * plaintext password is correct by trying to bind
+ * to the LDAP server
+ *
+ * @param string $user
+ * @param string $pass
+ * @return bool
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ public function checkPass($user, $pass)
+ {
+ // reject empty password
+ if (empty($pass)) return false;
+ if (!$this->openLDAP()) return false;
+
+ // indirect user bind
+ if ($this->getConf('binddn') && $this->getConf('bindpw')) {
+ // use superuser credentials
+ if (!@ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw')))) {
+ $this->debug('LDAP bind as superuser: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ return false;
+ }
+ $this->bound = 2;
+ } elseif ($this->getConf('binddn') &&
+ $this->getConf('usertree') &&
+ $this->getConf('userfilter')
+ ) {
+ // special bind string
+ $dn = $this->makeFilter(
+ $this->getConf('binddn'),
+ array('user' => $user, 'server' => $this->getConf('server'))
+ );
+ } elseif (strpos($this->getConf('usertree'), '%{user}')) {
+ // direct user bind
+ $dn = $this->makeFilter(
+ $this->getConf('usertree'),
+ array('user' => $user, 'server' => $this->getConf('server'))
+ );
+ } else {
+ // Anonymous bind
+ if (!@ldap_bind($this->con)) {
+ msg("LDAP: can not bind anonymously", -1);
+ $this->debug('LDAP anonymous bind: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ return false;
+ }
+ }
+
+ // Try to bind to with the dn if we have one.
+ if (!empty($dn)) {
+ // User/Password bind
+ if (!@ldap_bind($this->con, $dn, $pass)) {
+ $this->debug("LDAP: bind with $dn failed", -1, __LINE__, __FILE__);
+ $this->debug('LDAP user dn bind: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ return false;
+ }
+ $this->bound = 1;
+ return true;
+ } else {
+ // See if we can find the user
+ $info = $this->fetchUserData($user, true);
+ if (empty($info['dn'])) {
+ return false;
+ } else {
+ $dn = $info['dn'];
+ }
+
+ // Try to bind with the dn provided
+ if (!@ldap_bind($this->con, $dn, $pass)) {
+ $this->debug("LDAP: bind with $dn failed", -1, __LINE__, __FILE__);
+ $this->debug('LDAP user bind: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ return false;
+ }
+ $this->bound = 1;
+ return true;
+ }
+ }
+
+ /**
+ * Return user info
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ *
+ * name string full name of the user
+ * mail string email addres of the user
+ * grps array list of groups the user is in
+ *
+ * This LDAP specific function returns the following
+ * addional fields:
+ *
+ * dn string distinguished name (DN)
+ * uid string Posix User ID
+ * inbind bool for internal use - avoid loop in binding
+ *
+ * @param string $user
+ * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin
+ * @return array containing user data or false
+ * @author <evaldas.auryla@pheur.org>
+ * @author Stephane Chazelas <stephane.chazelas@emerson.com>
+ * @author Steffen Schoch <schoch@dsb.net>
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Trouble
+ * @author Dan Allen <dan.j.allen@gmail.com>
+ */
+ public function getUserData($user, $requireGroups = true)
+ {
+ return $this->fetchUserData($user);
+ }
+
+ /**
+ * @param string $user
+ * @param bool $inbind authldap specific, true if in bind phase
+ * @return array containing user data or false
+ */
+ protected function fetchUserData($user, $inbind = false)
+ {
+ global $conf;
+ if (!$this->openLDAP()) return array();
+
+ // force superuser bind if wanted and not bound as superuser yet
+ if ($this->getConf('binddn') && $this->getConf('bindpw') && $this->bound < 2) {
+ // use superuser credentials
+ if (!@ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw')))) {
+ $this->debug('LDAP bind as superuser: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ return array();
+ }
+ $this->bound = 2;
+ } elseif ($this->bound == 0 && !$inbind) {
+ // in some cases getUserData is called outside the authentication workflow
+ // eg. for sending email notification on subscribed pages. This data might not
+ // be accessible anonymously, so we try to rebind the current user here
+ list($loginuser, $loginsticky, $loginpass) = auth_getCookie();
+ if ($loginuser && $loginpass) {
+ $loginpass = auth_decrypt($loginpass, auth_cookiesalt(!$loginsticky, true));
+ $this->checkPass($loginuser, $loginpass);
+ }
+ }
+
+ $info = array();
+ $info['user'] = $user;
+ $this->debug('LDAP user to find: ' . hsc($info['user']), 0, __LINE__, __FILE__);
+
+ $info['server'] = $this->getConf('server');
+ $this->debug('LDAP Server: ' . hsc($info['server']), 0, __LINE__, __FILE__);
+
+ //get info for given user
+ $base = $this->makeFilter($this->getConf('usertree'), $info);
+ if ($this->getConf('userfilter')) {
+ $filter = $this->makeFilter($this->getConf('userfilter'), $info);
+ } else {
+ $filter = "(ObjectClass=*)";
+ }
+
+ $this->debug('LDAP Filter: ' . hsc($filter), 0, __LINE__, __FILE__);
+
+ $this->debug('LDAP user search: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ $this->debug('LDAP search at: ' . hsc($base . ' ' . $filter), 0, __LINE__, __FILE__);
+ $sr = $this->ldapSearch($this->con, $base, $filter, $this->getConf('userscope'), $this->getConf('attributes'));
+
+
+ $result = @ldap_get_entries($this->con, $sr);
+
+ // if result is not an array
+ if (!is_array($result)) {
+ // no objects found
+ $this->debug('LDAP search returned non-array result: ' . hsc(print($result)), -1, __LINE__, __FILE__);
+ return array();
+ }
+
+ // Don't accept more or less than one response
+ if ($result['count'] != 1) {
+ $this->debug(
+ 'LDAP search returned ' . hsc($result['count']) . ' results while it should return 1!',
+ -1,
+ __LINE__,
+ __FILE__
+ );
+ //for($i = 0; $i < $result["count"]; $i++) {
+ //$this->_debug('result: '.hsc(print_r($result[$i])), 0, __LINE__, __FILE__);
+ //}
+ return array();
+ }
+
+ $this->debug('LDAP search found single result !', 0, __LINE__, __FILE__);
+
+ $user_result = $result[0];
+ ldap_free_result($sr);
+
+ // general user info
+ $info['dn'] = $user_result['dn'];
+ $info['gid'] = $user_result['gidnumber'][0];
+ $info['mail'] = $user_result['mail'][0];
+ $info['name'] = $user_result['cn'][0];
+ $info['grps'] = array();
+
+ // overwrite if other attribs are specified.
+ if (is_array($this->getConf('mapping'))) {
+ foreach ($this->getConf('mapping') as $localkey => $key) {
+ if (is_array($key)) {
+ // use regexp to clean up user_result
+ // $key = array($key=>$regexp), only handles the first key-value
+ $regexp = current($key);
+ $key = key($key);
+ if ($user_result[$key]) foreach ($user_result[$key] as $grpkey => $grp) {
+ if ($grpkey !== 'count' && preg_match($regexp, $grp, $match)) {
+ if ($localkey == 'grps') {
+ $info[$localkey][] = $match[1];
+ } else {
+ $info[$localkey] = $match[1];
+ }
+ }
+ }
+ } else {
+ $info[$localkey] = $user_result[$key][0];
+ }
+ }
+ }
+ $user_result = array_merge($info, $user_result);
+
+ //get groups for given user if grouptree is given
+ if ($this->getConf('grouptree') || $this->getConf('groupfilter')) {
+ $base = $this->makeFilter($this->getConf('grouptree'), $user_result);
+ $filter = $this->makeFilter($this->getConf('groupfilter'), $user_result);
+ $sr = $this->ldapSearch(
+ $this->con,
+ $base,
+ $filter,
+ $this->getConf('groupscope'),
+ array($this->getConf('groupkey'))
+ );
+ $this->debug('LDAP group search: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ $this->debug('LDAP search at: ' . hsc($base . ' ' . $filter), 0, __LINE__, __FILE__);
+
+ if (!$sr) {
+ msg("LDAP: Reading group memberships failed", -1);
+ return array();
+ }
+ $result = ldap_get_entries($this->con, $sr);
+ ldap_free_result($sr);
+
+ if (is_array($result)) foreach ($result as $grp) {
+ if (!empty($grp[$this->getConf('groupkey')])) {
+ $group = $grp[$this->getConf('groupkey')];
+ if (is_array($group)) {
+ $group = $group[0];
+ } else {
+ $this->debug('groupkey did not return a detailled result', 0, __LINE__, __FILE__);
+ }
+ if ($group === '') continue;
+
+ $this->debug('LDAP usergroup: ' . hsc($group), 0, __LINE__, __FILE__);
+ $info['grps'][] = $group;
+ }
+ }
+ }
+
+ // always add the default group to the list of groups
+ if (!$info['grps'] or !in_array($conf['defaultgroup'], $info['grps'])) {
+ $info['grps'][] = $conf['defaultgroup'];
+ }
+ return $info;
+ }
+
+ /**
+ * Definition of the function modifyUser in order to modify the password
+ *
+ * @param string $user nick of the user to be changed
+ * @param array $changes array of field/value pairs to be changed (password will be clear text)
+ * @return bool true on success, false on error
+ */
+ public function modifyUser($user, $changes)
+ {
+
+ // open the connection to the ldap
+ if (!$this->openLDAP()) {
+ $this->debug('LDAP cannot connect: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ return false;
+ }
+
+ // find the information about the user, in particular the "dn"
+ $info = $this->getUserData($user, true);
+ if (empty($info['dn'])) {
+ $this->debug('LDAP cannot find your user dn', 0, __LINE__, __FILE__);
+ return false;
+ }
+ $dn = $info['dn'];
+
+ // find the old password of the user
+ list($loginuser, $loginsticky, $loginpass) = auth_getCookie();
+ if ($loginuser !== null) { // the user is currently logged in
+ $secret = auth_cookiesalt(!$loginsticky, true);
+ $pass = auth_decrypt($loginpass, $secret);
+
+ // bind with the ldap
+ if (!@ldap_bind($this->con, $dn, $pass)) {
+ $this->debug(
+ 'LDAP user bind failed: ' . hsc($dn) . ': ' . hsc(ldap_error($this->con)),
+ 0,
+ __LINE__,
+ __FILE__
+ );
+ return false;
+ }
+ } elseif ($this->getConf('binddn') && $this->getConf('bindpw')) {
+ // we are changing the password on behalf of the user (eg: forgotten password)
+ // bind with the superuser ldap
+ if (!@ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw')))) {
+ $this->debug('LDAP bind as superuser: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ return false;
+ }
+ } else {
+ return false; // no otherway
+ }
+
+ // Generate the salted hashed password for LDAP
+ $phash = new \dokuwiki\PassHash();
+ $hash = $phash->hash_ssha($changes['pass']);
+
+ // change the password
+ if (!@ldap_mod_replace($this->con, $dn, array('userpassword' => $hash))) {
+ $this->debug(
+ 'LDAP mod replace failed: ' . hsc($dn) . ': ' . hsc(ldap_error($this->con)),
+ 0,
+ __LINE__,
+ __FILE__
+ );
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Most values in LDAP are case-insensitive
+ *
+ * @return bool
+ */
+ public function isCaseSensitive()
+ {
+ return false;
+ }
+
+ /**
+ * Bulk retrieval of user data
+ *
+ * @param int $start index of first user to be returned
+ * @param int $limit max number of users to be returned
+ * @param array $filter array of field/pattern pairs, null for no filter
+ * @return array of userinfo (refer getUserData for internal userinfo details)
+ * @author Dominik Eckelmann <dokuwiki@cosmocode.de>
+ */
+ public function retrieveUsers($start = 0, $limit = 0, $filter = array())
+ {
+ if (!$this->openLDAP()) return array();
+
+ if (is_null($this->users)) {
+ // Perform the search and grab all their details
+ if ($this->getConf('userfilter')) {
+ $all_filter = str_replace('%{user}', '*', $this->getConf('userfilter'));
+ } else {
+ $all_filter = "(ObjectClass=*)";
+ }
+ $sr = ldap_search($this->con, $this->getConf('usertree'), $all_filter);
+ $entries = ldap_get_entries($this->con, $sr);
+ $users_array = array();
+ $userkey = $this->getConf('userkey');
+ for ($i = 0; $i < $entries["count"]; $i++) {
+ array_push($users_array, $entries[$i][$userkey][0]);
+ }
+ asort($users_array);
+ $result = $users_array;
+ if (!$result) return array();
+ $this->users = array_fill_keys($result, false);
+ }
+ $i = 0;
+ $count = 0;
+ $this->constructPattern($filter);
+ $result = array();
+
+ foreach ($this->users as $user => &$info) {
+ if ($i++ < $start) {
+ continue;
+ }
+ if ($info === false) {
+ $info = $this->getUserData($user);
+ }
+ if ($this->filter($user, $info)) {
+ $result[$user] = $info;
+ if (($limit > 0) && (++$count >= $limit)) break;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Make LDAP filter strings.
+ *
+ * Used by auth_getUserData to make the filter
+ * strings for grouptree and groupfilter
+ *
+ * @param string $filter ldap search filter with placeholders
+ * @param array $placeholders placeholders to fill in
+ * @return string
+ * @author Troels Liebe Bentsen <tlb@rapanden.dk>
+ */
+ protected function makeFilter($filter, $placeholders)
+ {
+ preg_match_all("/%{([^}]+)/", $filter, $matches, PREG_PATTERN_ORDER);
+ //replace each match
+ foreach ($matches[1] as $match) {
+ //take first element if array
+ if (is_array($placeholders[$match])) {
+ $value = $placeholders[$match][0];
+ } else {
+ $value = $placeholders[$match];
+ }
+ $value = $this->filterEscape($value);
+ $filter = str_replace('%{' . $match . '}', $value, $filter);
+ }
+ return $filter;
+ }
+
+ /**
+ * return true if $user + $info match $filter criteria, false otherwise
+ *
+ * @param string $user the user's login name
+ * @param array $info the user's userinfo array
+ * @return bool
+ * @author Chris Smith <chris@jalakai.co.uk>
+ *
+ */
+ protected function filter($user, $info)
+ {
+ foreach ($this->pattern as $item => $pattern) {
+ if ($item == 'user') {
+ if (!preg_match($pattern, $user)) return false;
+ } elseif ($item == 'grps') {
+ if (!count(preg_grep($pattern, $info['grps']))) return false;
+ } else {
+ if (!preg_match($pattern, $info[$item])) return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Set the filter pattern
+ *
+ * @param $filter
+ * @return void
+ * @author Chris Smith <chris@jalakai.co.uk>
+ *
+ */
+ protected function constructPattern($filter)
+ {
+ $this->pattern = array();
+ foreach ($filter as $item => $pattern) {
+ $this->pattern[$item] = '/' . str_replace('/', '\/', $pattern) . '/i'; // allow regex characters
+ }
+ }
+
+ /**
+ * Escape a string to be used in a LDAP filter
+ *
+ * Ported from Perl's Net::LDAP::Util escape_filter_value
+ *
+ * @param string $string
+ * @return string
+ * @author Andreas Gohr
+ */
+ protected function filterEscape($string)
+ {
+ // see https://github.com/adldap/adLDAP/issues/22
+ return preg_replace_callback(
+ '/([\x00-\x1F\*\(\)\\\\])/',
+ function ($matches) {
+ return "\\" . join("", unpack("H2", $matches[1]));
+ },
+ $string
+ );
+ }
+
+ /**
+ * Opens a connection to the configured LDAP server and sets the wanted
+ * option on the connection
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function openLDAP()
+ {
+ if ($this->con) return true; // connection already established
+
+ if ($this->getConf('debug')) {
+ ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, 7);
+ }
+
+ $this->bound = 0;
+
+ $port = $this->getConf('port');
+ $bound = false;
+ $servers = explode(',', $this->getConf('server'));
+ foreach ($servers as $server) {
+ $server = trim($server);
+ $this->con = @ldap_connect($server, $port);
+ if (!$this->con) {
+ continue;
+ }
+
+ /*
+ * When OpenLDAP 2.x.x is used, ldap_connect() will always return a resource as it does
+ * not actually connect but just initializes the connecting parameters. The actual
+ * connect happens with the next calls to ldap_* funcs, usually with ldap_bind().
+ *
+ * So we should try to bind to server in order to check its availability.
+ */
+
+ //set protocol version and dependend options
+ if ($this->getConf('version')) {
+ if (!@ldap_set_option(
+ $this->con,
+ LDAP_OPT_PROTOCOL_VERSION,
+ $this->getConf('version')
+ )
+ ) {
+ msg('Setting LDAP Protocol version ' . $this->getConf('version') . ' failed', -1);
+ $this->debug('LDAP version set: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ } else {
+ //use TLS (needs version 3)
+ if ($this->getConf('starttls')) {
+ if (!@ldap_start_tls($this->con)) {
+ msg('Starting TLS failed', -1);
+ $this->debug('LDAP TLS set: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ }
+ }
+ // needs version 3
+ if ($this->getConf('referrals') > -1) {
+ if (!@ldap_set_option(
+ $this->con,
+ LDAP_OPT_REFERRALS,
+ $this->getConf('referrals')
+ )
+ ) {
+ msg('Setting LDAP referrals failed', -1);
+ $this->debug('LDAP referal set: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ }
+ }
+ }
+ }
+
+ //set deref mode
+ if ($this->getConf('deref')) {
+ if (!@ldap_set_option($this->con, LDAP_OPT_DEREF, $this->getConf('deref'))) {
+ msg('Setting LDAP Deref mode ' . $this->getConf('deref') . ' failed', -1);
+ $this->debug('LDAP deref set: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__);
+ }
+ }
+ /* As of PHP 5.3.0 we can set timeout to speedup skipping of invalid servers */
+ if (defined('LDAP_OPT_NETWORK_TIMEOUT')) {
+ ldap_set_option($this->con, LDAP_OPT_NETWORK_TIMEOUT, 1);
+ }
+
+ if ($this->getConf('binddn') && $this->getConf('bindpw')) {
+ $bound = @ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw')));
+ $this->bound = 2;
+ } else {
+ $bound = @ldap_bind($this->con);
+ }
+ if ($bound) {
+ break;
+ }
+ }
+
+ if (!$bound) {
+ msg("LDAP: couldn't connect to LDAP server", -1);
+ $this->debug(ldap_error($this->con), 0, __LINE__, __FILE__);
+ return false;
+ }
+
+ $this->cando['getUsers'] = true;
+ return true;
+ }
+
+ /**
+ * Wraps around ldap_search, ldap_list or ldap_read depending on $scope
+ *
+ * @param resource $link_identifier
+ * @param string $base_dn
+ * @param string $filter
+ * @param string $scope can be 'base', 'one' or 'sub'
+ * @param null|array $attributes
+ * @param int $attrsonly
+ * @param int $sizelimit
+ * @return resource
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function ldapSearch(
+ $link_identifier,
+ $base_dn,
+ $filter,
+ $scope = 'sub',
+ $attributes = null,
+ $attrsonly = 0,
+ $sizelimit = 0
+ )
+ {
+ if (is_null($attributes)) $attributes = array();
+
+ if ($scope == 'base') {
+ return @ldap_read(
+ $link_identifier,
+ $base_dn,
+ $filter,
+ $attributes,
+ $attrsonly,
+ $sizelimit
+ );
+ } elseif ($scope == 'one') {
+ return @ldap_list(
+ $link_identifier,
+ $base_dn,
+ $filter,
+ $attributes,
+ $attrsonly,
+ $sizelimit
+ );
+ } else {
+ return @ldap_search(
+ $link_identifier,
+ $base_dn,
+ $filter,
+ $attributes,
+ $attrsonly,
+ $sizelimit
+ );
+ }
+ }
+
+ /**
+ * Wrapper around msg() but outputs only when debug is enabled
+ *
+ * @param string $message
+ * @param int $err
+ * @param int $line
+ * @param string $file
+ * @return void
+ */
+ protected function debug($message, $err, $line, $file)
+ {
+ if (!$this->getConf('debug')) return;
+ msg($message, $err, $line, $file);
+ }
+}
diff --git a/platform/www/lib/plugins/authldap/conf/default.php b/platform/www/lib/plugins/authldap/conf/default.php
new file mode 100644
index 0000000..52fa1e6
--- /dev/null
+++ b/platform/www/lib/plugins/authldap/conf/default.php
@@ -0,0 +1,23 @@
+<?php
+
+$conf['server'] = '';
+$conf['port'] = 389;
+$conf['usertree'] = '';
+$conf['grouptree'] = '';
+$conf['userfilter'] = '';
+$conf['groupfilter'] = '';
+$conf['version'] = 2;
+$conf['starttls'] = 0;
+$conf['referrals'] = -1;
+$conf['deref'] = 0;
+$conf['binddn'] = '';
+$conf['bindpw'] = '';
+//$conf['mapping']['name'] unsupported in config manager
+//$conf['mapping']['grps'] unsupported in config manager
+$conf['userscope'] = 'sub';
+$conf['groupscope'] = 'sub';
+$conf['userkey'] = 'uid';
+$conf['groupkey'] = 'cn';
+$conf['debug'] = 0;
+$conf['modPass'] = 1;
+$conf['attributes'] = array();
diff --git a/platform/www/lib/plugins/authldap/conf/metadata.php b/platform/www/lib/plugins/authldap/conf/metadata.php
new file mode 100644
index 0000000..3a58590
--- /dev/null
+++ b/platform/www/lib/plugins/authldap/conf/metadata.php
@@ -0,0 +1,22 @@
+<?php
+$meta['server'] = array('string','_caution' => 'danger');
+$meta['port'] = array('numeric','_caution' => 'danger');
+$meta['usertree'] = array('string','_caution' => 'danger');
+$meta['grouptree'] = array('string','_caution' => 'danger');
+$meta['userfilter'] = array('string','_caution' => 'danger');
+$meta['groupfilter'] = array('string','_caution' => 'danger');
+$meta['version'] = array('numeric','_caution' => 'danger');
+$meta['starttls'] = array('onoff','_caution' => 'danger');
+$meta['referrals'] = array('multichoice','_choices' => array(-1,0,1),'_caution' => 'danger');
+$meta['deref'] = array('multichoice','_choices' => array(0,1,2,3),'_caution' => 'danger');
+$meta['binddn'] = array('string','_caution' => 'danger');
+$meta['bindpw'] = array('password','_caution' => 'danger','_code'=>'base64');
+$meta['attributes'] = array('array');
+//$meta['mapping']['name'] unsupported in config manager
+//$meta['mapping']['grps'] unsupported in config manager
+$meta['userscope'] = array('multichoice','_choices' => array('sub','one','base'),'_caution' => 'danger');
+$meta['groupscope'] = array('multichoice','_choices' => array('sub','one','base'),'_caution' => 'danger');
+$meta['userkey'] = array('string','_caution' => 'danger');
+$meta['groupkey'] = array('string','_caution' => 'danger');
+$meta['debug'] = array('onoff','_caution' => 'security');
+$meta['modPass'] = array('onoff');
diff --git a/platform/www/lib/plugins/authldap/lang/en/lang.php b/platform/www/lib/plugins/authldap/lang/en/lang.php
new file mode 100644
index 0000000..8185a84
--- /dev/null
+++ b/platform/www/lib/plugins/authldap/lang/en/lang.php
@@ -0,0 +1,11 @@
+<?php
+/**
+ * English language file for authldap plugin
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ */
+
+$lang['connectfail'] = 'LDAP cannot connect: %s';
+$lang['domainfail'] = 'LDAP cannot find your user dn';
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/authldap/lang/en/settings.php b/platform/www/lib/plugins/authldap/lang/en/settings.php
new file mode 100644
index 0000000..9988474
--- /dev/null
+++ b/platform/www/lib/plugins/authldap/lang/en/settings.php
@@ -0,0 +1,30 @@
+<?php
+$lang['server'] = 'Your LDAP server. Either hostname (<code>localhost</code>) or full qualified URL (<code>ldap://server.tld:389</code>)';
+$lang['port'] = 'LDAP server port if no full URL was given above';
+$lang['usertree'] = 'Where to find the user accounts. Eg. <code>ou=People, dc=server, dc=tld</code>';
+$lang['grouptree'] = 'Where to find the user groups. Eg. <code>ou=Group, dc=server, dc=tld</code>';
+$lang['userfilter'] = 'LDAP filter to search for user accounts. Eg. <code>(&amp;(uid=%{user})(objectClass=posixAccount))</code>';
+$lang['groupfilter'] = 'LDAP filter to search for groups. Eg. <code>(&amp;(objectClass=posixGroup)(|(gidNumber=%{gid})(memberUID=%{user})))</code>';
+$lang['version'] = 'The protocol version to use. You may need to set this to <code>3</code>';
+$lang['starttls'] = 'Use TLS connections?';
+$lang['referrals'] = 'Shall referrals be followed?';
+$lang['deref'] = 'How to dereference aliases?';
+$lang['binddn'] = 'DN of an optional bind user if anonymous bind is not sufficient. Eg. <code>cn=admin, dc=my, dc=home</code>';
+$lang['bindpw'] = 'Password of above user';
+$lang['attributes'] = 'Attributes to retrieve with the LDAP search.';
+$lang['userscope'] = 'Limit search scope for user search';
+$lang['groupscope'] = 'Limit search scope for group search';
+$lang['userkey'] = 'Attribute denoting the username; must be consistent to userfilter.';
+$lang['groupkey'] = 'Group membership from any user attribute (instead of standard AD groups) e.g. group from department or telephone number';
+$lang['modPass'] = 'Can the LDAP password be changed via dokuwiki?';
+$lang['debug'] = 'Display additional debug information on errors';
+
+
+$lang['deref_o_0'] = 'LDAP_DEREF_NEVER';
+$lang['deref_o_1'] = 'LDAP_DEREF_SEARCHING';
+$lang['deref_o_2'] = 'LDAP_DEREF_FINDING';
+$lang['deref_o_3'] = 'LDAP_DEREF_ALWAYS';
+
+$lang['referrals_o_-1'] = 'use default';
+$lang['referrals_o_0'] = 'don\'t follow referrals';
+$lang['referrals_o_1'] = 'follow referrals'; \ No newline at end of file
diff --git a/platform/www/lib/plugins/authldap/plugin.info.txt b/platform/www/lib/plugins/authldap/plugin.info.txt
new file mode 100644
index 0000000..e0c6144
--- /dev/null
+++ b/platform/www/lib/plugins/authldap/plugin.info.txt
@@ -0,0 +1,7 @@
+base authldap
+author Andreas Gohr
+email andi@splitbrain.org
+date 2015-07-13
+name LDAP Auth Plugin
+desc Provides user authentication against an LDAP server
+url http://www.dokuwiki.org/plugin:authldap
diff --git a/platform/www/lib/plugins/authpdo/README b/platform/www/lib/plugins/authpdo/README
new file mode 100644
index 0000000..c99bfbf
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/README
@@ -0,0 +1,27 @@
+authpdo Plugin for DokuWiki
+
+Authenticate against a database via PDO
+
+All documentation for this plugin can be found at
+https://www.dokuwiki.org/plugin:authpdo
+
+If you install this plugin manually, make sure it is installed in
+lib/plugins/authpdo/ - if the folder is called different it
+will not work!
+
+Please refer to http://www.dokuwiki.org/plugins for additional info
+on how to install plugins in DokuWiki.
+
+----
+Copyright (C) Andreas Gohr <andi@splitbrain.org>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; version 2 of the License
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+See the COPYING file in your DokuWiki folder for details
diff --git a/platform/www/lib/plugins/authpdo/auth.php b/platform/www/lib/plugins/authpdo/auth.php
new file mode 100644
index 0000000..9c0968e
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/auth.php
@@ -0,0 +1,826 @@
+<?php
+/**
+ * DokuWiki Plugin authpdo (Auth Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+/**
+ * Class auth_plugin_authpdo
+ */
+class auth_plugin_authpdo extends DokuWiki_Auth_Plugin
+{
+
+ /** @var PDO */
+ protected $pdo;
+
+ /** @var null|array The list of all groups */
+ protected $groupcache = null;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct(); // for compatibility
+
+ if (!class_exists('PDO')) {
+ $this->debugMsg('PDO extension for PHP not found.', -1, __LINE__);
+ $this->success = false;
+ return;
+ }
+
+ if (!$this->getConf('dsn')) {
+ $this->debugMsg('No DSN specified', -1, __LINE__);
+ $this->success = false;
+ return;
+ }
+
+ try {
+ $this->pdo = new PDO(
+ $this->getConf('dsn'),
+ $this->getConf('user'),
+ conf_decodeString($this->getConf('pass')),
+ array(
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // always fetch as array
+ PDO::ATTR_EMULATE_PREPARES => true, // emulating prepares allows us to reuse param names
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // we want exceptions, not error codes
+ )
+ );
+ } catch (PDOException $e) {
+ $this->debugMsg($e);
+ msg($this->getLang('connectfail'), -1);
+ $this->success = false;
+ return;
+ }
+
+ // can Users be created?
+ $this->cando['addUser'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'select-groups',
+ 'insert-user',
+ 'insert-group',
+ 'join-group'
+ )
+ );
+
+ // can Users be deleted?
+ $this->cando['delUser'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'select-groups',
+ 'leave-group',
+ 'delete-user'
+ )
+ );
+
+ // can login names be changed?
+ $this->cando['modLogin'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'update-user-login'
+ )
+ );
+
+ // can passwords be changed?
+ $this->cando['modPass'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'update-user-pass'
+ )
+ );
+
+ // can real names be changed?
+ $this->cando['modName'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'update-user-info:name'
+ )
+ );
+
+ // can real email be changed?
+ $this->cando['modMail'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'update-user-info:mail'
+ )
+ );
+
+ // can groups be changed?
+ $this->cando['modGroups'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'select-groups',
+ 'leave-group',
+ 'join-group',
+ 'insert-group'
+ )
+ );
+
+ // can a filtered list of users be retrieved?
+ $this->cando['getUsers'] = $this->checkConfig(
+ array(
+ 'list-users'
+ )
+ );
+
+ // can the number of users be retrieved?
+ $this->cando['getUserCount'] = $this->checkConfig(
+ array(
+ 'count-users'
+ )
+ );
+
+ // can a list of available groups be retrieved?
+ $this->cando['getGroups'] = $this->checkConfig(
+ array(
+ 'select-groups'
+ )
+ );
+
+ $this->success = true;
+ }
+
+ /**
+ * Check user+password
+ *
+ * @param string $user the user name
+ * @param string $pass the clear text password
+ * @return bool
+ */
+ public function checkPass($user, $pass)
+ {
+
+ $userdata = $this->selectUser($user);
+ if ($userdata == false) return false;
+
+ // password checking done in SQL?
+ if ($this->checkConfig(array('check-pass'))) {
+ $userdata['clear'] = $pass;
+ $userdata['hash'] = auth_cryptPassword($pass);
+ $result = $this->query($this->getConf('check-pass'), $userdata);
+ if ($result === false) return false;
+ return (count($result) == 1);
+ }
+
+ // we do password checking on our own
+ if (isset($userdata['hash'])) {
+ // hashed password
+ $passhash = new \dokuwiki\PassHash();
+ return $passhash->verify_hash($pass, $userdata['hash']);
+ } else {
+ // clear text password in the database O_o
+ return ($pass === $userdata['clear']);
+ }
+ }
+
+ /**
+ * Return user info
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ *
+ * name string full name of the user
+ * mail string email addres of the user
+ * grps array list of groups the user is in
+ *
+ * @param string $user the user name
+ * @param bool $requireGroups whether or not the returned data must include groups
+ * @return array|bool containing user data or false
+ */
+ public function getUserData($user, $requireGroups = true)
+ {
+ $data = $this->selectUser($user);
+ if ($data == false) return false;
+
+ if (isset($data['hash'])) unset($data['hash']);
+ if (isset($data['clean'])) unset($data['clean']);
+
+ if ($requireGroups) {
+ $data['grps'] = $this->selectUserGroups($data);
+ if ($data['grps'] === false) return false;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Create a new User [implement only where required/possible]
+ *
+ * Returns false if the user already exists, null when an error
+ * occurred and true if everything went well.
+ *
+ * The new user HAS TO be added to the default group by this
+ * function!
+ *
+ * Set addUser capability when implemented
+ *
+ * @param string $user
+ * @param string $clear
+ * @param string $name
+ * @param string $mail
+ * @param null|array $grps
+ * @return bool|null
+ */
+ public function createUser($user, $clear, $name, $mail, $grps = null)
+ {
+ global $conf;
+
+ if (($info = $this->getUserData($user, false)) !== false) {
+ msg($this->getLang('userexists'), -1);
+ return false; // user already exists
+ }
+
+ // prepare data
+ if ($grps == null) $grps = array();
+ array_unshift($grps, $conf['defaultgroup']);
+ $grps = array_unique($grps);
+ $hash = auth_cryptPassword($clear);
+ $userdata = compact('user', 'clear', 'hash', 'name', 'mail');
+
+ // action protected by transaction
+ $this->pdo->beginTransaction();
+ {
+ // insert the user
+ $ok = $this->query($this->getConf('insert-user'), $userdata);
+ if ($ok === false) goto FAIL;
+ $userdata = $this->getUserData($user, false);
+ if ($userdata === false) goto FAIL;
+
+ // create all groups that do not exist, the refetch the groups
+ $allgroups = $this->selectGroups();
+ foreach ($grps as $group) {
+ if (!isset($allgroups[$group])) {
+ $ok = $this->addGroup($group);
+ if ($ok === false) goto FAIL;
+ }
+ }
+ $allgroups = $this->selectGroups();
+
+ // add user to the groups
+ foreach ($grps as $group) {
+ $ok = $this->joinGroup($userdata, $allgroups[$group]);
+ if ($ok === false) goto FAIL;
+ }
+ }
+ $this->pdo->commit();
+ return true;
+
+ // something went wrong, rollback
+ FAIL:
+ $this->pdo->rollBack();
+ $this->debugMsg('Transaction rolled back', 0, __LINE__);
+ msg($this->getLang('writefail'), -1);
+ return null; // return error
+ }
+
+ /**
+ * Modify user data
+ *
+ * @param string $user nick of the user to be changed
+ * @param array $changes array of field/value pairs to be changed (password will be clear text)
+ * @return bool
+ */
+ public function modifyUser($user, $changes)
+ {
+ // secure everything in transaction
+ $this->pdo->beginTransaction();
+ {
+ $olddata = $this->getUserData($user);
+ $oldgroups = $olddata['grps'];
+ unset($olddata['grps']);
+
+ // changing the user name?
+ if (isset($changes['user'])) {
+ if ($this->getUserData($changes['user'], false)) goto FAIL;
+ $params = $olddata;
+ $params['newlogin'] = $changes['user'];
+
+ $ok = $this->query($this->getConf('update-user-login'), $params);
+ if ($ok === false) goto FAIL;
+ }
+
+ // changing the password?
+ if (isset($changes['pass'])) {
+ $params = $olddata;
+ $params['clear'] = $changes['pass'];
+ $params['hash'] = auth_cryptPassword($changes['pass']);
+
+ $ok = $this->query($this->getConf('update-user-pass'), $params);
+ if ($ok === false) goto FAIL;
+ }
+
+ // changing info?
+ if (isset($changes['mail']) || isset($changes['name'])) {
+ $params = $olddata;
+ if (isset($changes['mail'])) $params['mail'] = $changes['mail'];
+ if (isset($changes['name'])) $params['name'] = $changes['name'];
+
+ $ok = $this->query($this->getConf('update-user-info'), $params);
+ if ($ok === false) goto FAIL;
+ }
+
+ // changing groups?
+ if (isset($changes['grps'])) {
+ $allgroups = $this->selectGroups();
+
+ // remove membership for previous groups
+ foreach ($oldgroups as $group) {
+ if (!in_array($group, $changes['grps']) && isset($allgroups[$group])) {
+ $ok = $this->leaveGroup($olddata, $allgroups[$group]);
+ if ($ok === false) goto FAIL;
+ }
+ }
+
+ // create all new groups that are missing
+ $added = 0;
+ foreach ($changes['grps'] as $group) {
+ if (!isset($allgroups[$group])) {
+ $ok = $this->addGroup($group);
+ if ($ok === false) goto FAIL;
+ $added++;
+ }
+ }
+ // reload group info
+ if ($added > 0) $allgroups = $this->selectGroups();
+
+ // add membership for new groups
+ foreach ($changes['grps'] as $group) {
+ if (!in_array($group, $oldgroups)) {
+ $ok = $this->joinGroup($olddata, $allgroups[$group]);
+ if ($ok === false) goto FAIL;
+ }
+ }
+ }
+
+ }
+ $this->pdo->commit();
+ return true;
+
+ // something went wrong, rollback
+ FAIL:
+ $this->pdo->rollBack();
+ $this->debugMsg('Transaction rolled back', 0, __LINE__);
+ msg($this->getLang('writefail'), -1);
+ return false; // return error
+ }
+
+ /**
+ * Delete one or more users
+ *
+ * Set delUser capability when implemented
+ *
+ * @param array $users
+ * @return int number of users deleted
+ */
+ public function deleteUsers($users)
+ {
+ $count = 0;
+ foreach ($users as $user) {
+ if ($this->deleteUser($user)) $count++;
+ }
+ return $count;
+ }
+
+ /**
+ * Bulk retrieval of user data [implement only where required/possible]
+ *
+ * Set getUsers capability when implemented
+ *
+ * @param int $start index of first user to be returned
+ * @param int $limit max number of users to be returned
+ * @param array $filter array of field/pattern pairs, null for no filter
+ * @return array list of userinfo (refer getUserData for internal userinfo details)
+ */
+ public function retrieveUsers($start = 0, $limit = -1, $filter = null)
+ {
+ if ($limit < 0) $limit = 10000; // we don't support no limit
+ if (is_null($filter)) $filter = array();
+
+ if (isset($filter['grps'])) $filter['group'] = $filter['grps'];
+ foreach (array('user', 'name', 'mail', 'group') as $key) {
+ if (!isset($filter[$key])) {
+ $filter[$key] = '%';
+ } else {
+ $filter[$key] = '%' . $filter[$key] . '%';
+ }
+ }
+ $filter['start'] = (int)$start;
+ $filter['end'] = (int)$start + $limit;
+ $filter['limit'] = (int)$limit;
+
+ $result = $this->query($this->getConf('list-users'), $filter);
+ if (!$result) return array();
+ $users = array();
+ if (is_array($result)) {
+ foreach ($result as $row) {
+ if (!isset($row['user'])) {
+ $this->debugMsg("list-users statement did not return 'user' attribute", -1, __LINE__);
+ return array();
+ }
+ $users[] = $this->getUserData($row['user']);
+ }
+ } else {
+ $this->debugMsg("list-users statement did not return a list of result", -1, __LINE__);
+ }
+ return $users;
+ }
+
+ /**
+ * Return a count of the number of user which meet $filter criteria
+ *
+ * @param array $filter array of field/pattern pairs, empty array for no filter
+ * @return int
+ */
+ public function getUserCount($filter = array())
+ {
+ if (is_null($filter)) $filter = array();
+
+ if (isset($filter['grps'])) $filter['group'] = $filter['grps'];
+ foreach (array('user', 'name', 'mail', 'group') as $key) {
+ if (!isset($filter[$key])) {
+ $filter[$key] = '%';
+ } else {
+ $filter[$key] = '%' . $filter[$key] . '%';
+ }
+ }
+
+ $result = $this->query($this->getConf('count-users'), $filter);
+ if (!$result || !isset($result[0]['count'])) {
+ $this->debugMsg("Statement did not return 'count' attribute", -1, __LINE__);
+ }
+ return (int)$result[0]['count'];
+ }
+
+ /**
+ * Create a new group with the given name
+ *
+ * @param string $group
+ * @return bool
+ */
+ public function addGroup($group)
+ {
+ $sql = $this->getConf('insert-group');
+
+ $result = $this->query($sql, array(':group' => $group));
+ $this->clearGroupCache();
+ if ($result === false) return false;
+ return true;
+ }
+
+ /**
+ * Retrieve groups
+ *
+ * Set getGroups capability when implemented
+ *
+ * @param int $start
+ * @param int $limit
+ * @return array
+ */
+ public function retrieveGroups($start = 0, $limit = 0)
+ {
+ $groups = array_keys($this->selectGroups());
+ if ($groups === false) return array();
+
+ if (!$limit) {
+ return array_splice($groups, $start);
+ } else {
+ return array_splice($groups, $start, $limit);
+ }
+ }
+
+ /**
+ * Select data of a specified user
+ *
+ * @param string $user the user name
+ * @return bool|array user data, false on error
+ */
+ protected function selectUser($user)
+ {
+ $sql = $this->getConf('select-user');
+
+ $result = $this->query($sql, array(':user' => $user));
+ if (!$result) return false;
+
+ if (count($result) > 1) {
+ $this->debugMsg('Found more than one matching user', -1, __LINE__);
+ return false;
+ }
+
+ $data = array_shift($result);
+ $dataok = true;
+
+ if (!isset($data['user'])) {
+ $this->debugMsg("Statement did not return 'user' attribute", -1, __LINE__);
+ $dataok = false;
+ }
+ if (!isset($data['hash']) && !isset($data['clear']) && !$this->checkConfig(array('check-pass'))) {
+ $this->debugMsg("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__);
+ $dataok = false;
+ }
+ if (!isset($data['name'])) {
+ $this->debugMsg("Statement did not return 'name' attribute", -1, __LINE__);
+ $dataok = false;
+ }
+ if (!isset($data['mail'])) {
+ $this->debugMsg("Statement did not return 'mail' attribute", -1, __LINE__);
+ $dataok = false;
+ }
+
+ if (!$dataok) return false;
+ return $data;
+ }
+
+ /**
+ * Delete a user after removing all their group memberships
+ *
+ * @param string $user
+ * @return bool true when the user was deleted
+ */
+ protected function deleteUser($user)
+ {
+ $this->pdo->beginTransaction();
+ {
+ $userdata = $this->getUserData($user);
+ if ($userdata === false) goto FAIL;
+ $allgroups = $this->selectGroups();
+
+ // remove group memberships (ignore errors)
+ foreach ($userdata['grps'] as $group) {
+ if (isset($allgroups[$group])) {
+ $this->leaveGroup($userdata, $allgroups[$group]);
+ }
+ }
+
+ $ok = $this->query($this->getConf('delete-user'), $userdata);
+ if ($ok === false) goto FAIL;
+ }
+ $this->pdo->commit();
+ return true;
+
+ FAIL:
+ $this->pdo->rollBack();
+ return false;
+ }
+
+ /**
+ * Select all groups of a user
+ *
+ * @param array $userdata The userdata as returned by _selectUser()
+ * @return array|bool list of group names, false on error
+ */
+ protected function selectUserGroups($userdata)
+ {
+ global $conf;
+ $sql = $this->getConf('select-user-groups');
+ $result = $this->query($sql, $userdata);
+ if ($result === false) return false;
+
+ $groups = array($conf['defaultgroup']); // always add default config
+ if (is_array($result)) {
+ foreach ($result as $row) {
+ if (!isset($row['group'])) {
+ $this->debugMsg("No 'group' field returned in select-user-groups statement", -1, __LINE__);
+ return false;
+ }
+ $groups[] = $row['group'];
+ }
+ } else {
+ $this->debugMsg("select-user-groups statement did not return a list of result", -1, __LINE__);
+ }
+
+ $groups = array_unique($groups);
+ sort($groups);
+ return $groups;
+ }
+
+ /**
+ * Select all available groups
+ *
+ * @return array|bool list of all available groups and their properties
+ */
+ protected function selectGroups()
+ {
+ if ($this->groupcache) return $this->groupcache;
+
+ $sql = $this->getConf('select-groups');
+ $result = $this->query($sql);
+ if ($result === false) return false;
+
+ $groups = array();
+ if (is_array($result)) {
+ foreach ($result as $row) {
+ if (!isset($row['group'])) {
+ $this->debugMsg("No 'group' field returned from select-groups statement", -1, __LINE__);
+ return false;
+ }
+
+ // relayout result with group name as key
+ $group = $row['group'];
+ $groups[$group] = $row;
+ }
+ } else {
+ $this->debugMsg("select-groups statement did not return a list of result", -1, __LINE__);
+ }
+
+ ksort($groups);
+ return $groups;
+ }
+
+ /**
+ * Remove all entries from the group cache
+ */
+ protected function clearGroupCache()
+ {
+ $this->groupcache = null;
+ }
+
+ /**
+ * Adds the user to the group
+ *
+ * @param array $userdata all the user data
+ * @param array $groupdata all the group data
+ * @return bool
+ */
+ protected function joinGroup($userdata, $groupdata)
+ {
+ $data = array_merge($userdata, $groupdata);
+ $sql = $this->getConf('join-group');
+ $result = $this->query($sql, $data);
+ if ($result === false) return false;
+ return true;
+ }
+
+ /**
+ * Removes the user from the group
+ *
+ * @param array $userdata all the user data
+ * @param array $groupdata all the group data
+ * @return bool
+ */
+ protected function leaveGroup($userdata, $groupdata)
+ {
+ $data = array_merge($userdata, $groupdata);
+ $sql = $this->getConf('leave-group');
+ $result = $this->query($sql, $data);
+ if ($result === false) return false;
+ return true;
+ }
+
+ /**
+ * Executes a query
+ *
+ * @param string $sql The SQL statement to execute
+ * @param array $arguments Named parameters to be used in the statement
+ * @return array|int|bool The result as associative array for SELECTs, affected rows for others, false on error
+ */
+ protected function query($sql, $arguments = array())
+ {
+ $sql = trim($sql);
+ if (empty($sql)) {
+ $this->debugMsg('No SQL query given', -1, __LINE__);
+ return false;
+ }
+
+ // execute
+ $params = array();
+ $sth = $this->pdo->prepare($sql);
+ $result = false;
+ try {
+ // prepare parameters - we only use those that exist in the SQL
+ foreach ($arguments as $key => $value) {
+ if (is_array($value)) continue;
+ if (is_object($value)) continue;
+ if ($key[0] != ':') $key = ":$key"; // prefix with colon if needed
+ if (strpos($sql, $key) === false) continue; // skip if parameter is missing
+
+ if (is_int($value)) {
+ $sth->bindValue($key, $value, PDO::PARAM_INT);
+ } else {
+ $sth->bindValue($key, $value);
+ }
+ $params[$key] = $value; //remember for debugging
+ }
+
+ $sth->execute();
+ // only report last line's result
+ $hasnextrowset = true;
+ $currentsql = $sql;
+ while ($hasnextrowset) {
+ if (strtolower(substr($currentsql, 0, 6)) == 'select') {
+ $result = $sth->fetchAll();
+ } else {
+ $result = $sth->rowCount();
+ }
+ $semi_pos = strpos($currentsql, ';');
+ if ($semi_pos) {
+ $currentsql = trim(substr($currentsql, $semi_pos + 1));
+ }
+ try {
+ $hasnextrowset = $sth->nextRowset(); // run next rowset
+ } catch (PDOException $rowset_e) {
+ $hasnextrowset = false; // driver does not support multi-rowset, should be executed in one time
+ }
+ }
+ } catch (Exception $e) {
+ // report the caller's line
+ $trace = debug_backtrace();
+ $line = $trace[0]['line'];
+ $dsql = $this->debugSQL($sql, $params, !defined('DOKU_UNITTEST'));
+ $this->debugMsg($e, -1, $line);
+ $this->debugMsg("SQL: <pre>$dsql</pre>", -1, $line);
+ }
+ $sth->closeCursor();
+ $sth = null;
+
+ return $result;
+ }
+
+ /**
+ * Wrapper around msg() but outputs only when debug is enabled
+ *
+ * @param string|Exception $message
+ * @param int $err
+ * @param int $line
+ */
+ protected function debugMsg($message, $err = 0, $line = 0)
+ {
+ if (!$this->getConf('debug')) return;
+ if (is_a($message, 'Exception')) {
+ $err = -1;
+ $msg = $message->getMessage();
+ if (!$line) $line = $message->getLine();
+ } else {
+ $msg = $message;
+ }
+
+ if (defined('DOKU_UNITTEST')) {
+ printf("\n%s, %s:%d\n", $msg, __FILE__, $line);
+ } else {
+ msg('authpdo: ' . $msg, $err, $line, __FILE__);
+ }
+ }
+
+ /**
+ * Check if the given config strings are set
+ *
+ * @param string[] $keys
+ * @return bool
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ *
+ */
+ protected function checkConfig($keys)
+ {
+ foreach ($keys as $key) {
+ $params = explode(':', $key);
+ $key = array_shift($params);
+ $sql = trim($this->getConf($key));
+
+ // check if sql is set
+ if (!$sql) return false;
+ // check if needed params are there
+ foreach ($params as $param) {
+ if (strpos($sql, ":$param") === false) return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * create an approximation of the SQL string with parameters replaced
+ *
+ * @param string $sql
+ * @param array $params
+ * @param bool $htmlescape Should the result be escaped for output in HTML?
+ * @return string
+ */
+ protected function debugSQL($sql, $params, $htmlescape = true)
+ {
+ foreach ($params as $key => $val) {
+ if (is_int($val)) {
+ $val = $this->pdo->quote($val, PDO::PARAM_INT);
+ } elseif (is_bool($val)) {
+ $val = $this->pdo->quote($val, PDO::PARAM_BOOL);
+ } elseif (is_null($val)) {
+ $val = 'NULL';
+ } else {
+ $val = $this->pdo->quote($val);
+ }
+ $sql = str_replace($key, $val, $sql);
+ }
+ if ($htmlescape) $sql = hsc($sql);
+ return $sql;
+ }
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/authpdo/conf/default.php b/platform/www/lib/plugins/authpdo/conf/default.php
new file mode 100644
index 0000000..138ca2f
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/conf/default.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Default settings for the authpdo plugin
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+$conf['debug'] = 0;
+$conf['dsn'] = '';
+$conf['user'] = '';
+$conf['pass'] = '';
+
+/**
+ * statement to select a single user identified by its login name
+ *
+ * input: :user
+ * return: user, name, mail, (clear|hash), [uid], [*]
+ */
+$conf['select-user'] = '';
+
+/**
+ * statement to check the password in SQL, optional when above returned clear or hash
+ *
+ * input: :user, :clear, :hash, [uid], [*]
+ * return: *
+ */
+$conf['check-pass'] = '';
+
+/**
+ * statement to select a single user identified by its login name
+ *
+ * input: :user, [uid]
+ * return: group
+ */
+$conf['select-user-groups'] = '';
+
+/**
+ * Select all the existing group names
+ *
+ * return: group, [gid], [*]
+ */
+$conf['select-groups'] = '';
+
+/**
+ * Create a new user
+ *
+ * input: :user, :name, :mail, (:clear|:hash)
+ */
+$conf['insert-user'] = '';
+
+/**
+ * Remove a user
+ *
+ * input: :user, [:uid], [*]
+ */
+$conf['delete-user'] = '';
+
+/**
+ * list user names matching the given criteria
+ *
+ * Make sure the list is distinct and sorted by user name. Apply the given limit and offset
+ *
+ * input: :user, :name, :mail, :group, :start, :end, :limit
+ * out: user
+ */
+$conf['list-users'] = '';
+
+/**
+ * count user names matching the given criteria
+ *
+ * Make sure the counted list is distinct
+ *
+ * input: :user, :name, :mail, :group
+ * out: count
+ */
+$conf['count-users'] = '';
+
+/**
+ * Update user data (except password and user name)
+ *
+ * input: :user, :name, :mail, [:uid], [*]
+ */
+$conf['update-user-info'] = '';
+
+/**
+ * Update user name aka login
+ *
+ * input: :user, :newlogin, [:uid], [*]
+ */
+$conf['update-user-login'] = '';
+
+/**
+ * Update user password
+ *
+ * input: :user, :clear, :hash, [:uid], [*]
+ */
+$conf['update-user-pass'] = '';
+
+/**
+ * Create a new group
+ *
+ * input: :group
+ */
+$conf['insert-group'] = '';
+
+/**
+ * Make user join group
+ *
+ * input: :user, [:uid], group, [:gid], [*]
+ */
+$conf['join-group'] = '';
+
+/**
+ * Make user leave group
+ *
+ * input: :user, [:uid], group, [:gid], [*]
+ */
+$conf['leave-group'] = '';
diff --git a/platform/www/lib/plugins/authpdo/conf/metadata.php b/platform/www/lib/plugins/authpdo/conf/metadata.php
new file mode 100644
index 0000000..34e60a4
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/conf/metadata.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Options for the authpdo plugin
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+$meta['debug'] = array('onoff', '_caution' => 'security');
+$meta['dsn'] = array('string', '_caution' => 'danger');
+$meta['user'] = array('string', '_caution' => 'danger');
+$meta['pass'] = array('password', '_caution' => 'danger', '_code' => 'base64');
+$meta['select-user'] = array('', '_caution' => 'danger');
+$meta['check-pass'] = array('', '_caution' => 'danger');
+$meta['select-user-groups'] = array('', '_caution' => 'danger');
+$meta['select-groups'] = array('', '_caution' => 'danger');
+$meta['insert-user'] = array('', '_caution' => 'danger');
+$meta['delete-user'] = array('', '_caution' => 'danger');
+$meta['list-users'] = array('', '_caution' => 'danger');
+$meta['count-users'] = array('', '_caution' => 'danger');
+$meta['update-user-info'] = array('', '_caution' => 'danger');
+$meta['update-user-login'] = array('', '_caution' => 'danger');
+$meta['update-user-pass'] = array('', '_caution' => 'danger');
+$meta['insert-group'] = array('', '_caution' => 'danger');
+$meta['join-group'] = array('', '_caution' => 'danger');
+$meta['leave-group'] = array('', '_caution' => 'danger');
diff --git a/platform/www/lib/plugins/authpdo/lang/en/lang.php b/platform/www/lib/plugins/authpdo/lang/en/lang.php
new file mode 100644
index 0000000..3e1482e
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/lang/en/lang.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * English language file for authpdo plugin
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ */
+
+$lang['connectfail'] = 'Failed to connect to database.';
+$lang['userexists'] = 'Sorry, a user with this login already exists.';
+$lang['writefail'] = 'Unable to modify user data. Please inform the Wiki-Admin';
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/authpdo/lang/en/settings.php b/platform/www/lib/plugins/authpdo/lang/en/settings.php
new file mode 100644
index 0000000..1aaaec0
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/lang/en/settings.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * english language file for authpdo plugin
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+$lang['debug'] = 'Print out detailed error messages. Should be disabled after setup.';
+$lang['dsn'] = 'The DSN to connect to the database.';
+$lang['user'] = 'The user for the above database connection (empty for sqlite)';
+$lang['pass'] = 'The password for the above database connection (empty for sqlite)';
+$lang['select-user'] = 'SQL Statement to select the data of a single user';
+$lang['select-user-groups'] = 'SQL Statement to select all groups of a single user';
+$lang['select-groups'] = 'SQL Statement to select all available groups';
+$lang['insert-user'] = 'SQL Statement to insert a new user into the database';
+$lang['delete-user'] = 'SQL Statement to remove a single user from the database';
+$lang['list-users'] = 'SQL Statement to list users matching a filter';
+$lang['count-users'] = 'SQL Statement to count users matching a filter';
+$lang['update-user-info'] = 'SQL Statement to update the full name and email address of a single user';
+$lang['update-user-login'] = 'SQL Statement to update the login name of a single user';
+$lang['update-user-pass'] = 'SQL Statement to update the password of a single user';
+$lang['insert-group'] = 'SQL Statement to insert a new group into the database';
+$lang['join-group'] = 'SQL Statement to add a user to an existing group';
+$lang['leave-group'] = 'SQL Statement to remove a user from a group';
+$lang['check-pass'] = 'SQL Statement to check the password for a user. Can be left empty if password info is fetched in select-user.';
diff --git a/platform/www/lib/plugins/authpdo/plugin.info.txt b/platform/www/lib/plugins/authpdo/plugin.info.txt
new file mode 100644
index 0000000..e60ff0b
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/plugin.info.txt
@@ -0,0 +1,7 @@
+base authpdo
+author Andreas Gohr
+email andi@splitbrain.org
+date 2016-08-20
+name authpdo plugin
+desc Authenticate against a database via PDO
+url https://www.dokuwiki.org/plugin:authpdo
diff --git a/platform/www/lib/plugins/authplain/auth.php b/platform/www/lib/plugins/authplain/auth.php
new file mode 100644
index 0000000..421af88
--- /dev/null
+++ b/platform/www/lib/plugins/authplain/auth.php
@@ -0,0 +1,494 @@
+<?php
+
+/**
+ * Plaintext authentication backend
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Jan Schumann <js@schumann-it.com>
+ */
+class auth_plugin_authplain extends DokuWiki_Auth_Plugin
+{
+ /** @var array user cache */
+ protected $users = null;
+
+ /** @var array filter pattern */
+ protected $pattern = array();
+
+ /** @var bool safe version of preg_split */
+ protected $pregsplit_safe = false;
+
+ /**
+ * Constructor
+ *
+ * Carry out sanity checks to ensure the object is
+ * able to operate. Set capabilities.
+ *
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ global $config_cascade;
+
+ if (!@is_readable($config_cascade['plainauth.users']['default'])) {
+ $this->success = false;
+ } else {
+ if (@is_writable($config_cascade['plainauth.users']['default'])) {
+ $this->cando['addUser'] = true;
+ $this->cando['delUser'] = true;
+ $this->cando['modLogin'] = true;
+ $this->cando['modPass'] = true;
+ $this->cando['modName'] = true;
+ $this->cando['modMail'] = true;
+ $this->cando['modGroups'] = true;
+ }
+ $this->cando['getUsers'] = true;
+ $this->cando['getUserCount'] = true;
+ $this->cando['getGroups'] = true;
+ }
+
+ $this->pregsplit_safe = version_compare(PCRE_VERSION, '6.7', '>=');
+ }
+
+ /**
+ * Check user+password
+ *
+ * Checks if the given user exists and the given
+ * plaintext password is correct
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $user
+ * @param string $pass
+ * @return bool
+ */
+ public function checkPass($user, $pass)
+ {
+ $userinfo = $this->getUserData($user);
+ if ($userinfo === false) return false;
+
+ return auth_verifyPassword($pass, $this->users[$user]['pass']);
+ }
+
+ /**
+ * Return user info
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ *
+ * name string full name of the user
+ * mail string email addres of the user
+ * grps array list of groups the user is in
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $user
+ * @param bool $requireGroups (optional) ignored by this plugin, grps info always supplied
+ * @return array|false
+ */
+ public function getUserData($user, $requireGroups = true)
+ {
+ if ($this->users === null) $this->loadUserData();
+ return isset($this->users[$user]) ? $this->users[$user] : false;
+ }
+
+ /**
+ * Creates a string suitable for saving as a line
+ * in the file database
+ * (delimiters escaped, etc.)
+ *
+ * @param string $user
+ * @param string $pass
+ * @param string $name
+ * @param string $mail
+ * @param array $grps list of groups the user is in
+ * @return string
+ */
+ protected function createUserLine($user, $pass, $name, $mail, $grps)
+ {
+ $groups = join(',', $grps);
+ $userline = array($user, $pass, $name, $mail, $groups);
+ $userline = str_replace('\\', '\\\\', $userline); // escape \ as \\
+ $userline = str_replace(':', '\\:', $userline); // escape : as \:
+ $userline = join(':', $userline)."\n";
+ return $userline;
+ }
+
+ /**
+ * Create a new User
+ *
+ * Returns false if the user already exists, null when an error
+ * occurred and true if everything went well.
+ *
+ * The new user will be added to the default group by this
+ * function if grps are not specified (default behaviour).
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ *
+ * @param string $user
+ * @param string $pwd
+ * @param string $name
+ * @param string $mail
+ * @param array $grps
+ * @return bool|null|string
+ */
+ public function createUser($user, $pwd, $name, $mail, $grps = null)
+ {
+ global $conf;
+ global $config_cascade;
+
+ // user mustn't already exist
+ if ($this->getUserData($user) !== false) {
+ msg($this->getLang('userexists'), -1);
+ return false;
+ }
+
+ $pass = auth_cryptPassword($pwd);
+
+ // set default group if no groups specified
+ if (!is_array($grps)) $grps = array($conf['defaultgroup']);
+
+ // prepare user line
+ $userline = $this->createUserLine($user, $pass, $name, $mail, $grps);
+
+ if (!io_saveFile($config_cascade['plainauth.users']['default'], $userline, true)) {
+ msg($this->getLang('writefail'), -1);
+ return null;
+ }
+
+ $this->users[$user] = compact('pass', 'name', 'mail', 'grps');
+ return $pwd;
+ }
+
+ /**
+ * Modify user data
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param string $user nick of the user to be changed
+ * @param array $changes array of field/value pairs to be changed (password will be clear text)
+ * @return bool
+ */
+ public function modifyUser($user, $changes)
+ {
+ global $ACT;
+ global $config_cascade;
+
+ // sanity checks, user must already exist and there must be something to change
+ if (($userinfo = $this->getUserData($user)) === false) {
+ msg($this->getLang('usernotexists'), -1);
+ return false;
+ }
+
+ // don't modify protected users
+ if (!empty($userinfo['protected'])) {
+ msg(sprintf($this->getLang('protected'), hsc($user)), -1);
+ return false;
+ }
+
+ if (!is_array($changes) || !count($changes)) return true;
+
+ // update userinfo with new data, remembering to encrypt any password
+ $newuser = $user;
+ foreach ($changes as $field => $value) {
+ if ($field == 'user') {
+ $newuser = $value;
+ continue;
+ }
+ if ($field == 'pass') $value = auth_cryptPassword($value);
+ $userinfo[$field] = $value;
+ }
+
+ $userline = $this->createUserLine(
+ $newuser,
+ $userinfo['pass'],
+ $userinfo['name'],
+ $userinfo['mail'],
+ $userinfo['grps']
+ );
+
+ if (!io_replaceInFile($config_cascade['plainauth.users']['default'], '/^'.$user.':/', $userline, true)) {
+ msg('There was an error modifying your user data. You may need to register again.', -1);
+ // FIXME, io functions should be fail-safe so existing data isn't lost
+ $ACT = 'register';
+ return false;
+ }
+
+ $this->users[$newuser] = $userinfo;
+ return true;
+ }
+
+ /**
+ * Remove one or more users from the list of registered users
+ *
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ * @param array $users array of users to be deleted
+ * @return int the number of users deleted
+ */
+ public function deleteUsers($users)
+ {
+ global $config_cascade;
+
+ if (!is_array($users) || empty($users)) return 0;
+
+ if ($this->users === null) $this->loadUserData();
+
+ $deleted = array();
+ foreach ($users as $user) {
+ // don't delete protected users
+ if (!empty($this->users[$user]['protected'])) {
+ msg(sprintf($this->getLang('protected'), hsc($user)), -1);
+ continue;
+ }
+ if (isset($this->users[$user])) $deleted[] = preg_quote($user, '/');
+ }
+
+ if (empty($deleted)) return 0;
+
+ $pattern = '/^('.join('|', $deleted).'):/';
+ if (!io_deleteFromFile($config_cascade['plainauth.users']['default'], $pattern, true)) {
+ msg($this->getLang('writefail'), -1);
+ return 0;
+ }
+
+ // reload the user list and count the difference
+ $count = count($this->users);
+ $this->loadUserData();
+ $count -= count($this->users);
+ return $count;
+ }
+
+ /**
+ * Return a count of the number of user which meet $filter criteria
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ *
+ * @param array $filter
+ * @return int
+ */
+ public function getUserCount($filter = array())
+ {
+
+ if ($this->users === null) $this->loadUserData();
+
+ if (!count($filter)) return count($this->users);
+
+ $count = 0;
+ $this->constructPattern($filter);
+
+ foreach ($this->users as $user => $info) {
+ $count += $this->filter($user, $info);
+ }
+
+ return $count;
+ }
+
+ /**
+ * Bulk retrieval of user data
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ *
+ * @param int $start index of first user to be returned
+ * @param int $limit max number of users to be returned
+ * @param array $filter array of field/pattern pairs
+ * @return array userinfo (refer getUserData for internal userinfo details)
+ */
+ public function retrieveUsers($start = 0, $limit = 0, $filter = array())
+ {
+
+ if ($this->users === null) $this->loadUserData();
+
+ ksort($this->users);
+
+ $i = 0;
+ $count = 0;
+ $out = array();
+ $this->constructPattern($filter);
+
+ foreach ($this->users as $user => $info) {
+ if ($this->filter($user, $info)) {
+ if ($i >= $start) {
+ $out[$user] = $info;
+ $count++;
+ if (($limit > 0) && ($count >= $limit)) break;
+ }
+ $i++;
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Retrieves groups.
+ * Loads complete user data into memory before searching for groups.
+ *
+ * @param int $start index of first group to be returned
+ * @param int $limit max number of groups to be returned
+ * @return array
+ */
+ public function retrieveGroups($start = 0, $limit = 0)
+ {
+ $groups = [];
+
+ if ($this->users === null) $this->loadUserData();
+ foreach($this->users as $user => $info) {
+ $groups = array_merge($groups, array_diff($info['grps'], $groups));
+ }
+
+ if($limit > 0) {
+ return array_splice($groups, $start, $limit);
+ }
+ return array_splice($groups, $start);
+ }
+
+ /**
+ * Only valid pageid's (no namespaces) for usernames
+ *
+ * @param string $user
+ * @return string
+ */
+ public function cleanUser($user)
+ {
+ global $conf;
+ return cleanID(str_replace(':', $conf['sepchar'], $user));
+ }
+
+ /**
+ * Only valid pageid's (no namespaces) for groupnames
+ *
+ * @param string $group
+ * @return string
+ */
+ public function cleanGroup($group)
+ {
+ global $conf;
+ return cleanID(str_replace(':', $conf['sepchar'], $group));
+ }
+
+ /**
+ * Load all user data
+ *
+ * loads the user file into a datastructure
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function loadUserData()
+ {
+ global $config_cascade;
+
+ $this->users = $this->readUserFile($config_cascade['plainauth.users']['default']);
+
+ // support protected users
+ if (!empty($config_cascade['plainauth.users']['protected'])) {
+ $protected = $this->readUserFile($config_cascade['plainauth.users']['protected']);
+ foreach (array_keys($protected) as $key) {
+ $protected[$key]['protected'] = true;
+ }
+ $this->users = array_merge($this->users, $protected);
+ }
+ }
+
+ /**
+ * Read user data from given file
+ *
+ * ignores non existing files
+ *
+ * @param string $file the file to load data from
+ * @return array
+ */
+ protected function readUserFile($file)
+ {
+ $users = array();
+ if (!file_exists($file)) return $users;
+
+ $lines = file($file);
+ foreach ($lines as $line) {
+ $line = preg_replace('/#.*$/', '', $line); //ignore comments
+ $line = trim($line);
+ if (empty($line)) continue;
+
+ $row = $this->splitUserData($line);
+ $row = str_replace('\\:', ':', $row);
+ $row = str_replace('\\\\', '\\', $row);
+
+ $groups = array_values(array_filter(explode(",", $row[4])));
+
+ $users[$row[0]]['pass'] = $row[1];
+ $users[$row[0]]['name'] = urldecode($row[2]);
+ $users[$row[0]]['mail'] = $row[3];
+ $users[$row[0]]['grps'] = $groups;
+ }
+ return $users;
+ }
+
+ /**
+ * Get the user line split into it's parts
+ *
+ * @param string $line
+ * @return string[]
+ */
+ protected function splitUserData($line)
+ {
+ // due to a bug in PCRE 6.6, preg_split will fail with the regex we use here
+ // refer github issues 877 & 885
+ if ($this->pregsplit_safe) {
+ return preg_split('/(?<![^\\\\]\\\\)\:/', $line, 5); // allow for : escaped as \:
+ }
+
+ $row = array();
+ $piece = '';
+ $len = strlen($line);
+ for ($i=0; $i<$len; $i++) {
+ if ($line[$i]=='\\') {
+ $piece .= $line[$i];
+ $i++;
+ if ($i>=$len) break;
+ } elseif ($line[$i]==':') {
+ $row[] = $piece;
+ $piece = '';
+ continue;
+ }
+ $piece .= $line[$i];
+ }
+ $row[] = $piece;
+
+ return $row;
+ }
+
+ /**
+ * return true if $user + $info match $filter criteria, false otherwise
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ *
+ * @param string $user User login
+ * @param array $info User's userinfo array
+ * @return bool
+ */
+ protected function filter($user, $info)
+ {
+ foreach ($this->pattern as $item => $pattern) {
+ if ($item == 'user') {
+ if (!preg_match($pattern, $user)) return false;
+ } elseif ($item == 'grps') {
+ if (!count(preg_grep($pattern, $info['grps']))) return false;
+ } else {
+ if (!preg_match($pattern, $info[$item])) return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * construct a filter pattern
+ *
+ * @param array $filter
+ */
+ protected function constructPattern($filter)
+ {
+ $this->pattern = array();
+ foreach ($filter as $item => $pattern) {
+ $this->pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters
+ }
+ }
+}
diff --git a/platform/www/lib/plugins/authplain/lang/en/lang.php b/platform/www/lib/plugins/authplain/lang/en/lang.php
new file mode 100644
index 0000000..7108f38
--- /dev/null
+++ b/platform/www/lib/plugins/authplain/lang/en/lang.php
@@ -0,0 +1,9 @@
+<?php
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ */
+$lang['userexists'] = 'Sorry, a user with this login already exists.';
+$lang['usernotexists'] = 'Sorry, that user doesn\'t exist.';
+$lang['writefail'] = 'Unable to modify user data. Please inform the Wiki-Admin';
+$lang['protected'] = 'Data for user %s is protected and can not be modified or deleted.';
diff --git a/platform/www/lib/plugins/authplain/plugin.info.txt b/platform/www/lib/plugins/authplain/plugin.info.txt
new file mode 100644
index 0000000..c09dbcb
--- /dev/null
+++ b/platform/www/lib/plugins/authplain/plugin.info.txt
@@ -0,0 +1,7 @@
+base authplain
+author Andreas Gohr
+email andi@splitbrain.org
+date 2015-07-18
+name Plain Auth Plugin
+desc Provides user authentication against DokuWiki's local password storage
+url http://www.dokuwiki.org/plugin:authplain
diff --git a/platform/www/lib/plugins/cli.php b/platform/www/lib/plugins/cli.php
new file mode 100644
index 0000000..a3cbec7
--- /dev/null
+++ b/platform/www/lib/plugins/cli.php
@@ -0,0 +1,2 @@
+<?php
+dbg_deprecated('Autoloading. Do not require() files yourself.');
diff --git a/platform/www/lib/plugins/config/admin.php b/platform/www/lib/plugins/config/admin.php
new file mode 100644
index 0000000..219612c
--- /dev/null
+++ b/platform/www/lib/plugins/config/admin.php
@@ -0,0 +1,282 @@
+<?php
+/**
+ * Configuration Manager admin plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+
+use dokuwiki\plugin\config\core\Configuration;
+use dokuwiki\plugin\config\core\Setting\Setting;
+use dokuwiki\plugin\config\core\Setting\SettingFieldset;
+use dokuwiki\plugin\config\core\Setting\SettingHidden;
+
+/**
+ * All DokuWiki plugins to extend the admin function
+ * need to inherit from this class
+ */
+class admin_plugin_config extends DokuWiki_Admin_Plugin {
+
+ const IMGDIR = DOKU_BASE . 'lib/plugins/config/images/';
+
+ /** @var Configuration */
+ protected $configuration;
+
+ /** @var bool were there any errors in the submitted data? */
+ protected $hasErrors = false;
+
+ /** @var bool have the settings translations been loaded? */
+ protected $promptsLocalized = false;
+
+
+ /**
+ * handle user request
+ */
+ public function handle() {
+ global $ID, $INPUT;
+
+ // always initialize the configuration
+ $this->configuration = new Configuration();
+
+ if(!$INPUT->bool('save') || !checkSecurityToken()) {
+ return;
+ }
+
+ // don't go any further if the configuration is locked
+ if($this->configuration->isLocked()) return;
+
+ // update settings and redirect of successful
+ $ok = $this->configuration->updateSettings($INPUT->arr('config'));
+ if($ok) { // no errors
+ try {
+ if($this->configuration->hasChanged()) {
+ $this->configuration->save();
+ } else {
+ $this->configuration->touch();
+ }
+ msg($this->getLang('updated'), 1);
+ } catch(Exception $e) {
+ msg($this->getLang('error'), -1);
+ }
+ send_redirect(wl($ID, array('do' => 'admin', 'page' => 'config'), true, '&'));
+ } else {
+ $this->hasErrors = true;
+ }
+ }
+
+ /**
+ * output appropriate html
+ */
+ public function html() {
+ $allow_debug = $GLOBALS['conf']['allowdebug']; // avoid global $conf; here.
+ global $lang;
+ global $ID;
+
+ $this->setupLocale(true);
+
+ echo $this->locale_xhtml('intro');
+
+ echo '<div id="config__manager">';
+
+ if($this->configuration->isLocked()) {
+ echo '<div class="info">' . $this->getLang('locked') . '</div>';
+ }
+
+ // POST to script() instead of wl($ID) so config manager still works if
+ // rewrite config is broken. Add $ID as hidden field to remember
+ // current ID in most cases.
+ echo '<form id="dw__configform" action="' . script() . '" method="post">';
+ echo '<div class="no"><input type="hidden" name="id" value="' . $ID . '" /></div>';
+ formSecurityToken();
+ $this->printH1('dokuwiki_settings', $this->getLang('_header_dokuwiki'));
+
+ $in_fieldset = false;
+ $first_plugin_fieldset = true;
+ $first_template_fieldset = true;
+ foreach($this->configuration->getSettings() as $setting) {
+ if(is_a($setting, SettingHidden::class)) {
+ continue;
+ } else if(is_a($setting, settingFieldset::class)) {
+ // config setting group
+ if($in_fieldset) {
+ echo '</table>';
+ echo '</div>';
+ echo '</fieldset>';
+ } else {
+ $in_fieldset = true;
+ }
+ if($first_plugin_fieldset && $setting->getType() == 'plugin') {
+ $this->printH1('plugin_settings', $this->getLang('_header_plugin'));
+ $first_plugin_fieldset = false;
+ } else if($first_template_fieldset && $setting->getType() == 'template') {
+ $this->printH1('template_settings', $this->getLang('_header_template'));
+ $first_template_fieldset = false;
+ }
+ echo '<fieldset id="' . $setting->getKey() . '">';
+ echo '<legend>' . $setting->prompt($this) . '</legend>';
+ echo '<div class="table">';
+ echo '<table class="inline">';
+ } else {
+ // config settings
+ list($label, $input) = $setting->html($this, $this->hasErrors);
+
+ $class = $setting->isDefault()
+ ? ' class="default"'
+ : ($setting->isProtected() ? ' class="protected"' : '');
+ $error = $setting->hasError()
+ ? ' class="value error"'
+ : ' class="value"';
+ $icon = $setting->caution()
+ ? '<img src="' . self::IMGDIR . $setting->caution() . '.png" ' .
+ 'alt="' . $setting->caution() . '" title="' . $this->getLang($setting->caution()) . '" />'
+ : '';
+
+ echo '<tr' . $class . '>';
+ echo '<td class="label">';
+ echo '<span class="outkey">' . $setting->getPrettyKey() . '</span>';
+ echo $icon . $label;
+ echo '</td>';
+ echo '<td' . $error . '>' . $input . '</td>';
+ echo '</tr>';
+ }
+ }
+
+ echo '</table>';
+ echo '</div>';
+ if($in_fieldset) {
+ echo '</fieldset>';
+ }
+
+ // show undefined settings list
+ $undefined_settings = $this->configuration->getUndefined();
+ if($allow_debug && !empty($undefined_settings)) {
+ /**
+ * Callback for sorting settings
+ *
+ * @param Setting $a
+ * @param Setting $b
+ * @return int if $a is lower/equal/higher than $b
+ */
+ function settingNaturalComparison($a, $b) {
+ return strnatcmp($a->getKey(), $b->getKey());
+ }
+
+ usort($undefined_settings, 'settingNaturalComparison');
+ $this->printH1('undefined_settings', $this->getLang('_header_undefined'));
+ echo '<fieldset>';
+ echo '<div class="table">';
+ echo '<table class="inline">';
+ foreach($undefined_settings as $setting) {
+ list($label, $input) = $setting->html($this);
+ echo '<tr>';
+ echo '<td class="label">' . $label . '</td>';
+ echo '<td>' . $input . '</td>';
+ echo '</tr>';
+ }
+ echo '</table>';
+ echo '</div>';
+ echo '</fieldset>';
+ }
+
+ // finish up form
+ echo '<p>';
+ echo '<input type="hidden" name="do" value="admin" />';
+ echo '<input type="hidden" name="page" value="config" />';
+
+ if(!$this->configuration->isLocked()) {
+ echo '<input type="hidden" name="save" value="1" />';
+ echo '<button type="submit" name="submit" accesskey="s">' . $lang['btn_save'] . '</button>';
+ echo '<button type="reset">' . $lang['btn_reset'] . '</button>';
+ }
+
+ echo '</p>';
+
+ echo '</form>';
+ echo '</div>';
+ }
+
+ /**
+ * @param bool $prompts
+ */
+ public function setupLocale($prompts = false) {
+ parent::setupLocale();
+ if(!$prompts || $this->promptsLocalized) return;
+ $this->lang = array_merge($this->lang, $this->configuration->getLangs());
+ $this->promptsLocalized = true;
+ }
+
+ /**
+ * Generates a two-level table of contents for the config plugin.
+ *
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ *
+ * @return array
+ */
+ public function getTOC() {
+ $this->setupLocale(true);
+
+ $allow_debug = $GLOBALS['conf']['allowdebug']; // avoid global $conf; here.
+ $toc = array();
+ $check = false;
+
+ // gather settings data into three sub arrays
+ $labels = ['dokuwiki' => [], 'plugin' => [], 'template' => []];
+ foreach($this->configuration->getSettings() as $setting) {
+ if(is_a($setting, SettingFieldset::class)) {
+ $labels[$setting->getType()][] = $setting;
+ }
+ }
+
+ // top header
+ $title = $this->getLang('_configuration_manager');
+ $toc[] = html_mktocitem(sectionID($title, $check), $title, 1);
+
+ // main entries
+ foreach(['dokuwiki', 'plugin', 'template'] as $section) {
+ if(empty($labels[$section])) continue; // no entries, skip
+
+ // create main header
+ $toc[] = html_mktocitem(
+ $section . '_settings',
+ $this->getLang('_header_' . $section),
+ 1
+ );
+
+ // create sub headers
+ foreach($labels[$section] as $setting) {
+ /** @var SettingFieldset $setting */
+ $name = $setting->prompt($this);
+ $toc[] = html_mktocitem($setting->getKey(), $name, 2);
+ }
+ }
+
+ // undefined settings if allowed
+ if(count($this->configuration->getUndefined()) && $allow_debug) {
+ $toc[] = html_mktocitem('undefined_settings', $this->getLang('_header_undefined'), 1);
+ }
+
+ return $toc;
+ }
+
+ /**
+ * @param string $id
+ * @param string $text
+ */
+ protected function printH1($id, $text) {
+ echo '<h1 id="' . $id . '">' . $text . '</h1>';
+ }
+
+ /**
+ * Adds a translation to this plugin's language array
+ *
+ * Used by some settings to set up dynamic translations
+ *
+ * @param string $key
+ * @param string $value
+ */
+ public function addLang($key, $value) {
+ if(!$this->localised) $this->setupLocale();
+ $this->lang[$key] = $value;
+ }
+}
diff --git a/platform/www/lib/plugins/config/admin.svg b/platform/www/lib/plugins/config/admin.svg
new file mode 100644
index 0000000..ced9871
--- /dev/null
+++ b/platform/www/lib/plugins/config/admin.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 15.5A3.5 3.5 0 0 1 8.5 12 3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97 0-.33-.03-.66-.07-1l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65A.506.506 0 0 0 14 2h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.34-.07.67-.07 1 0 .33.03.65.07.97l-2.11 1.66c-.19.15-.25.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1.01c.52.4 1.06.74 1.69.99l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.26 1.17-.59 1.69-.99l2.49 1.01c.22.08.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.66z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/plugins/config/core/ConfigParser.php b/platform/www/lib/plugins/config/core/ConfigParser.php
new file mode 100644
index 0000000..9e79b96
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/ConfigParser.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace dokuwiki\plugin\config\core;
+
+/**
+ * A naive PHP file parser
+ *
+ * This parses our very simple config file in PHP format. We use this instead of simply including
+ * the file, because we want to keep expressions such as 24*60*60 as is.
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+class ConfigParser {
+ /** @var string variable to parse from the file */
+ protected $varname = 'conf';
+ /** @var string the key to mark sub arrays */
+ protected $keymarker = Configuration::KEYMARKER;
+
+ /**
+ * Parse the given PHP file into an array
+ *
+ * When the given files does not exist, this returns an empty array
+ *
+ * @param string $file
+ * @return array
+ */
+ public function parse($file) {
+ if(!file_exists($file)) return array();
+
+ $config = array();
+ $contents = @php_strip_whitespace($file);
+ $pattern = '/\$' . $this->varname . '\[[\'"]([^=]+)[\'"]\] ?= ?(.*?);(?=[^;]*(?:\$' . $this->varname . '|$))/s';
+ $matches = array();
+ preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER);
+
+ for($i = 0; $i < count($matches); $i++) {
+ $value = $matches[$i][2];
+
+ // merge multi-dimensional array indices using the keymarker
+ $key = preg_replace('/.\]\[./', $this->keymarker, $matches[$i][1]);
+
+ // handle arrays
+ if(preg_match('/^array ?\((.*)\)/', $value, $match)) {
+ $arr = explode(',', $match[1]);
+
+ // remove quotes from quoted strings & unescape escaped data
+ $len = count($arr);
+ for($j = 0; $j < $len; $j++) {
+ $arr[$j] = trim($arr[$j]);
+ $arr[$j] = $this->readValue($arr[$j]);
+ }
+
+ $value = $arr;
+ } else {
+ $value = $this->readValue($value);
+ }
+
+ $config[$key] = $value;
+ }
+
+ return $config;
+ }
+
+ /**
+ * Convert php string into value
+ *
+ * @param string $value
+ * @return bool|string
+ */
+ protected function readValue($value) {
+ $removequotes_pattern = '/^(\'|")(.*)(?<!\\\\)\1$/s';
+ $unescape_pairs = array(
+ '\\\\' => '\\',
+ '\\\'' => '\'',
+ '\\"' => '"'
+ );
+
+ if($value == 'true') {
+ $value = true;
+ } elseif($value == 'false') {
+ $value = false;
+ } else {
+ // remove quotes from quoted strings & unescape escaped data
+ $value = preg_replace($removequotes_pattern, '$2', $value);
+ $value = strtr($value, $unescape_pairs);
+ }
+ return $value;
+ }
+
+}
diff --git a/platform/www/lib/plugins/config/core/Configuration.php b/platform/www/lib/plugins/config/core/Configuration.php
new file mode 100644
index 0000000..c58645c
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Configuration.php
@@ -0,0 +1,219 @@
+<?php
+
+namespace dokuwiki\plugin\config\core;
+
+use dokuwiki\plugin\config\core\Setting\Setting;
+use dokuwiki\plugin\config\core\Setting\SettingNoClass;
+use dokuwiki\plugin\config\core\Setting\SettingNoDefault;
+use dokuwiki\plugin\config\core\Setting\SettingNoKnownClass;
+use dokuwiki\plugin\config\core\Setting\SettingUndefined;
+
+/**
+ * Holds all the current settings and proxies the Loader and Writer
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+class Configuration {
+
+ const KEYMARKER = '____';
+
+ /** @var Setting[] metadata as array of Settings objects */
+ protected $settings = array();
+ /** @var Setting[] undefined and problematic settings */
+ protected $undefined = array();
+
+ /** @var array all metadata */
+ protected $metadata;
+ /** @var array all default settings */
+ protected $default;
+ /** @var array all local settings */
+ protected $local;
+ /** @var array all protected settings */
+ protected $protected;
+
+ /** @var bool have the settings been changed since loading from disk? */
+ protected $changed = false;
+
+ /** @var Loader */
+ protected $loader;
+ /** @var Writer */
+ protected $writer;
+
+ /**
+ * ConfigSettings constructor.
+ */
+ public function __construct() {
+ $this->loader = new Loader(new ConfigParser());
+ $this->writer = new Writer();
+
+ $this->metadata = $this->loader->loadMeta();
+ $this->default = $this->loader->loadDefaults();
+ $this->local = $this->loader->loadLocal();
+ $this->protected = $this->loader->loadProtected();
+
+ $this->initSettings();
+ }
+
+ /**
+ * Get all settings
+ *
+ * @return Setting[]
+ */
+ public function getSettings() {
+ return $this->settings;
+ }
+
+ /**
+ * Get all unknown or problematic settings
+ *
+ * @return Setting[]
+ */
+ public function getUndefined() {
+ return $this->undefined;
+ }
+
+ /**
+ * Have the settings been changed since loading from disk?
+ *
+ * @return bool
+ */
+ public function hasChanged() {
+ return $this->changed;
+ }
+
+ /**
+ * Check if the config can be written
+ *
+ * @return bool
+ */
+ public function isLocked() {
+ return $this->writer->isLocked();
+ }
+
+ /**
+ * Update the settings using the data provided
+ *
+ * @param array $input as posted
+ * @return bool true if all updates went through, false on errors
+ */
+ public function updateSettings($input) {
+ $ok = true;
+
+ foreach($this->settings as $key => $obj) {
+ $value = isset($input[$key]) ? $input[$key] : null;
+ if($obj->update($value)) {
+ $this->changed = true;
+ }
+ if($obj->hasError()) $ok = false;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Save the settings
+ *
+ * This save the current state as defined in this object, including the
+ * undefined settings
+ *
+ * @throws \Exception
+ */
+ public function save() {
+ // only save the undefined settings that have not been handled in settings
+ $undefined = array_diff_key($this->undefined, $this->settings);
+ $this->writer->save(array_merge($this->settings, $undefined));
+ }
+
+ /**
+ * Touch the settings
+ *
+ * @throws \Exception
+ */
+ public function touch() {
+ $this->writer->touch();
+ }
+
+ /**
+ * Load the extension language strings
+ *
+ * @return array
+ */
+ public function getLangs() {
+ return $this->loader->loadLangs();
+ }
+
+ /**
+ * Initalizes the $settings and $undefined properties
+ */
+ protected function initSettings() {
+ $keys = array_merge(
+ array_keys($this->metadata),
+ array_keys($this->default),
+ array_keys($this->local),
+ array_keys($this->protected)
+ );
+ $keys = array_unique($keys);
+
+ foreach($keys as $key) {
+ $obj = $this->instantiateClass($key);
+
+ if($obj->shouldHaveDefault() && !isset($this->default[$key])) {
+ $this->undefined[$key] = new SettingNoDefault($key);
+ }
+
+ $d = isset($this->default[$key]) ? $this->default[$key] : null;
+ $l = isset($this->local[$key]) ? $this->local[$key] : null;
+ $p = isset($this->protected[$key]) ? $this->protected[$key] : null;
+
+ $obj->initialize($d, $l, $p);
+ }
+ }
+
+ /**
+ * Instantiates the proper class for the given config key
+ *
+ * The class is added to the $settings or $undefined arrays and returned
+ *
+ * @param string $key
+ * @return Setting
+ */
+ protected function instantiateClass($key) {
+ if(isset($this->metadata[$key])) {
+ $param = $this->metadata[$key];
+ $class = $this->determineClassName(array_shift($param), $key); // first param is class
+ $obj = new $class($key, $param);
+ $this->settings[$key] = $obj;
+ } else {
+ $obj = new SettingUndefined($key);
+ $this->undefined[$key] = $obj;
+ }
+ return $obj;
+ }
+
+ /**
+ * Return the class to load
+ *
+ * @param string $class the class name as given in the meta file
+ * @param string $key the settings key
+ * @return string
+ */
+ protected function determineClassName($class, $key) {
+ // try namespaced class first
+ if(is_string($class)) {
+ $modern = str_replace('_', '', ucwords($class, '_'));
+ $modern = '\\dokuwiki\\plugin\\config\\core\\Setting\\Setting' . $modern;
+ if($modern && class_exists($modern)) return $modern;
+ // try class as given
+ if(class_exists($class)) return $class;
+ // class wasn't found add to errors
+ $this->undefined[$key] = new SettingNoKnownClass($key);
+ } else {
+ // no class given, add to errors
+ $this->undefined[$key] = new SettingNoClass($key);
+ }
+ return '\\dokuwiki\\plugin\\config\\core\\Setting\\Setting';
+ }
+
+}
diff --git a/platform/www/lib/plugins/config/core/Loader.php b/platform/www/lib/plugins/config/core/Loader.php
new file mode 100644
index 0000000..90ad0f5
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Loader.php
@@ -0,0 +1,269 @@
+<?php
+
+namespace dokuwiki\plugin\config\core;
+
+use dokuwiki\Extension\Event;
+
+/**
+ * Configuration loader
+ *
+ * Loads configuration meta data and settings from the various files. Honors the
+ * configuration cascade and installed plugins.
+ */
+class Loader {
+ /** @var ConfigParser */
+ protected $parser;
+
+ /** @var string[] list of enabled plugins */
+ protected $plugins;
+ /** @var string current template */
+ protected $template;
+
+ /**
+ * Loader constructor.
+ * @param ConfigParser $parser
+ * @triggers PLUGIN_CONFIG_PLUGINLIST
+ */
+ public function __construct(ConfigParser $parser) {
+ global $conf;
+ $this->parser = $parser;
+ $this->plugins = plugin_list();
+ $this->template = $conf['template'];
+ // allow plugins to remove configurable plugins
+ Event::createAndTrigger('PLUGIN_CONFIG_PLUGINLIST', $this->plugins);
+ }
+
+ /**
+ * Read the settings meta data
+ *
+ * Reads the main file, plugins and template settings meta data
+ *
+ * @return array
+ */
+ public function loadMeta() {
+ // load main file
+ $meta = array();
+ include DOKU_PLUGIN . 'config/settings/config.metadata.php';
+
+ // plugins
+ foreach($this->plugins as $plugin) {
+ $meta = array_merge(
+ $meta,
+ $this->loadExtensionMeta(
+ DOKU_PLUGIN . $plugin . '/conf/metadata.php',
+ 'plugin',
+ $plugin
+ )
+ );
+ }
+
+ // current template
+ $meta = array_merge(
+ $meta,
+ $this->loadExtensionMeta(
+ tpl_incdir() . '/conf/metadata.php',
+ 'tpl',
+ $this->template
+ )
+ );
+
+ return $meta;
+ }
+
+ /**
+ * Read the default values
+ *
+ * Reads the main file, plugins and template defaults
+ *
+ * @return array
+ */
+ public function loadDefaults() {
+ // load main files
+ global $config_cascade;
+ $conf = $this->loadConfigs($config_cascade['main']['default']);
+
+ // plugins
+ foreach($this->plugins as $plugin) {
+ $conf = array_merge(
+ $conf,
+ $this->loadExtensionConf(
+ DOKU_PLUGIN . $plugin . '/conf/default.php',
+ 'plugin',
+ $plugin
+ )
+ );
+ }
+
+ // current template
+ $conf = array_merge(
+ $conf,
+ $this->loadExtensionConf(
+ tpl_incdir() . '/conf/default.php',
+ 'tpl',
+ $this->template
+ )
+ );
+
+ return $conf;
+ }
+
+ /**
+ * Reads the language strings
+ *
+ * Only reads extensions, main one is loaded the usual way
+ *
+ * @return array
+ */
+ public function loadLangs() {
+ $lang = array();
+
+ // plugins
+ foreach($this->plugins as $plugin) {
+ $lang = array_merge(
+ $lang,
+ $this->loadExtensionLang(
+ DOKU_PLUGIN . $plugin . '/',
+ 'plugin',
+ $plugin
+ )
+ );
+ }
+
+ // current template
+ $lang = array_merge(
+ $lang,
+ $this->loadExtensionLang(
+ tpl_incdir() . '/',
+ 'tpl',
+ $this->template
+ )
+ );
+
+ return $lang;
+ }
+
+ /**
+ * Read the local settings
+ *
+ * @return array
+ */
+ public function loadLocal() {
+ global $config_cascade;
+ return $this->loadConfigs($config_cascade['main']['local']);
+ }
+
+ /**
+ * Read the protected settings
+ *
+ * @return array
+ */
+ public function loadProtected() {
+ global $config_cascade;
+ return $this->loadConfigs($config_cascade['main']['protected']);
+ }
+
+ /**
+ * Read the config values from the given files
+ *
+ * @param string[] $files paths to config php's
+ * @return array
+ */
+ protected function loadConfigs($files) {
+ $conf = array();
+ foreach($files as $file) {
+ $conf = array_merge($conf, $this->parser->parse($file));
+ }
+ return $conf;
+ }
+
+ /**
+ * Read settings file from an extension
+ *
+ * This is used to read the settings.php files of plugins and templates
+ *
+ * @param string $file php file to read
+ * @param string $type should be 'plugin' or 'tpl'
+ * @param string $extname name of the extension
+ * @return array
+ */
+ protected function loadExtensionMeta($file, $type, $extname) {
+ if(!file_exists($file)) return array();
+ $prefix = $type . Configuration::KEYMARKER . $extname . Configuration::KEYMARKER;
+
+ // include file
+ $meta = array();
+ include $file;
+ if(empty($meta)) return array();
+
+ // read data
+ $data = array();
+ $data[$prefix . $type . '_settings_name'] = ['fieldset'];
+ foreach($meta as $key => $value) {
+ if($value[0] == 'fieldset') continue; //plugins only get one fieldset
+ $data[$prefix . $key] = $value;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Read a default file from an extension
+ *
+ * This is used to read the default.php files of plugins and templates
+ *
+ * @param string $file php file to read
+ * @param string $type should be 'plugin' or 'tpl'
+ * @param string $extname name of the extension
+ * @return array
+ */
+ protected function loadExtensionConf($file, $type, $extname) {
+ if(!file_exists($file)) return array();
+ $prefix = $type . Configuration::KEYMARKER . $extname . Configuration::KEYMARKER;
+
+ // parse file
+ $conf = $this->parser->parse($file);
+ if(empty($conf)) return array();
+
+ // read data
+ $data = array();
+ foreach($conf as $key => $value) {
+ $data[$prefix . $key] = $value;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Read the language file of an extension
+ *
+ * @param string $dir directory of the extension
+ * @param string $type should be 'plugin' or 'tpl'
+ * @param string $extname name of the extension
+ * @return array
+ */
+ protected function loadExtensionLang($dir, $type, $extname) {
+ global $conf;
+ $ll = $conf['lang'];
+ $prefix = $type . Configuration::KEYMARKER . $extname . Configuration::KEYMARKER;
+
+ // include files
+ $lang = array();
+ if(file_exists($dir . 'lang/en/settings.php')) {
+ include $dir . 'lang/en/settings.php';
+ }
+ if($ll != 'en' && file_exists($dir . 'lang/' . $ll . '/settings.php')) {
+ include $dir . 'lang/' . $ll . '/settings.php';
+ }
+
+ // set up correct keys
+ $strings = array();
+ foreach($lang as $key => $val) {
+ $strings[$prefix . $key] = $val;
+ }
+
+ // add fieldset key
+ $strings[$prefix . $type . '_settings_name'] = ucwords(str_replace('_', ' ', $extname));
+
+ return $strings;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/Setting.php b/platform/www/lib/plugins/config/core/Setting/Setting.php
new file mode 100644
index 0000000..d64f684
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/Setting.php
@@ -0,0 +1,294 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+use dokuwiki\plugin\config\core\Configuration;
+
+/**
+ * Class Setting
+ */
+class Setting {
+ /** @var string unique identifier of this setting */
+ protected $key = '';
+
+ /** @var mixed the default value of this setting */
+ protected $default = null;
+ /** @var mixed the local value of this setting */
+ protected $local = null;
+ /** @var mixed the protected value of this setting */
+ protected $protected = null;
+
+ /** @var array valid alerts, images matching the alerts are in the plugin's images directory */
+ static protected $validCautions = array('warning', 'danger', 'security');
+
+ protected $pattern = '';
+ protected $error = false; // only used by those classes which error check
+ protected $input = null; // only used by those classes which error check
+ protected $caution = null; // used by any setting to provide an alert along with the setting
+
+ /**
+ * Constructor.
+ *
+ * The given parameters will be set up as class properties
+ *
+ * @see initialize() to set the actual value of the setting
+ *
+ * @param string $key
+ * @param array|null $params array with metadata of setting
+ */
+ public function __construct($key, $params = null) {
+ $this->key = $key;
+
+ if(is_array($params)) {
+ foreach($params as $property => $value) {
+ $property = trim($property, '_'); // we don't use underscores anymore
+ $this->$property = $value;
+ }
+ }
+ }
+
+ /**
+ * Set the current values for the setting $key
+ *
+ * This is used to initialize the setting with the data read form the config files.
+ *
+ * @see update() to set a new value
+ * @param mixed $default default setting value
+ * @param mixed $local local setting value
+ * @param mixed $protected protected setting value
+ */
+ public function initialize($default = null, $local = null, $protected = null) {
+ $this->default = $this->cleanValue($default);
+ $this->local = $this->cleanValue($local);
+ $this->protected = $this->cleanValue($protected);
+ }
+
+ /**
+ * update changed setting with validated user provided value $input
+ * - if changed value fails validation check, save it to $this->input (to allow echoing later)
+ * - if changed value passes validation check, set $this->local to the new value
+ *
+ * @param mixed $input the new value
+ * @return boolean true if changed, false otherwise
+ */
+ public function update($input) {
+ if(is_null($input)) return false;
+ if($this->isProtected()) return false;
+ $input = $this->cleanValue($input);
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ // validate new value
+ if($this->pattern && !preg_match($this->pattern, $input)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+
+ // update local copy of this setting with new value
+ $this->local = $input;
+
+ // setting ready for update
+ return true;
+ }
+
+ /**
+ * Clean a value read from a config before using it internally
+ *
+ * Default implementation returns $value as is. Subclasses can override.
+ * Note: null should always be returned as null!
+ *
+ * This is applied in initialize() and update()
+ *
+ * @param mixed $value
+ * @return mixed
+ */
+ protected function cleanValue($value) {
+ return $value;
+ }
+
+ /**
+ * Should this type of config have a default?
+ *
+ * @return bool
+ */
+ public function shouldHaveDefault() {
+ return true;
+ }
+
+ /**
+ * Get this setting's unique key
+ *
+ * @return string
+ */
+ public function getKey() {
+ return $this->key;
+ }
+
+ /**
+ * Get the key of this setting marked up human readable
+ *
+ * @param bool $url link to dokuwiki.org manual?
+ * @return string
+ */
+ public function getPrettyKey($url = true) {
+ $out = str_replace(Configuration::KEYMARKER, "»", $this->key);
+ if($url && !strstr($out, '»')) {//provide no urls for plugins, etc.
+ if($out == 'start') {
+ // exception, because this config name is clashing with our actual start page
+ return '<a href="http://www.dokuwiki.org/config:startpage">' . $out . '</a>';
+ } else {
+ return '<a href="http://www.dokuwiki.org/config:' . $out . '">' . $out . '</a>';
+ }
+ }
+ return $out;
+ }
+
+ /**
+ * Returns setting key as an array key separator
+ *
+ * This is used to create form output
+ *
+ * @return string key
+ */
+ public function getArrayKey() {
+ return str_replace(Configuration::KEYMARKER, "']['", $this->key);
+ }
+
+ /**
+ * What type of configuration is this
+ *
+ * Returns one of
+ *
+ * 'plugin' for plugin configuration
+ * 'template' for template configuration
+ * 'dokuwiki' for core configuration
+ *
+ * @return string
+ */
+ public function getType() {
+ if(substr($this->getKey(), 0, 10) == 'plugin' . Configuration::KEYMARKER) {
+ return 'plugin';
+ } else if(substr($this->getKey(), 0, 7) == 'tpl' . Configuration::KEYMARKER) {
+ return 'template';
+ } else {
+ return 'dokuwiki';
+ }
+ }
+
+ /**
+ * Build html for label and input of setting
+ *
+ * @param \admin_plugin_config $plugin object of config plugin
+ * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting
+ * @return string[] with content array(string $label_html, string $input_html)
+ */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ $disable = '';
+
+ if($this->isProtected()) {
+ $value = $this->protected;
+ $disable = 'disabled="disabled"';
+ } else {
+ if($echo && $this->error) {
+ $value = $this->input;
+ } else {
+ $value = is_null($this->local) ? $this->default : $this->local;
+ }
+ }
+
+ $key = htmlspecialchars($this->key);
+ $value = formText($value);
+
+ $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
+ $input = '<textarea rows="3" cols="40" id="config___' . $key .
+ '" name="config[' . $key . ']" class="edit" ' . $disable . '>' . $value . '</textarea>';
+ return array($label, $input);
+ }
+
+ /**
+ * Should the current local value be saved?
+ *
+ * @see out() to run when this returns true
+ * @return bool
+ */
+ public function shouldBeSaved() {
+ if($this->isProtected()) return false;
+ if($this->local === null) return false;
+ if($this->default == $this->local) return false;
+ return true;
+ }
+
+ /**
+ * Generate string to save local setting value to file according to $fmt
+ *
+ * @see shouldBeSaved() to check if this should be called
+ * @param string $var name of variable
+ * @param string $fmt save format
+ * @return string
+ */
+ public function out($var, $fmt = 'php') {
+ if($fmt != 'php') return '';
+
+ $tr = array("\\" => '\\\\', "'" => '\\\''); // escape the value
+ $out = '$' . $var . "['" . $this->getArrayKey() . "'] = '" . strtr(cleanText($this->local), $tr) . "';\n";
+
+ return $out;
+ }
+
+ /**
+ * Returns the localized prompt
+ *
+ * @param \admin_plugin_config $plugin object of config plugin
+ * @return string text
+ */
+ public function prompt(\admin_plugin_config $plugin) {
+ $prompt = $plugin->getLang($this->key);
+ if(!$prompt) $prompt = htmlspecialchars(str_replace(array('____', '_'), ' ', $this->key));
+ return $prompt;
+ }
+
+ /**
+ * Is setting protected
+ *
+ * @return bool
+ */
+ public function isProtected() {
+ return !is_null($this->protected);
+ }
+
+ /**
+ * Is setting the default?
+ *
+ * @return bool
+ */
+ public function isDefault() {
+ return !$this->isProtected() && is_null($this->local);
+ }
+
+ /**
+ * Has an error?
+ *
+ * @return bool
+ */
+ public function hasError() {
+ return $this->error;
+ }
+
+ /**
+ * Returns caution
+ *
+ * @return false|string caution string, otherwise false for invalid caution
+ */
+ public function caution() {
+ if(empty($this->caution)) return false;
+ if(!in_array($this->caution, Setting::$validCautions)) {
+ throw new \RuntimeException(
+ 'Invalid caution string (' . $this->caution . ') in metadata for setting "' . $this->key . '"'
+ );
+ }
+ return $this->caution;
+ }
+
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingArray.php b/platform/www/lib/plugins/config/core/Setting/SettingArray.php
new file mode 100644
index 0000000..c48dc76
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingArray.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_array
+ */
+class SettingArray extends Setting {
+
+ /**
+ * Create an array from a string
+ *
+ * @param string $string
+ * @return array
+ */
+ protected function fromString($string) {
+ $array = explode(',', $string);
+ $array = array_map('trim', $array);
+ $array = array_filter($array);
+ $array = array_unique($array);
+ return $array;
+ }
+
+ /**
+ * Create a string from an array
+ *
+ * @param array $array
+ * @return string
+ */
+ protected function fromArray($array) {
+ return join(', ', (array) $array);
+ }
+
+ /**
+ * update setting with user provided value $input
+ * if value fails error check, save it
+ *
+ * @param string $input
+ * @return bool true if changed, false otherwise (incl. on error)
+ */
+ public function update($input) {
+ if(is_null($input)) return false;
+ if($this->isProtected()) return false;
+
+ $input = $this->fromString($input);
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ foreach($input as $item) {
+ if($this->pattern && !preg_match($this->pattern, $item)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+ }
+
+ $this->local = $input;
+ return true;
+ }
+
+ /**
+ * Escaping
+ *
+ * @param string $string
+ * @return string
+ */
+ protected function escape($string) {
+ $tr = array("\\" => '\\\\', "'" => '\\\'');
+ return "'" . strtr(cleanText($string), $tr) . "'";
+ }
+
+ /** @inheritdoc */
+ public function out($var, $fmt = 'php') {
+ if($fmt != 'php') return '';
+
+ $vals = array_map(array($this, 'escape'), $this->local);
+ $out = '$' . $var . "['" . $this->getArrayKey() . "'] = array(" . join(', ', $vals) . ");\n";
+ return $out;
+ }
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ $disable = '';
+
+ if($this->isProtected()) {
+ $value = $this->protected;
+ $disable = 'disabled="disabled"';
+ } else {
+ if($echo && $this->error) {
+ $value = $this->input;
+ } else {
+ $value = is_null($this->local) ? $this->default : $this->local;
+ }
+ }
+
+ $key = htmlspecialchars($this->key);
+ $value = htmlspecialchars($this->fromArray($value));
+
+ $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
+ $input = '<input id="config___' . $key . '" name="config[' . $key .
+ ']" type="text" class="edit" value="' . $value . '" ' . $disable . '/>';
+ return array($label, $input);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingAuthtype.php b/platform/www/lib/plugins/config/core/Setting/SettingAuthtype.php
new file mode 100644
index 0000000..3a6df6f
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingAuthtype.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_authtype
+ */
+class SettingAuthtype extends SettingMultichoice {
+
+ /** @inheritdoc */
+ public function initialize($default = null, $local = null, $protected = null) {
+ /** @var $plugin_controller \dokuwiki\Extension\PluginController */
+ global $plugin_controller;
+
+ // retrieve auth types provided by plugins
+ foreach($plugin_controller->getList('auth') as $plugin) {
+ $this->choices[] = $plugin;
+ }
+
+ parent::initialize($default, $local, $protected);
+ }
+
+ /** @inheritdoc */
+ public function update($input) {
+ /** @var $plugin_controller \dokuwiki\Extension\PluginController */
+ global $plugin_controller;
+
+ // is an update possible/requested?
+ $local = $this->local; // save this, parent::update() may change it
+ if(!parent::update($input)) return false; // nothing changed or an error caught by parent
+ $this->local = $local; // restore original, more error checking to come
+
+ // attempt to load the plugin
+ $auth_plugin = $plugin_controller->load('auth', $input);
+
+ // @TODO: throw an error in plugin controller instead of returning null
+ if(is_null($auth_plugin)) {
+ $this->error = true;
+ msg('Cannot load Auth Plugin "' . $input . '"', -1);
+ return false;
+ }
+
+ // verify proper instantiation (is this really a plugin?) @TODO use instanceof? implement interface?
+ if(is_object($auth_plugin) && !method_exists($auth_plugin, 'getPluginName')) {
+ $this->error = true;
+ msg('Cannot create Auth Plugin "' . $input . '"', -1);
+ return false;
+ }
+
+ // did we change the auth type? logout
+ global $conf;
+ if($conf['authtype'] != $input) {
+ msg('Authentication system changed. Please re-login.');
+ auth_logoff();
+ }
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingCompression.php b/platform/www/lib/plugins/config/core/Setting/SettingCompression.php
new file mode 100644
index 0000000..f97d828
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingCompression.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_compression
+ */
+class SettingCompression extends SettingMultichoice {
+
+ protected $choices = array('0'); // 0 = no compression, always supported
+
+ /** @inheritdoc */
+ public function initialize($default = null, $local = null, $protected = null) {
+
+ // populate _choices with the compression methods supported by this php installation
+ if(function_exists('gzopen')) $this->choices[] = 'gz';
+ if(function_exists('bzopen')) $this->choices[] = 'bz2';
+
+ parent::initialize($default, $local, $protected);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingDirchoice.php b/platform/www/lib/plugins/config/core/Setting/SettingDirchoice.php
new file mode 100644
index 0000000..dfb27f5
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingDirchoice.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_dirchoice
+ */
+class SettingDirchoice extends SettingMultichoice {
+
+ protected $dir = '';
+
+ /** @inheritdoc */
+ public function initialize($default = null, $local = null, $protected = null) {
+
+ // populate $this->_choices with a list of directories
+ $list = array();
+
+ if($dh = @opendir($this->dir)) {
+ while(false !== ($entry = readdir($dh))) {
+ if($entry == '.' || $entry == '..') continue;
+ if($this->pattern && !preg_match($this->pattern, $entry)) continue;
+
+ $file = (is_link($this->dir . $entry)) ? readlink($this->dir . $entry) : $this->dir . $entry;
+ if(is_dir($file)) $list[] = $entry;
+ }
+ closedir($dh);
+ }
+ sort($list);
+ $this->choices = $list;
+
+ parent::initialize($default, $local, $protected);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingDisableactions.php b/platform/www/lib/plugins/config/core/Setting/SettingDisableactions.php
new file mode 100644
index 0000000..2553175
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingDisableactions.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_disableactions
+ */
+class SettingDisableactions extends SettingMulticheckbox {
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ global $lang;
+
+ // make some language adjustments (there must be a better way)
+ // transfer some DokuWiki language strings to the plugin
+ $plugin->addLang($this->key . '_revisions', $lang['btn_revs']);
+ foreach($this->choices as $choice) {
+ if(isset($lang['btn_' . $choice])) $plugin->addLang($this->key . '_' . $choice, $lang['btn_' . $choice]);
+ }
+
+ return parent::html($plugin, $echo);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingEmail.php b/platform/www/lib/plugins/config/core/Setting/SettingEmail.php
new file mode 100644
index 0000000..25a0c0e
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingEmail.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_email
+ */
+class SettingEmail extends SettingString {
+ protected $multiple = false;
+ protected $placeholders = false;
+
+ /** @inheritdoc */
+ public function update($input) {
+ if(is_null($input)) return false;
+ if($this->isProtected()) return false;
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+ if($input === '') {
+ $this->local = $input;
+ return true;
+ }
+ $mail = $input;
+
+ if($this->placeholders) {
+ // replace variables with pseudo values
+ $mail = str_replace('@USER@', 'joe', $mail);
+ $mail = str_replace('@NAME@', 'Joe Schmoe', $mail);
+ $mail = str_replace('@MAIL@', 'joe@example.com', $mail);
+ }
+
+ // multiple mail addresses?
+ if($this->multiple) {
+ $mails = array_filter(array_map('trim', explode(',', $mail)));
+ } else {
+ $mails = array($mail);
+ }
+
+ // check them all
+ foreach($mails as $mail) {
+ // only check the address part
+ if(preg_match('#(.*?)<(.*?)>#', $mail, $matches)) {
+ $addr = $matches[2];
+ } else {
+ $addr = $mail;
+ }
+
+ if(!mail_isvalid($addr)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+ }
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingFieldset.php b/platform/www/lib/plugins/config/core/Setting/SettingFieldset.php
new file mode 100644
index 0000000..4e86189
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingFieldset.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * A do-nothing class used to detect the 'fieldset' type.
+ *
+ * Used to start a new settings "display-group".
+ */
+class SettingFieldset extends Setting {
+
+ /** @inheritdoc */
+ public function shouldHaveDefault() {
+ return false;
+ }
+
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingHidden.php b/platform/www/lib/plugins/config/core/Setting/SettingHidden.php
new file mode 100644
index 0000000..ca8a03e
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingHidden.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_hidden
+ */
+class SettingHidden extends Setting {
+ // Used to explicitly ignore a setting in the configuration manager.
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingImConvert.php b/platform/www/lib/plugins/config/core/Setting/SettingImConvert.php
new file mode 100644
index 0000000..8740d94
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingImConvert.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_im_convert
+ */
+class SettingImConvert extends SettingString {
+
+ /** @inheritdoc */
+ public function update($input) {
+ if($this->isProtected()) return false;
+
+ $input = trim($input);
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ if($input && !file_exists($input)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingLicense.php b/platform/www/lib/plugins/config/core/Setting/SettingLicense.php
new file mode 100644
index 0000000..8dacf8e
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingLicense.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_license
+ */
+class SettingLicense extends SettingMultichoice {
+
+ protected $choices = array(''); // none choosen
+
+ /** @inheritdoc */
+ public function initialize($default = null, $local = null, $protected = null) {
+ global $license;
+
+ foreach($license as $key => $data) {
+ $this->choices[] = $key;
+ $this->lang[$this->key . '_o_' . $key] = $data['name']; // stored in setting
+ }
+
+ parent::initialize($default, $local, $protected);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingMulticheckbox.php b/platform/www/lib/plugins/config/core/Setting/SettingMulticheckbox.php
new file mode 100644
index 0000000..df212cc
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingMulticheckbox.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_multicheckbox
+ */
+class SettingMulticheckbox extends SettingString {
+
+ protected $choices = array();
+ protected $combine = array();
+ protected $other = 'always';
+
+ /** @inheritdoc */
+ public function update($input) {
+ if($this->isProtected()) return false;
+
+ // split any combined values + convert from array to comma separated string
+ $input = ($input) ? $input : array();
+ $input = $this->array2str($input);
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ if($this->pattern && !preg_match($this->pattern, $input)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+
+ $this->local = $input;
+ return true;
+ }
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+
+ $disable = '';
+
+ if($this->isProtected()) {
+ $value = $this->protected;
+ $disable = 'disabled="disabled"';
+ } else {
+ if($echo && $this->error) {
+ $value = $this->input;
+ } else {
+ $value = is_null($this->local) ? $this->default : $this->local;
+ }
+ }
+
+ $key = htmlspecialchars($this->key);
+
+ // convert from comma separated list into array + combine complimentary actions
+ $value = $this->str2array($value);
+ $default = $this->str2array($this->default);
+
+ $input = '';
+ foreach($this->choices as $choice) {
+ $idx = array_search($choice, $value);
+ $idx_default = array_search($choice, $default);
+
+ $checked = ($idx !== false) ? 'checked="checked"' : '';
+
+ // @todo ideally this would be handled using a second class of "default"
+ $class = (($idx !== false) == (false !== $idx_default)) ? " selectiondefault" : "";
+
+ $prompt = ($plugin->getLang($this->key . '_' . $choice) ?
+ $plugin->getLang($this->key . '_' . $choice) : htmlspecialchars($choice));
+
+ $input .= '<div class="selection' . $class . '">' . "\n";
+ $input .= '<label for="config___' . $key . '_' . $choice . '">' . $prompt . "</label>\n";
+ $input .= '<input id="config___' . $key . '_' . $choice . '" name="config[' . $key .
+ '][]" type="checkbox" class="checkbox" value="' . $choice . '" ' . $disable . ' ' . $checked . "/>\n";
+ $input .= "</div>\n";
+
+ // remove this action from the disabledactions array
+ if($idx !== false) unset($value[$idx]);
+ if($idx_default !== false) unset($default[$idx_default]);
+ }
+
+ // handle any remaining values
+ if($this->other != 'never') {
+ $other = join(',', $value);
+ // test equivalent to ($this->_other == 'always' || ($other && $this->_other == 'exists')
+ // use != 'exists' rather than == 'always' to ensure invalid values default to 'always'
+ if($this->other != 'exists' || $other) {
+
+ $class = (
+ (count($default) == count($value)) &&
+ (count($value) == count(array_intersect($value, $default)))
+ ) ?
+ " selectiondefault" : "";
+
+ $input .= '<div class="other' . $class . '">' . "\n";
+ $input .= '<label for="config___' . $key . '_other">' .
+ $plugin->getLang($key . '_other') .
+ "</label>\n";
+ $input .= '<input id="config___' . $key . '_other" name="config[' . $key .
+ '][other]" type="text" class="edit" value="' . htmlspecialchars($other) .
+ '" ' . $disable . " />\n";
+ $input .= "</div>\n";
+ }
+ }
+ $label = '<label>' . $this->prompt($plugin) . '</label>';
+ return array($label, $input);
+ }
+
+ /**
+ * convert comma separated list to an array and combine any complimentary values
+ *
+ * @param string $str
+ * @return array
+ */
+ protected function str2array($str) {
+ $array = explode(',', $str);
+
+ if(!empty($this->combine)) {
+ foreach($this->combine as $key => $combinators) {
+ $idx = array();
+ foreach($combinators as $val) {
+ if(($idx[] = array_search($val, $array)) === false) break;
+ }
+
+ if(count($idx) && $idx[count($idx) - 1] !== false) {
+ foreach($idx as $i) unset($array[$i]);
+ $array[] = $key;
+ }
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * convert array of values + other back to a comma separated list, incl. splitting any combined values
+ *
+ * @param array $input
+ * @return string
+ */
+ protected function array2str($input) {
+
+ // handle other
+ $other = trim($input['other']);
+ $other = !empty($other) ? explode(',', str_replace(' ', '', $input['other'])) : array();
+ unset($input['other']);
+
+ $array = array_unique(array_merge($input, $other));
+
+ // deconstruct any combinations
+ if(!empty($this->combine)) {
+ foreach($this->combine as $key => $combinators) {
+
+ $idx = array_search($key, $array);
+ if($idx !== false) {
+ unset($array[$idx]);
+ $array = array_merge($array, $combinators);
+ }
+ }
+ }
+
+ return join(',', array_unique($array));
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingMultichoice.php b/platform/www/lib/plugins/config/core/Setting/SettingMultichoice.php
new file mode 100644
index 0000000..3a50857
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingMultichoice.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_multichoice
+ */
+class SettingMultichoice extends SettingString {
+ protected $choices = array();
+ public $lang; //some custom language strings are stored in setting
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ $disable = '';
+ $nochoice = '';
+
+ if($this->isProtected()) {
+ $value = $this->protected;
+ $disable = ' disabled="disabled"';
+ } else {
+ $value = is_null($this->local) ? $this->default : $this->local;
+ }
+
+ // ensure current value is included
+ if(!in_array($value, $this->choices)) {
+ $this->choices[] = $value;
+ }
+ // disable if no other choices
+ if(!$this->isProtected() && count($this->choices) <= 1) {
+ $disable = ' disabled="disabled"';
+ $nochoice = $plugin->getLang('nochoice');
+ }
+
+ $key = htmlspecialchars($this->key);
+
+ $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
+
+ $input = "<div class=\"input\">\n";
+ $input .= '<select class="edit" id="config___' . $key . '" name="config[' . $key . ']"' . $disable . '>' . "\n";
+ foreach($this->choices as $choice) {
+ $selected = ($value == $choice) ? ' selected="selected"' : '';
+ $option = $plugin->getLang($this->key . '_o_' . $choice);
+ if(!$option && isset($this->lang[$this->key . '_o_' . $choice])) {
+ $option = $this->lang[$this->key . '_o_' . $choice];
+ }
+ if(!$option) $option = $choice;
+
+ $choice = htmlspecialchars($choice);
+ $option = htmlspecialchars($option);
+ $input .= ' <option value="' . $choice . '"' . $selected . ' >' . $option . '</option>' . "\n";
+ }
+ $input .= "</select> $nochoice \n";
+ $input .= "</div>\n";
+
+ return array($label, $input);
+ }
+
+ /** @inheritdoc */
+ public function update($input) {
+ if(is_null($input)) return false;
+ if($this->isProtected()) return false;
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ if(!in_array($input, $this->choices)) return false;
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingNoClass.php b/platform/www/lib/plugins/config/core/Setting/SettingNoClass.php
new file mode 100644
index 0000000..8efff21
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingNoClass.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_no_class
+ * A do-nothing class used to detect settings with a missing setting class.
+ * Used internaly to hide undefined settings, and generate the undefined settings list.
+ */
+class SettingNoClass extends SettingUndefined {
+ protected $errorMessage = '_msg_setting_no_class';
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingNoDefault.php b/platform/www/lib/plugins/config/core/Setting/SettingNoDefault.php
new file mode 100644
index 0000000..07b8412
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingNoDefault.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_no_default
+ *
+ * A do-nothing class used to detect settings with no default value.
+ * Used internaly to hide undefined settings, and generate the undefined settings list.
+ */
+class SettingNoDefault extends SettingUndefined {
+ protected $errorMessage = '_msg_setting_no_default';
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingNoKnownClass.php b/platform/www/lib/plugins/config/core/Setting/SettingNoKnownClass.php
new file mode 100644
index 0000000..3c527e1
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingNoKnownClass.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * A do-nothing class used to detect settings with a missing setting class.
+ * Used internaly to hide undefined settings, and generate the undefined settings list.
+ */
+class SettingNoKnownClass extends SettingUndefined {
+ protected $errorMessage = '_msg_setting_no_known_class';
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingNumeric.php b/platform/www/lib/plugins/config/core/Setting/SettingNumeric.php
new file mode 100644
index 0000000..8a6b179
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingNumeric.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_numeric
+ */
+class SettingNumeric extends SettingString {
+ // This allows for many PHP syntax errors...
+ // var $_pattern = '/^[-+\/*0-9 ]*$/';
+ // much more restrictive, but should eliminate syntax errors.
+ protected $pattern = '/^[-+]? *[0-9]+ *(?:[-+*] *[0-9]+ *)*$/';
+ protected $min = null;
+ protected $max = null;
+
+ /** @inheritdoc */
+ public function update($input) {
+ $local = $this->local;
+ $valid = parent::update($input);
+ if($valid && !(is_null($this->min) && is_null($this->max))) {
+ $numeric_local = (int) eval('return ' . $this->local . ';');
+ if((!is_null($this->min) && $numeric_local < $this->min) ||
+ (!is_null($this->max) && $numeric_local > $this->max)) {
+ $this->error = true;
+ $this->input = $input;
+ $this->local = $local;
+ $valid = false;
+ }
+ }
+ return $valid;
+ }
+
+ /** @inheritdoc */
+ public function out($var, $fmt = 'php') {
+ if($fmt != 'php') return '';
+
+ $local = $this->local === '' ? "''" : $this->local;
+ $out = '$' . $var . "['" . $this->getArrayKey() . "'] = " . $local . ";\n";
+
+ return $out;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingNumericopt.php b/platform/www/lib/plugins/config/core/Setting/SettingNumericopt.php
new file mode 100644
index 0000000..a486e18
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingNumericopt.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_numericopt
+ */
+class SettingNumericopt extends SettingNumeric {
+ // just allow an empty config
+ protected $pattern = '/^(|[-]?[0-9]+(?:[-+*][0-9]+)*)$/';
+
+ /**
+ * @inheritdoc
+ * Empty string is valid for numericopt
+ */
+ public function update($input) {
+ if($input === '') {
+ if($input == $this->local) return false;
+ $this->local = $input;
+ return true;
+ }
+
+ return parent::update($input);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingOnoff.php b/platform/www/lib/plugins/config/core/Setting/SettingOnoff.php
new file mode 100644
index 0000000..780778b
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingOnoff.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_onoff
+ */
+class SettingOnoff extends SettingNumeric {
+
+ /**
+ * We treat the strings 'false' and 'off' as false
+ * @inheritdoc
+ */
+ protected function cleanValue($value) {
+ if($value === null) return null;
+
+ if(is_string($value)) {
+ if(strtolower($value) === 'false') return 0;
+ if(strtolower($value) === 'off') return 0;
+ if(trim($value) === '') return 0;
+ }
+
+ return (int) (bool) $value;
+ }
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ $disable = '';
+
+ if($this->isProtected()) {
+ $value = $this->protected;
+ $disable = ' disabled="disabled"';
+ } else {
+ $value = is_null($this->local) ? $this->default : $this->local;
+ }
+
+ $key = htmlspecialchars($this->key);
+ $checked = ($value) ? ' checked="checked"' : '';
+
+ $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
+ $input = '<div class="input"><input id="config___' . $key . '" name="config[' . $key .
+ ']" type="checkbox" class="checkbox" value="1"' . $checked . $disable . '/></div>';
+ return array($label, $input);
+ }
+
+ /** @inheritdoc */
+ public function update($input) {
+ if($this->isProtected()) return false;
+
+ $input = ($input) ? 1 : 0;
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingPassword.php b/platform/www/lib/plugins/config/core/Setting/SettingPassword.php
new file mode 100644
index 0000000..9d9c533
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingPassword.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_password
+ */
+class SettingPassword extends SettingString {
+
+ protected $code = 'plain'; // mechanism to be used to obscure passwords
+
+ /** @inheritdoc */
+ public function update($input) {
+ if($this->isProtected()) return false;
+ if(!$input) return false;
+
+ if($this->pattern && !preg_match($this->pattern, $input)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+
+ $this->local = conf_encodeString($input, $this->code);
+ return true;
+ }
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+
+ $disable = $this->isProtected() ? 'disabled="disabled"' : '';
+
+ $key = htmlspecialchars($this->key);
+
+ $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
+ $input = '<input id="config___' . $key . '" name="config[' . $key .
+ ']" autocomplete="off" type="password" class="edit" value="" ' . $disable . ' />';
+ return array($label, $input);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingRegex.php b/platform/www/lib/plugins/config/core/Setting/SettingRegex.php
new file mode 100644
index 0000000..b38f0a5
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingRegex.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_regex
+ */
+class SettingRegex extends SettingString {
+
+ protected $delimiter = '/'; // regex delimiter to be used in testing input
+ protected $pregflags = 'ui'; // regex pattern modifiers to be used in testing input
+
+ /** @inheritdoc */
+ public function update($input) {
+
+ // let parent do basic checks, value, not changed, etc.
+ $local = $this->local;
+ if(!parent::update($input)) return false;
+ $this->local = $local;
+
+ // see if the regex compiles and runs (we don't check for effectiveness)
+ $regex = $this->delimiter . $input . $this->delimiter . $this->pregflags;
+ $lastError = error_get_last();
+ @preg_match($regex, 'testdata');
+ if(preg_last_error() != PREG_NO_ERROR || error_get_last() != $lastError) {
+ $this->input = $input;
+ $this->error = true;
+ return false;
+ }
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingRenderer.php b/platform/www/lib/plugins/config/core/Setting/SettingRenderer.php
new file mode 100644
index 0000000..37ba9c7
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingRenderer.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * additional setting classes specific to these settings
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_renderer
+ */
+class SettingRenderer extends SettingMultichoice {
+ protected $prompts = array();
+ protected $format = null;
+
+ /** @inheritdoc */
+ public function initialize($default = null, $local = null, $protected = null) {
+ $format = $this->format;
+
+ foreach(plugin_list('renderer') as $plugin) {
+ $renderer = plugin_load('renderer', $plugin);
+ if(method_exists($renderer, 'canRender') && $renderer->canRender($format)) {
+ $this->choices[] = $plugin;
+
+ $info = $renderer->getInfo();
+ $this->prompts[$plugin] = $info['name'];
+ }
+ }
+
+ parent::initialize($default, $local, $protected);
+ }
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+
+ // make some language adjustments (there must be a better way)
+ // transfer some plugin names to the config plugin
+ foreach($this->choices as $choice) {
+ if(!$plugin->getLang($this->key . '_o_' . $choice)) {
+ if(!isset($this->prompts[$choice])) {
+ $plugin->addLang(
+ $this->key . '_o_' . $choice,
+ sprintf($plugin->getLang('renderer__core'), $choice)
+ );
+ } else {
+ $plugin->addLang(
+ $this->key . '_o_' . $choice,
+ sprintf($plugin->getLang('renderer__plugin'), $this->prompts[$choice])
+ );
+ }
+ }
+ }
+ return parent::html($plugin, $echo);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingSavedir.php b/platform/www/lib/plugins/config/core/Setting/SettingSavedir.php
new file mode 100644
index 0000000..43e428d
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingSavedir.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_savedir
+ */
+class SettingSavedir extends SettingString {
+
+ /** @inheritdoc */
+ public function update($input) {
+ if($this->isProtected()) return false;
+
+ $value = is_null($this->local) ? $this->default : $this->local;
+ if($value == $input) return false;
+
+ if(!init_path($input)) {
+ $this->error = true;
+ $this->input = $input;
+ return false;
+ }
+
+ $this->local = $input;
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingSepchar.php b/platform/www/lib/plugins/config/core/Setting/SettingSepchar.php
new file mode 100644
index 0000000..57cd0ae
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingSepchar.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_sepchar
+ */
+class SettingSepchar extends SettingMultichoice {
+
+ /** @inheritdoc */
+ public function __construct($key, $param = null) {
+ $str = '_-.';
+ for($i = 0; $i < strlen($str); $i++) $this->choices[] = $str[$i];
+
+ // call foundation class constructor
+ parent::__construct($key, $param);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingString.php b/platform/www/lib/plugins/config/core/Setting/SettingString.php
new file mode 100644
index 0000000..b819407
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingString.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+/**
+ * Class setting_string
+ */
+class SettingString extends Setting {
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ $disable = '';
+
+ if($this->isProtected()) {
+ $value = $this->protected;
+ $disable = 'disabled="disabled"';
+ } else {
+ if($echo && $this->error) {
+ $value = $this->input;
+ } else {
+ $value = is_null($this->local) ? $this->default : $this->local;
+ }
+ }
+
+ $key = htmlspecialchars($this->key);
+ $value = htmlspecialchars($value);
+
+ $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
+ $input = '<input id="config___' . $key . '" name="config[' . $key .
+ ']" type="text" class="edit" value="' . $value . '" ' . $disable . '/>';
+ return array($label, $input);
+ }
+}
diff --git a/platform/www/lib/plugins/config/core/Setting/SettingUndefined.php b/platform/www/lib/plugins/config/core/Setting/SettingUndefined.php
new file mode 100644
index 0000000..fa46a9f
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Setting/SettingUndefined.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace dokuwiki\plugin\config\core\Setting;
+
+use dokuwiki\plugin\config\core\Configuration;
+
+/**
+ * A do-nothing class used to detect settings with no metadata entry.
+ * Used internaly to hide undefined settings, and generate the undefined settings list.
+ */
+class SettingUndefined extends SettingHidden {
+
+ protected $errorMessage = '_msg_setting_undefined';
+
+ /** @inheritdoc */
+ public function shouldHaveDefault() {
+ return false;
+ }
+
+ /** @inheritdoc */
+ public function html(\admin_plugin_config $plugin, $echo = false) {
+ // determine the name the meta key would be called
+ if(preg_match(
+ '/^(?:plugin|tpl)' . Configuration::KEYMARKER . '.*?' . Configuration::KEYMARKER . '(.*)$/',
+ $this->getKey(),
+ $undefined_setting_match
+ )) {
+ $undefined_setting_key = $undefined_setting_match[1];
+ } else {
+ $undefined_setting_key = $this->getKey();
+ }
+
+ $label = '<span title="$meta[\'' . $undefined_setting_key . '\']">$' .
+ 'conf' . '[\'' . $this->getArrayKey() . '\']</span>';
+ $input = $plugin->getLang($this->errorMessage);
+
+ return array($label, $input);
+ }
+
+}
diff --git a/platform/www/lib/plugins/config/core/Writer.php b/platform/www/lib/plugins/config/core/Writer.php
new file mode 100644
index 0000000..56de621
--- /dev/null
+++ b/platform/www/lib/plugins/config/core/Writer.php
@@ -0,0 +1,116 @@
+<?php
+
+namespace dokuwiki\plugin\config\core;
+use dokuwiki\plugin\config\core\Setting\Setting;
+
+/**
+ * Writes the settings to the correct local file
+ */
+class Writer {
+ /** @var string header info */
+ protected $header = 'Dokuwiki\'s Main Configuration File - Local Settings';
+
+ /** @var string the file where the config will be saved to */
+ protected $savefile;
+
+ /**
+ * Writer constructor.
+ */
+ public function __construct() {
+ global $config_cascade;
+ $this->savefile = end($config_cascade['main']['local']);
+ }
+
+ /**
+ * Save the given settings
+ *
+ * @param Setting[] $settings
+ * @throws \Exception
+ */
+ public function save($settings) {
+ global $conf;
+ if($this->isLocked()) throw new \Exception('no save');
+
+ // backup current file (remove any existing backup)
+ if(file_exists($this->savefile)) {
+ if(file_exists($this->savefile . '.bak.php')) @unlink($this->savefile . '.bak.php');
+ if(!io_rename($this->savefile, $this->savefile . '.bak.php')) throw new \Exception('no backup');
+ }
+
+ if(!$fh = @fopen($this->savefile, 'wb')) {
+ io_rename($this->savefile . '.bak.php', $this->savefile); // problem opening, restore the backup
+ throw new \Exception('no save');
+ }
+
+ $out = $this->getHeader();
+ foreach($settings as $setting) {
+ if($setting->shouldBeSaved()) {
+ $out .= $setting->out('conf', 'php');
+ }
+ }
+
+ fwrite($fh, $out);
+ fclose($fh);
+ if($conf['fperm']) chmod($this->savefile, $conf['fperm']);
+ $this->opcacheUpdate($this->savefile);
+ }
+
+ /**
+ * Update last modified time stamp of the config file
+ *
+ * Will invalidate all DokuWiki caches
+ *
+ * @throws \Exception when the config isn't writable
+ */
+ public function touch() {
+ if($this->isLocked()) throw new \Exception('no save');
+ @touch($this->savefile);
+ $this->opcacheUpdate($this->savefile);
+ }
+
+ /**
+ * Invalidate the opcache of the given file
+ *
+ * @todo this should probably be moved to core
+ * @param string $file
+ */
+ protected function opcacheUpdate($file) {
+ if(!function_exists('opcache_invalidate')) return;
+ opcache_invalidate($file);
+ }
+
+ /**
+ * Configuration is considered locked if there is no local settings filename
+ * or the directory its in is not writable or the file exists and is not writable
+ *
+ * @return bool true: locked, false: writable
+ */
+ public function isLocked() {
+ if(!$this->savefile) return true;
+ if(!is_writable(dirname($this->savefile))) return true;
+ if(file_exists($this->savefile) && !is_writable($this->savefile)) return true;
+ return false;
+ }
+
+ /**
+ * Returns the PHP intro header for the config file
+ *
+ * @return string
+ */
+ protected function getHeader() {
+ return join(
+ "\n",
+ array(
+ '<?php',
+ '/*',
+ ' * ' . $this->header,
+ ' * Auto-generated by config plugin',
+ ' * Run for user: ' . $_SERVER['REMOTE_USER'],
+ ' * Date: ' . date('r'),
+ ' */',
+ '',
+ ''
+ )
+ );
+ }
+}
diff --git a/platform/www/lib/plugins/config/images/danger.png b/platform/www/lib/plugins/config/images/danger.png
new file mode 100644
index 0000000..da06924
--- /dev/null
+++ b/platform/www/lib/plugins/config/images/danger.png
Binary files differ
diff --git a/platform/www/lib/plugins/config/images/security.png b/platform/www/lib/plugins/config/images/security.png
new file mode 100644
index 0000000..3ee8476
--- /dev/null
+++ b/platform/www/lib/plugins/config/images/security.png
Binary files differ
diff --git a/platform/www/lib/plugins/config/images/warning.png b/platform/www/lib/plugins/config/images/warning.png
new file mode 100644
index 0000000..c1af79f
--- /dev/null
+++ b/platform/www/lib/plugins/config/images/warning.png
Binary files differ
diff --git a/platform/www/lib/plugins/config/lang/en/intro.txt b/platform/www/lib/plugins/config/lang/en/intro.txt
new file mode 100644
index 0000000..0108987
--- /dev/null
+++ b/platform/www/lib/plugins/config/lang/en/intro.txt
@@ -0,0 +1,7 @@
+====== Configuration Manager ======
+
+Use this page to control the settings of your DokuWiki installation. For help on individual settings refer to [[doku>config]]. For more details about this plugin see [[doku>plugin:config]].
+
+Settings shown with a light red background are protected and can not be altered with this plugin. Settings shown with a blue background are the default values and settings shown with a white background have been set locally for this particular installation. Both blue and white settings can be altered.
+
+Remember to press the **Save** button before leaving this page otherwise your changes will be lost.
diff --git a/platform/www/lib/plugins/config/lang/en/lang.php b/platform/www/lib/plugins/config/lang/en/lang.php
new file mode 100644
index 0000000..fb8186c
--- /dev/null
+++ b/platform/www/lib/plugins/config/lang/en/lang.php
@@ -0,0 +1,277 @@
+<?php
+/**
+ * english language file
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ * @author Matthias Schulte <dokuwiki@lupo49.de>
+ * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
+ */
+
+// for admin plugins, the menu prompt to be displayed in the admin menu
+// if set here, the plugin doesn't need to override the getMenuText() method
+$lang['menu'] = 'Configuration Settings';
+
+$lang['error'] = 'Settings not updated due to an invalid value, please review your changes and resubmit.
+ <br />The incorrect value(s) will be shown surrounded by a red border.';
+$lang['updated'] = 'Settings updated successfully.';
+$lang['nochoice'] = '(no other choices available)';
+$lang['locked'] = 'The settings file can not be updated, if this is unintentional, <br />
+ ensure the local settings file name and permissions are correct.';
+
+$lang['danger'] = 'Danger: Changing this option could make your wiki and the configuration menu inaccessible.';
+$lang['warning'] = 'Warning: Changing this option could cause unintended behaviour.';
+$lang['security'] = 'Security Warning: Changing this option could present a security risk.';
+
+/* --- Config Setting Headers --- */
+$lang['_configuration_manager'] = 'Configuration Manager'; //same as heading in intro.txt
+$lang['_header_dokuwiki'] = 'DokuWiki';
+$lang['_header_plugin'] = 'Plugin';
+$lang['_header_template'] = 'Template';
+$lang['_header_undefined'] = 'Undefined Settings';
+
+/* --- Config Setting Groups --- */
+$lang['_basic'] = 'Basic';
+$lang['_display'] = 'Display';
+$lang['_authentication'] = 'Authentication';
+$lang['_anti_spam'] = 'Anti-Spam';
+$lang['_editing'] = 'Editing';
+$lang['_links'] = 'Links';
+$lang['_media'] = 'Media';
+$lang['_notifications'] = 'Notification';
+$lang['_syndication'] = 'Syndication (RSS)';
+$lang['_advanced'] = 'Advanced';
+$lang['_network'] = 'Network';
+
+/* --- Undefined Setting Messages --- */
+$lang['_msg_setting_undefined'] = 'No setting metadata.';
+$lang['_msg_setting_no_class'] = 'No setting class.';
+$lang['_msg_setting_no_known_class'] = 'Setting class not available.';
+$lang['_msg_setting_no_default'] = 'No default value.';
+
+/* -------------------- Config Options --------------------------- */
+
+/* Basic Settings */
+$lang['title'] = 'Wiki title aka. your wiki\'s name';
+$lang['start'] = 'Page name to use as the starting point for each namespace';
+$lang['lang'] = 'Interface language';
+$lang['template'] = 'Template aka. the design of the wiki.';
+$lang['tagline'] = 'Tagline (if template supports it)';
+$lang['sidebar'] = 'Sidebar page name (if template supports it), empty field disables the sidebar';
+$lang['license'] = 'Under which license should your content be released?';
+$lang['savedir'] = 'Directory for saving data';
+$lang['basedir'] = 'Server path (eg. <code>/dokuwiki/</code>). Leave blank for autodetection.';
+$lang['baseurl'] = 'Server URL (eg. <code>http://www.yourserver.com</code>). Leave blank for autodetection.';
+$lang['cookiedir'] = 'Cookie path. Leave blank for using baseurl.';
+$lang['dmode'] = 'Directory creation mode';
+$lang['fmode'] = 'File creation mode';
+$lang['allowdebug'] = 'Allow debug. <b>Disable if not needed!</b>';
+
+/* Display Settings */
+$lang['recent'] = 'Number of entries per page in the recent changes';
+$lang['recent_days'] = 'How many recent changes to keep (days)';
+$lang['breadcrumbs'] = 'Number of "trace" breadcrumbs. Set to 0 to disable.';
+$lang['youarehere'] = 'Use hierarchical breadcrumbs (you probably want to disable the above option then)';
+$lang['fullpath'] = 'Reveal full path of pages in the footer';
+$lang['typography'] = 'Do typographical replacements';
+$lang['dformat'] = 'Date format (see PHP\'s <a href="http://php.net/strftime">strftime</a> function)';
+$lang['signature'] = 'What to insert with the signature button in the editor';
+$lang['showuseras'] = 'What to display when showing the user that last edited a page';
+$lang['toptoclevel'] = 'Top level for table of contents';
+$lang['tocminheads'] = 'Minimum amount of headlines that determines whether the TOC is built';
+$lang['maxtoclevel'] = 'Maximum level for table of contents';
+$lang['maxseclevel'] = 'Maximum section edit level';
+$lang['camelcase'] = 'Use CamelCase for links';
+$lang['deaccent'] = 'How to clean pagenames';
+$lang['useheading'] = 'Use first heading for pagenames';
+$lang['sneaky_index'] = 'By default, DokuWiki will show all namespaces in the sitemap. Enabling this option will hide those where the user doesn\'t have read permissions. This might result in hiding of accessable subnamespaces which may make the index unusable with certain ACL setups.';
+$lang['hidepages'] = 'Hide pages matching this regular expression from search, the sitemap and other automatic indexes';
+
+/* Authentication Settings */
+$lang['useacl'] = 'Use access control lists';
+$lang['autopasswd'] = 'Autogenerate passwords';
+$lang['authtype'] = 'Authentication backend';
+$lang['passcrypt'] = 'Password encryption method';
+$lang['defaultgroup']= 'Default group, all new users will be placed in this group';
+$lang['superuser'] = 'Superuser - group, user or comma separated list user1,@group1,user2 with full access to all pages and functions regardless of the ACL settings';
+$lang['manager'] = 'Manager - group, user or comma separated list user1,@group1,user2 with access to certain management functions';
+$lang['profileconfirm'] = 'Confirm profile changes with password';
+$lang['rememberme'] = 'Allow permanent login cookies (remember me)';
+$lang['disableactions'] = 'Disable DokuWiki actions';
+$lang['disableactions_check'] = 'Check';
+$lang['disableactions_subscription'] = 'Subscribe/Unsubscribe';
+$lang['disableactions_wikicode'] = 'View source/Export Raw';
+$lang['disableactions_profile_delete'] = 'Delete Own Account';
+$lang['disableactions_other'] = 'Other actions (comma separated)';
+$lang['disableactions_rss'] = 'XML Syndication (RSS)';
+$lang['auth_security_timeout'] = 'Authentication Security Timeout (seconds)';
+$lang['securecookie'] = 'Should cookies set via HTTPS only be sent via HTTPS by the browser? Disable this option when only the login of your wiki is secured with SSL but browsing the wiki is done unsecured.';
+$lang['remote'] = 'Enable the remote API system. This allows other applications to access the wiki via XML-RPC or other mechanisms.';
+$lang['remoteuser'] = 'Restrict remote API access to the comma separated groups or users given here. Leave empty to give access to everyone.';
+
+/* Anti-Spam Settings */
+$lang['usewordblock']= 'Block spam based on wordlist';
+$lang['relnofollow'] = 'Use rel="ugc nofollow" on external links';
+$lang['indexdelay'] = 'Time delay before indexing (sec)';
+$lang['mailguard'] = 'Obfuscate email addresses';
+$lang['iexssprotect']= 'Check uploaded files for possibly malicious JavaScript or HTML code';
+
+/* Editing Settings */
+$lang['usedraft'] = 'Automatically save a draft while editing';
+$lang['htmlok'] = 'Allow embedded HTML';
+$lang['phpok'] = 'Allow embedded PHP';
+$lang['locktime'] = 'Maximum age for lock files (sec)';
+$lang['cachetime'] = 'Maximum age for cache (sec)';
+
+/* Link settings */
+$lang['target____wiki'] = 'Target window for internal links';
+$lang['target____interwiki'] = 'Target window for interwiki links';
+$lang['target____extern'] = 'Target window for external links';
+$lang['target____media'] = 'Target window for media links';
+$lang['target____windows'] = 'Target window for windows links';
+
+/* Media Settings */
+$lang['mediarevisions'] = 'Enable Mediarevisions?';
+$lang['refcheck'] = 'Check if a media file is still in use before deleting it';
+$lang['gdlib'] = 'GD Lib version';
+$lang['im_convert'] = 'Path to ImageMagick\'s convert tool';
+$lang['jpg_quality'] = 'JPG compression quality (0-100)';
+$lang['fetchsize'] = 'Maximum size (bytes) fetch.php may download from external URLs, eg. to cache and resize external images.';
+
+/* Notification Settings */
+$lang['subscribers'] = 'Allow users to subscribe to page changes by email';
+$lang['subscribe_time'] = 'Time after which subscription lists and digests are sent (sec); This should be smaller than the time specified in recent_days.';
+$lang['notify'] = 'Always send change notifications to this email address';
+$lang['registernotify'] = 'Always send info on newly registered users to this email address';
+$lang['mailfrom'] = 'Sender email address to use for automatic mails';
+$lang['mailreturnpath'] = 'Recipient email address for non delivery notifications';
+$lang['mailprefix'] = 'Email subject prefix to use for automatic mails. Leave blank to use the wiki title';
+$lang['htmlmail'] = 'Send better looking, but larger in size HTML multipart emails. Disable for plain text only mails.';
+
+/* Syndication Settings */
+$lang['sitemap'] = 'Generate Google sitemap this often (in days). 0 to disable';
+$lang['rss_type'] = 'XML feed type';
+$lang['rss_linkto'] = 'XML feed links to';
+$lang['rss_content'] = 'What to display in the XML feed items?';
+$lang['rss_update'] = 'XML feed update interval (sec)';
+$lang['rss_show_summary'] = 'XML feed show summary in title';
+$lang['rss_show_deleted'] = 'XML feed Show deleted feeds';
+$lang['rss_media'] = 'What kind of changes should be listed in the XML feed?';
+$lang['rss_media_o_both'] = 'both';
+$lang['rss_media_o_pages'] = 'pages';
+$lang['rss_media_o_media'] = 'media';
+
+
+/* Advanced Options */
+$lang['updatecheck'] = 'Check for updates and security warnings? DokuWiki needs to contact update.dokuwiki.org for this feature.';
+$lang['userewrite'] = 'Use nice URLs';
+$lang['useslash'] = 'Use slash as namespace separator in URLs';
+$lang['sepchar'] = 'Page name word separator';
+$lang['canonical'] = 'Use fully canonical URLs';
+$lang['fnencode'] = 'Method for encoding non-ASCII filenames.';
+$lang['autoplural'] = 'Check for plural forms in links';
+$lang['compression'] = 'Compression method for attic files';
+$lang['gzip_output'] = 'Use gzip Content-Encoding for xhtml';
+$lang['compress'] = 'Compact CSS and javascript output';
+$lang['cssdatauri'] = 'Size in bytes up to which images referenced in CSS files should be embedded right into the stylesheet to reduce HTTP request header overhead. <code>400</code> to <code>600</code> bytes is a good value. Set <code>0</code> to disable.';
+$lang['send404'] = 'Send "HTTP 404/Page Not Found" for non existing pages';
+$lang['broken_iua'] = 'Is the ignore_user_abort function broken on your system? This could cause a non working search index. IIS+PHP/CGI is known to be broken. See <a href="http://bugs.dokuwiki.org/?do=details&amp;task_id=852">Bug 852</a> for more info.';
+$lang['xsendfile'] = 'Use the X-Sendfile header to let the webserver deliver static files? Your webserver needs to support this.';
+$lang['renderer_xhtml'] = 'Renderer to use for main (xhtml) wiki output';
+$lang['renderer__core'] = '%s (dokuwiki core)';
+$lang['renderer__plugin'] = '%s (plugin)';
+$lang['search_nslimit'] = 'Limit the search to the current X namespaces. When a search is executed from a page within a deeper namespace, the first X namespaces will be added as filter';
+$lang['search_fragment'] = 'Specify the default fragment search behavior';
+$lang['search_fragment_o_exact'] = 'exact';
+$lang['search_fragment_o_starts_with'] = 'starts with';
+$lang['search_fragment_o_ends_with'] = 'ends with';
+$lang['search_fragment_o_contains'] = 'contains';
+$lang['trustedproxy'] = 'Trust forwarding proxies matching this regular expression about the true client IP they report. The default matches local networks. Leave empty to trust no proxy.';
+
+$lang['_feature_flags'] = 'Feature Flags';
+$lang['defer_js'] = 'Defer javascript to be execute after the page\'s HTML has been parsed. Improves perceived page speed but could break a small number of plugins.';
+
+/* Network Options */
+$lang['dnslookups'] = 'DokuWiki will lookup hostnames for remote IP addresses of users editing pages. If you have a slow or non working DNS server or don\'t want this feature, disable this option';
+$lang['jquerycdn'] = 'Should the jQuery and jQuery UI script files be loaded from a CDN? This adds additional HTTP requests, but files may load faster and users may have them cached already.';
+
+/* jQuery CDN options */
+$lang['jquerycdn_o_0'] = 'No CDN, local delivery only';
+$lang['jquerycdn_o_jquery'] = 'CDN at code.jquery.com';
+$lang['jquerycdn_o_cdnjs'] = 'CDN at cdnjs.com';
+
+/* Proxy Options */
+$lang['proxy____host'] = 'Proxy servername';
+$lang['proxy____port'] = 'Proxy port';
+$lang['proxy____user'] = 'Proxy user name';
+$lang['proxy____pass'] = 'Proxy password';
+$lang['proxy____ssl'] = 'Use SSL to connect to proxy';
+$lang['proxy____except'] = 'Regular expression to match URLs for which the proxy should be skipped.';
+
+/* License Options */
+$lang['license_o_'] = 'None chosen';
+
+/* typography options */
+$lang['typography_o_0'] = 'none';
+$lang['typography_o_1'] = 'excluding single quotes';
+$lang['typography_o_2'] = 'including single quotes (might not always work)';
+
+/* userewrite options */
+$lang['userewrite_o_0'] = 'none';
+$lang['userewrite_o_1'] = '.htaccess';
+$lang['userewrite_o_2'] = 'DokuWiki internal';
+
+/* deaccent options */
+$lang['deaccent_o_0'] = 'off';
+$lang['deaccent_o_1'] = 'remove accents';
+$lang['deaccent_o_2'] = 'romanize';
+
+/* gdlib options */
+$lang['gdlib_o_0'] = 'GD Lib not available';
+$lang['gdlib_o_1'] = 'Version 1.x';
+$lang['gdlib_o_2'] = 'Autodetection';
+
+/* rss_type options */
+$lang['rss_type_o_rss'] = 'RSS 0.91';
+$lang['rss_type_o_rss1'] = 'RSS 1.0';
+$lang['rss_type_o_rss2'] = 'RSS 2.0';
+$lang['rss_type_o_atom'] = 'Atom 0.3';
+$lang['rss_type_o_atom1'] = 'Atom 1.0';
+
+/* rss_content options */
+$lang['rss_content_o_abstract'] = 'Abstract';
+$lang['rss_content_o_diff'] = 'Unified Diff';
+$lang['rss_content_o_htmldiff'] = 'HTML formatted diff table';
+$lang['rss_content_o_html'] = 'Full HTML page content';
+
+/* rss_linkto options */
+$lang['rss_linkto_o_diff'] = 'difference view';
+$lang['rss_linkto_o_page'] = 'the revised page';
+$lang['rss_linkto_o_rev'] = 'list of revisions';
+$lang['rss_linkto_o_current'] = 'the current page';
+
+/* compression options */
+$lang['compression_o_0'] = 'none';
+$lang['compression_o_gz'] = 'gzip';
+$lang['compression_o_bz2'] = 'bz2';
+
+/* xsendfile header */
+$lang['xsendfile_o_0'] = "don't use";
+$lang['xsendfile_o_1'] = 'Proprietary lighttpd header (before release 1.5)';
+$lang['xsendfile_o_2'] = 'Standard X-Sendfile header';
+$lang['xsendfile_o_3'] = 'Proprietary Nginx X-Accel-Redirect header';
+
+/* Display user info */
+$lang['showuseras_o_loginname'] = 'Login name';
+$lang['showuseras_o_username'] = "User's full name";
+$lang['showuseras_o_username_link'] = "User's full name as interwiki user link";
+$lang['showuseras_o_email'] = "User's e-mail addresss (obfuscated according to mailguard setting)";
+$lang['showuseras_o_email_link'] = "User's e-mail addresss as a mailto: link";
+
+/* useheading options */
+$lang['useheading_o_0'] = 'Never';
+$lang['useheading_o_navigation'] = 'Navigation Only';
+$lang['useheading_o_content'] = 'Wiki Content Only';
+$lang['useheading_o_1'] = 'Always';
+
+$lang['readdircache'] = 'Maximum age for readdir cache (sec)';
diff --git a/platform/www/lib/plugins/config/plugin.info.txt b/platform/www/lib/plugins/config/plugin.info.txt
new file mode 100644
index 0000000..ddd7265
--- /dev/null
+++ b/platform/www/lib/plugins/config/plugin.info.txt
@@ -0,0 +1,7 @@
+base config
+author Christopher Smith
+email chris@jalakai.co.uk
+date 2015-07-18
+name Configuration Manager
+desc Manage Dokuwiki's Configuration Settings
+url http://dokuwiki.org/plugin:config
diff --git a/platform/www/lib/plugins/config/settings/config.metadata.php b/platform/www/lib/plugins/config/settings/config.metadata.php
new file mode 100644
index 0000000..6fdd64d
--- /dev/null
+++ b/platform/www/lib/plugins/config/settings/config.metadata.php
@@ -0,0 +1,245 @@
+<?php
+/**
+ * Metadata for configuration manager plugin
+ *
+ * Note: This file is loaded in Loader::loadMeta().
+ *
+ * Format:
+ * $meta[<setting name>] = array(<handler class id>,<param name> => <param value>);
+ *
+ * <handler class id> is the handler class name without the "setting_" prefix
+ *
+ * Defined classes (see core/Setting/*):
+ * Generic
+ * -------------------------------------------
+ * '' - default class ('setting'), textarea, minimal input validation, setting output in quotes
+ * 'string' - single line text input, minimal input validation, setting output in quotes
+ * 'numeric' - text input, accepts numbers and arithmetic operators, setting output without quotes
+ * if given the '_min' and '_max' parameters are used for validation
+ * 'numericopt' - like above, but accepts empty values
+ * 'onoff' - checkbox input, setting output 0|1
+ * 'multichoice' - select input (single choice), setting output with quotes, required _choices parameter
+ * 'email' - text input, input must conform to email address format, supports optional '_multiple'
+ * parameter for multiple comma separated email addresses
+ * 'password' - password input, minimal input validation, setting output text in quotes, maybe encoded
+ * according to the _code parameter
+ * 'dirchoice' - as multichoice, selection choices based on folders found at location specified in _dir
+ * parameter (required). A pattern can be used to restrict the folders to only those which
+ * match the pattern.
+ * 'multicheckbox'- a checkbox for each choice plus an "other" string input, config file setting is a comma
+ * separated list of checked choices
+ * 'fieldset' - used to group configuration settings, but is not itself a setting. To make this clear in
+ * the language files the keys for this type should start with '_'.
+ * 'array' - a simple (one dimensional) array of string values, shown as comma separated list in the
+ * config manager but saved as PHP array(). Values may not contain commas themselves.
+ * _pattern matching on the array values supported.
+ * 'regex' - regular expression string, normally without delimiters; as for string, in addition tested
+ * to see if will compile & run as a regex. in addition to _pattern, also accepts _delimiter
+ * (default '/') and _pregflags (default 'ui')
+ *
+ * Single Setting
+ * -------------------------------------------------
+ * 'savedir' - as 'setting', input tested against initpath() (inc/init.php)
+ * 'sepchar' - as multichoice, selection constructed from string of valid values
+ * 'authtype' - as 'setting', input validated against a valid php file at expected location for auth files
+ * 'im_convert' - as 'setting', input must exist and be an im_convert module
+ * 'disableactions' - as 'setting'
+ * 'compression' - no additional parameters. checks php installation supports possible compression alternatives
+ * 'licence' - as multichoice, selection constructed from licence strings in language files
+ * 'renderer' - as multichoice, selection constructed from enabled renderer plugins which canRender()
+ * 'authtype' - as multichoice, selection constructed from the enabled auth plugins
+ *
+ * Any setting commented or missing will use 'setting' class - text input, minimal validation, quoted output
+ *
+ * Defined parameters:
+ * '_caution' - no value (default) or 'warning', 'danger', 'security'. display an alert along with the setting
+ * '_pattern' - string, a preg pattern. input is tested against this pattern before being accepted
+ * optional all classes, except onoff & multichoice which ignore it
+ * '_choices' - array of choices. used to populate a selection box. choice will be replaced by a localised
+ * language string, indexed by <setting name>_o_<choice>, if one exists
+ * required by 'multichoice' & 'multicheckbox' classes, ignored by others
+ * '_dir' - location of directory to be used to populate choice list
+ * required by 'dirchoice' class, ignored by other classes
+ * '_combine' - complimentary output setting values which can be combined into a single display checkbox
+ * optional for 'multicheckbox', ignored by other classes
+ * '_code' - encoding method to use, accepted values: 'base64','uuencode','plain'. defaults to plain.
+ * '_min' - minimum numeric value, optional for 'numeric' and 'numericopt', ignored by others
+ * '_max' - maximum numeric value, optional for 'numeric' and 'numericopt', ignored by others
+ * '_delimiter' - string, default '/', a single character used as a delimiter for testing regex input values
+ * '_pregflags' - string, default 'ui', valid preg pattern modifiers used when testing regex input values, for more
+ * information see http://php.net/manual/en/reference.pcre.pattern.modifiers.php
+ * '_multiple' - bool, allow multiple comma separated email values; optional for 'email', ignored by others
+ * '_other' - how to handle other values (not listed in _choices). accepted values: 'always','exists','never'
+ * default value 'always'. 'exists' only shows 'other' input field when the setting contains value(s)
+ * not listed in choices (e.g. due to manual editing or update changing _choices). This is safer than
+ * 'never' as it will not discard unknown/other values.
+ * optional for 'multicheckbox', ignored by others
+ *
+ * The order of the settings influences the order in which they apppear in the config manager
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+
+$meta['_basic'] = array('fieldset');
+$meta['title'] = array('string');
+$meta['start'] = array('string','_caution' => 'warning','_pattern' => '!^[^:;/]+$!'); // don't accept namespaces
+$meta['lang'] = array('dirchoice','_dir' => DOKU_INC.'inc/lang/');
+$meta['template'] = array('dirchoice','_dir' => DOKU_INC.'lib/tpl/','_pattern' => '/^[\w-]+$/');
+$meta['tagline'] = array('string');
+$meta['sidebar'] = array('string');
+$meta['license'] = array('license');
+$meta['savedir'] = array('savedir','_caution' => 'danger');
+$meta['basedir'] = array('string','_caution' => 'danger');
+$meta['baseurl'] = array('string','_caution' => 'danger');
+$meta['cookiedir'] = array('string','_caution' => 'danger');
+$meta['dmode'] = array('numeric','_pattern' => '/0[0-7]{3,4}/'); // only accept octal representation
+$meta['fmode'] = array('numeric','_pattern' => '/0[0-7]{3,4}/'); // only accept octal representation
+$meta['allowdebug'] = array('onoff','_caution' => 'security');
+
+$meta['_display'] = array('fieldset');
+$meta['recent'] = array('numeric');
+$meta['recent_days'] = array('numeric');
+$meta['breadcrumbs'] = array('numeric','_min' => 0);
+$meta['youarehere'] = array('onoff');
+$meta['fullpath'] = array('onoff','_caution' => 'security');
+$meta['typography'] = array('multichoice','_choices' => array(0,1,2));
+$meta['dformat'] = array('string');
+$meta['signature'] = array('string');
+$meta['showuseras'] = array(
+ 'multichoice',
+ '_choices' => array('loginname', 'username', 'username_link', 'email', 'email_link')
+);
+$meta['toptoclevel'] = array('multichoice','_choices' => array(1,2,3,4,5)); // 5 toc levels
+$meta['tocminheads'] = array('multichoice','_choices' => array(0,1,2,3,4,5,10,15,20));
+$meta['maxtoclevel'] = array('multichoice','_choices' => array(0,1,2,3,4,5));
+$meta['maxseclevel'] = array('multichoice','_choices' => array(0,1,2,3,4,5)); // 0 for no sec edit buttons
+$meta['camelcase'] = array('onoff','_caution' => 'warning');
+$meta['deaccent'] = array('multichoice','_choices' => array(0,1,2),'_caution' => 'warning');
+$meta['useheading'] = array('multichoice','_choices' => array(0,'navigation','content',1));
+$meta['sneaky_index'] = array('onoff');
+$meta['hidepages'] = array('regex');
+
+$meta['_authentication'] = array('fieldset');
+$meta['useacl'] = array('onoff','_caution' => 'danger');
+$meta['autopasswd'] = array('onoff');
+$meta['authtype'] = array('authtype','_caution' => 'danger');
+$meta['passcrypt'] = array('multichoice','_choices' => array(
+ 'smd5','md5','apr1','sha1','ssha','lsmd5','crypt','mysql','my411','kmd5','pmd5','hmd5',
+ 'mediawiki','bcrypt','djangomd5','djangosha1','djangopbkdf2_sha1','djangopbkdf2_sha256',
+ 'sha512','argon2i','argon2id'
+));
+$meta['defaultgroup']= array('string');
+$meta['superuser'] = array('string','_caution' => 'danger');
+$meta['manager'] = array('string');
+$meta['profileconfirm'] = array('onoff');
+$meta['rememberme'] = array('onoff');
+$meta['disableactions'] = array(
+ 'disableactions',
+ '_choices' => array(
+ 'backlink',
+ 'index',
+ 'recent',
+ 'revisions',
+ 'search',
+ 'subscription',
+ 'register',
+ 'resendpwd',
+ 'profile',
+ 'profile_delete',
+ 'edit',
+ 'wikicode',
+ 'check',
+ 'rss'
+ ),
+ '_combine' => array(
+ 'subscription' => array('subscribe', 'unsubscribe'),
+ 'wikicode' => array('source', 'export_raw')
+ )
+);
+$meta['auth_security_timeout'] = array('numeric');
+$meta['securecookie'] = array('onoff');
+$meta['remote'] = array('onoff','_caution' => 'security');
+$meta['remoteuser'] = array('string');
+
+$meta['_anti_spam'] = array('fieldset');
+$meta['usewordblock']= array('onoff');
+$meta['relnofollow'] = array('onoff');
+$meta['indexdelay'] = array('numeric');
+$meta['mailguard'] = array('multichoice','_choices' => array('visible','hex','none'));
+$meta['iexssprotect']= array('onoff','_caution' => 'security');
+
+$meta['_editing'] = array('fieldset');
+$meta['usedraft'] = array('onoff');
+$meta['htmlok'] = array('onoff','_caution' => 'security');
+$meta['phpok'] = array('onoff','_caution' => 'security');
+$meta['locktime'] = array('numeric');
+$meta['cachetime'] = array('numeric');
+
+$meta['_links'] = array('fieldset');
+$meta['target____wiki'] = array('string');
+$meta['target____interwiki'] = array('string');
+$meta['target____extern'] = array('string');
+$meta['target____media'] = array('string');
+$meta['target____windows'] = array('string');
+
+$meta['_media'] = array('fieldset');
+$meta['mediarevisions'] = array('onoff');
+$meta['gdlib'] = array('multichoice','_choices' => array(0,1,2));
+$meta['im_convert'] = array('im_convert');
+$meta['jpg_quality'] = array('numeric','_pattern' => '/^100$|^[1-9]?[0-9]$/'); //(0-100)
+$meta['fetchsize'] = array('numeric');
+$meta['refcheck'] = array('onoff');
+
+$meta['_notifications'] = array('fieldset');
+$meta['subscribers'] = array('onoff');
+$meta['subscribe_time'] = array('numeric');
+$meta['notify'] = array('email', '_multiple' => true);
+$meta['registernotify'] = array('email', '_multiple' => true);
+$meta['mailfrom'] = array('email', '_placeholders' => true);
+$meta['mailreturnpath'] = array('email', '_placeholders' => true);
+$meta['mailprefix'] = array('string');
+$meta['htmlmail'] = array('onoff');
+
+$meta['_syndication'] = array('fieldset');
+$meta['sitemap'] = array('numeric');
+$meta['rss_type'] = array('multichoice','_choices' => array('rss','rss1','rss2','atom','atom1'));
+$meta['rss_linkto'] = array('multichoice','_choices' => array('diff','page','rev','current'));
+$meta['rss_content'] = array('multichoice','_choices' => array('abstract','diff','htmldiff','html'));
+$meta['rss_media'] = array('multichoice','_choices' => array('both','pages','media'));
+$meta['rss_update'] = array('numeric');
+$meta['rss_show_summary'] = array('onoff');
+$meta['rss_show_deleted'] = array('onoff');
+
+$meta['_advanced'] = array('fieldset');
+$meta['updatecheck'] = array('onoff');
+$meta['userewrite'] = array('multichoice','_choices' => array(0,1,2),'_caution' => 'danger');
+$meta['useslash'] = array('onoff');
+$meta['sepchar'] = array('sepchar','_caution' => 'warning');
+$meta['canonical'] = array('onoff');
+$meta['fnencode'] = array('multichoice','_choices' => array('url','safe','utf-8'),'_caution' => 'warning');
+$meta['autoplural'] = array('onoff');
+$meta['compress'] = array('onoff');
+$meta['cssdatauri'] = array('numeric','_pattern' => '/^\d+$/');
+$meta['gzip_output'] = array('onoff');
+$meta['send404'] = array('onoff');
+$meta['compression'] = array('compression','_caution' => 'warning');
+$meta['broken_iua'] = array('onoff');
+$meta['xsendfile'] = array('multichoice','_choices' => array(0,1,2,3),'_caution' => 'warning');
+$meta['renderer_xhtml'] = array('renderer','_format' => 'xhtml','_choices' => array('xhtml'),'_caution' => 'warning');
+$meta['readdircache'] = array('numeric');
+$meta['search_nslimit'] = array('numeric', '_min' => 0);
+$meta['search_fragment'] = array('multichoice','_choices' => array('exact', 'starts_with', 'ends_with', 'contains'),);
+$meta['trustedproxy'] = array('regex');
+
+$meta['_feature_flags'] = ['fieldset'];
+$meta['defer_js'] = ['onoff'];
+
+$meta['_network'] = array('fieldset');
+$meta['dnslookups'] = array('onoff');
+$meta['jquerycdn'] = array('multichoice', '_choices' => array(0,'jquery', 'cdnjs'));
+$meta['proxy____host'] = array('string','_pattern' => '#^(|[a-z0-9\-\.+]+)$#i');
+$meta['proxy____port'] = array('numericopt');
+$meta['proxy____user'] = array('string');
+$meta['proxy____pass'] = array('password','_code' => 'base64');
+$meta['proxy____ssl'] = array('onoff');
+$meta['proxy____except'] = array('string');
diff --git a/platform/www/lib/plugins/config/style.css b/platform/www/lib/plugins/config/style.css
new file mode 100644
index 0000000..054021e
--- /dev/null
+++ b/platform/www/lib/plugins/config/style.css
@@ -0,0 +1,167 @@
+/* plugin:configmanager */
+#config__manager div.success,
+#config__manager div.error,
+#config__manager div.info {
+ background-position: 0.5em;
+ padding: 0.5em;
+ text-align: center;
+}
+
+#config__manager fieldset {
+ margin: 1em;
+ width: auto;
+ margin-bottom: 2em;
+ background-color: __background_alt__;
+ color: __text__;
+ padding: 0 1em;
+}
+[dir=rtl] #config__manager fieldset {
+ clear: both;
+}
+#config__manager legend {
+ font-size: 1.25em;
+}
+
+#config__manager form { }
+#config__manager table {
+ margin: 1em 0;
+ width: 100%;
+}
+
+#config__manager fieldset td {
+ text-align: left;
+}
+[dir=rtl] #config__manager fieldset td {
+ text-align: right;
+}
+#config__manager fieldset td.value {
+ /* fixed data column width */
+ width: 31em;
+}
+
+[dir=rtl] #config__manager label {
+ text-align: right;
+}
+[dir=rtl] #config__manager td.value input.checkbox {
+ float: right;
+ padding-left: 0;
+ padding-right: 0.7em;
+}
+[dir=rtl] #config__manager td.value label {
+ float: left;
+}
+
+#config__manager td.label {
+ padding: 0.8em 0 0.6em 1em;
+ vertical-align: top;
+}
+[dir=rtl] #config__manager td.label {
+ padding: 0.8em 1em 0.6em 0;
+}
+
+#config__manager td.label label {
+ clear: left;
+ display: block;
+}
+[dir=rtl] #config__manager td.label label {
+ clear: right;
+}
+#config__manager td.label img {
+ padding: 0 10px;
+ vertical-align: middle;
+ float: right;
+}
+[dir=rtl] #config__manager td.label img {
+ float: left;
+}
+
+#config__manager td.label span.outkey {
+ font-size: 70%;
+ margin-top: -1.7em;
+ margin-left: -1em;
+ display: block;
+ background-color: __background__;
+ color: __text_neu__;
+ float: left;
+ padding: 0 0.1em;
+ position: relative;
+ z-index: 1;
+}
+[dir=rtl] #config__manager td.label span.outkey {
+ float: right;
+ margin-right: 1em;
+}
+
+#config__manager td input.edit {
+ width: 30em;
+}
+#config__manager td .input {
+ width: 30.8em;
+}
+#config__manager td select.edit { }
+#config__manager td textarea.edit {
+ width: 27.5em;
+ height: 4em;
+}
+
+#config__manager td textarea.edit:focus {
+ height: 10em;
+}
+
+#config__manager tr .input,
+#config__manager tr input,
+#config__manager tr textarea,
+#config__manager tr select {
+ background-color: #fff;
+ color: #000;
+}
+
+#config__manager tr.default .input,
+#config__manager tr.default input,
+#config__manager tr.default textarea,
+#config__manager tr.default select,
+#config__manager .selectiondefault {
+ background-color: #ccddff;
+ color: #000;
+}
+
+#config__manager tr.protected .input,
+#config__manager tr.protected input,
+#config__manager tr.protected textarea,
+#config__manager tr.protected select,
+#config__manager tr.protected .selection {
+ background-color: #ffcccc!important;
+ color: #000 !important;
+}
+
+#config__manager td.error { background-color: red; color: #000; }
+
+#config__manager .selection {
+ width: 14.8em;
+ float: left;
+ margin: 0 0.3em 2px 0;
+}
+[dir=rtl] #config__manager .selection {
+ width: 14.8em;
+ float: right;
+ margin: 0 0 2px 0.3em;
+}
+
+#config__manager .selection label {
+ float: right;
+ width: 14em;
+ font-size: 90%;
+}
+
+
+#config__manager .other {
+ clear: both;
+ padding-top: 0.5em;
+}
+
+#config__manager .other label {
+ padding-left: 2px;
+ font-size: 90%;
+}
+
+/* end plugin:configmanager */
diff --git a/platform/www/lib/plugins/extension/action.php b/platform/www/lib/plugins/extension/action.php
new file mode 100644
index 0000000..3bb0448
--- /dev/null
+++ b/platform/www/lib/plugins/extension/action.php
@@ -0,0 +1,82 @@
+<?php
+/** DokuWiki Plugin extension (Action Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+class action_plugin_extension extends DokuWiki_Action_Plugin
+{
+
+ /**
+ * Registers a callback function for a given event
+ *
+ * @param Doku_Event_Handler $controller DokuWiki's event controller object
+ * @return void
+ */
+ public function register(Doku_Event_Handler $controller)
+ {
+ $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'info');
+ }
+
+ /**
+ * Create the detail info for a single plugin
+ *
+ * @param Doku_Event $event
+ * @param $param
+ */
+ public function info(Doku_Event $event, $param)
+ {
+ global $USERINFO;
+ global $INPUT;
+
+ if ($event->data != 'plugin_extension') return;
+ $event->preventDefault();
+ $event->stopPropagation();
+
+ /** @var admin_plugin_extension $admin */
+ $admin = plugin_load('admin', 'extension');
+ if (!$admin->isAccessibleByCurrentUser()) {
+ http_status(403);
+ echo 'Forbidden';
+ exit;
+ }
+
+ $ext = $INPUT->str('ext');
+ if (!$ext) {
+ http_status(400);
+ echo 'no extension given';
+ return;
+ }
+
+ /** @var helper_plugin_extension_extension $extension */
+ $extension = plugin_load('helper', 'extension_extension');
+ $extension->setExtension($ext);
+
+ $act = $INPUT->str('act');
+ switch ($act) {
+ case 'enable':
+ case 'disable':
+ $extension->$act(); //enables/disables
+
+ $reverse = ($act == 'disable') ? 'enable' : 'disable';
+
+ $return = array(
+ 'state' => $act.'d', // isn't English wonderful? :-)
+ 'reverse' => $reverse,
+ 'label' => $extension->getLang('btn_'.$reverse)
+ );
+
+ header('Content-Type: application/json');
+ echo json_encode($return);
+ break;
+
+ case 'info':
+ default:
+ /** @var helper_plugin_extension_list $list */
+ $list = plugin_load('helper', 'extension_list');
+ header('Content-Type: text/html; charset=utf-8');
+ echo $list->makeInfo($extension);
+ }
+ }
+}
diff --git a/platform/www/lib/plugins/extension/admin.php b/platform/www/lib/plugins/extension/admin.php
new file mode 100644
index 0000000..7e7eb60
--- /dev/null
+++ b/platform/www/lib/plugins/extension/admin.php
@@ -0,0 +1,185 @@
+<?php
+/**
+ * DokuWiki Plugin extension (Admin Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Hamann <michael@content-space.de>
+ */
+
+/**
+ * Admin part of the extension manager
+ */
+class admin_plugin_extension extends DokuWiki_Admin_Plugin
+{
+ protected $infoFor = null;
+ /** @var helper_plugin_extension_gui */
+ protected $gui;
+
+ /**
+ * Constructor
+ *
+ * loads additional helpers
+ */
+ public function __construct()
+ {
+ $this->gui = plugin_load('helper', 'extension_gui');
+ }
+
+ /**
+ * @return int sort number in admin menu
+ */
+ public function getMenuSort()
+ {
+ return 0;
+ }
+
+ /**
+ * @return bool true if only access for superuser, false is for superusers and moderators
+ */
+ public function forAdminOnly()
+ {
+ return true;
+ }
+
+ /**
+ * Execute the requested action(s) and initialize the plugin repository
+ */
+ public function handle()
+ {
+ global $INPUT;
+ // initialize the remote repository
+ /* @var helper_plugin_extension_repository $repository */
+ $repository = $this->loadHelper('extension_repository');
+
+ if (!$repository->hasAccess(!$INPUT->bool('purge'))) {
+ $url = $this->gui->tabURL('', ['purge' => 1], '&');
+ msg($this->getLang('repo_error').
+ ' [<a href="'.$url.'">'.$this->getLang('repo_retry').'</a>]', -1
+ );
+ }
+
+ if (!in_array('ssl', stream_get_transports())) {
+ msg($this->getLang('nossl'), -1);
+ }
+
+ /* @var helper_plugin_extension_extension $extension */
+ $extension = $this->loadHelper('extension_extension');
+
+ try {
+ if ($INPUT->post->has('fn') && checkSecurityToken()) {
+ $actions = $INPUT->post->arr('fn');
+ foreach ($actions as $action => $extensions) {
+ foreach ($extensions as $extname => $label) {
+ switch ($action) {
+ case 'install':
+ case 'reinstall':
+ case 'update':
+ $extension->setExtension($extname);
+ $installed = $extension->installOrUpdate();
+ foreach ($installed as $ext => $info) {
+ msg(sprintf(
+ $this->getLang('msg_'.$info['type'].'_'.$info['action'].'_success'),
+ $info['base']), 1
+ );
+ }
+ break;
+ case 'uninstall':
+ $extension->setExtension($extname);
+ $status = $extension->uninstall();
+ if ($status) {
+ msg(sprintf(
+ $this->getLang('msg_delete_success'),
+ hsc($extension->getDisplayName())), 1
+ );
+ } else {
+ msg(sprintf(
+ $this->getLang('msg_delete_failed'),
+ hsc($extension->getDisplayName())), -1
+ );
+ }
+ break;
+ case 'enable':
+ $extension->setExtension($extname);
+ $status = $extension->enable();
+ if ($status !== true) {
+ msg($status, -1);
+ } else {
+ msg(sprintf(
+ $this->getLang('msg_enabled'),
+ hsc($extension->getDisplayName())), 1
+ );
+ }
+ break;
+ case 'disable':
+ $extension->setExtension($extname);
+ $status = $extension->disable();
+ if ($status !== true) {
+ msg($status, -1);
+ } else {
+ msg(sprintf(
+ $this->getLang('msg_disabled'),
+ hsc($extension->getDisplayName())), 1
+ );
+ }
+ break;
+ }
+ }
+ }
+ send_redirect($this->gui->tabURL('', [], '&', true));
+ } elseif ($INPUT->post->str('installurl') && checkSecurityToken()) {
+ $installed = $extension->installFromURL(
+ $INPUT->post->str('installurl'),
+ $INPUT->post->bool('overwrite'));
+ foreach ($installed as $ext => $info) {
+ msg(sprintf(
+ $this->getLang('msg_'.$info['type'].'_'.$info['action'].'_success'),
+ $info['base']), 1
+ );
+ }
+ send_redirect($this->gui->tabURL('', [], '&', true));
+ } elseif (isset($_FILES['installfile']) && checkSecurityToken()) {
+ $installed = $extension->installFromUpload('installfile', $INPUT->post->bool('overwrite'));
+ foreach ($installed as $ext => $info) {
+ msg(sprintf(
+ $this->getLang('msg_'.$info['type'].'_'.$info['action'].'_success'),
+ $info['base']), 1
+ );
+ }
+ send_redirect($this->gui->tabURL('', [], '&', true));
+ }
+ } catch (Exception $e) {
+ msg($e->getMessage(), -1);
+ send_redirect($this->gui->tabURL('', [], '&', true));
+ }
+ }
+
+ /**
+ * Render HTML output
+ */
+ public function html()
+ {
+ echo '<h1>'.$this->getLang('menu').'</h1>'.DOKU_LF;
+ echo '<div id="extension__manager">'.DOKU_LF;
+
+ $this->gui->tabNavigation();
+
+ switch ($this->gui->currentTab()) {
+ case 'search':
+ $this->gui->tabSearch();
+ break;
+ case 'templates':
+ $this->gui->tabTemplates();
+ break;
+ case 'install':
+ $this->gui->tabInstall();
+ break;
+ case 'plugins':
+ default:
+ $this->gui->tabPlugins();
+ }
+
+ echo '</div>'.DOKU_LF;
+ }
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/extension/admin.svg b/platform/www/lib/plugins/extension/admin.svg
new file mode 100644
index 0000000..6bd7c0d
--- /dev/null
+++ b/platform/www/lib/plugins/extension/admin.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20.5 11H19V7a2 2 0 0 0-2-2h-4V3.5A2.5 2.5 0 0 0 10.5 1 2.5 2.5 0 0 0 8 3.5V5H4a2 2 0 0 0-2 2v3.8h1.5c1.5 0 2.7 1.2 2.7 2.7 0 1.5-1.2 2.7-2.7 2.7H2V20a2 2 0 0 0 2 2h3.8v-1.5c0-1.5 1.2-2.7 2.7-2.7 1.5 0 2.7 1.2 2.7 2.7V22H17a2 2 0 0 0 2-2v-4h1.5a2.5 2.5 0 0 0 2.5-2.5 2.5 2.5 0 0 0-2.5-2.5z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/plugins/extension/all.less b/platform/www/lib/plugins/extension/all.less
new file mode 100644
index 0000000..3d9688e
--- /dev/null
+++ b/platform/www/lib/plugins/extension/all.less
@@ -0,0 +1,37 @@
+
+@media only screen and (max-width: 600px) {
+
+#extension__list .legend {
+ > div {
+ padding-left: 0;
+ }
+
+ div.screenshot {
+ margin: 0 .5em .5em 0;
+ }
+
+ h2 {
+ width: auto;
+ float: none;
+ }
+
+ div.linkbar {
+ clear: left;
+ }
+}
+
+[dir=rtl] #extension__list .legend {
+ > div {
+ padding-right: 0;
+ }
+
+ div.screenshot {
+ margin: 0 0 .5em .5em;
+ }
+
+ div.linkbar {
+ clear: right;
+ }
+}
+
+} /* /@media */
diff --git a/platform/www/lib/plugins/extension/cli.php b/platform/www/lib/plugins/extension/cli.php
new file mode 100644
index 0000000..a293f87
--- /dev/null
+++ b/platform/www/lib/plugins/extension/cli.php
@@ -0,0 +1,372 @@
+<?php
+
+use splitbrain\phpcli\Colors;
+
+/**
+ * Class cli_plugin_extension
+ *
+ * Command Line component for the extension manager
+ *
+ * @license GPL2
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+class cli_plugin_extension extends DokuWiki_CLI_Plugin
+{
+ /** @inheritdoc */
+ protected function setup(\splitbrain\phpcli\Options $options)
+ {
+ // general setup
+ $options->setHelp(
+ "Manage plugins and templates for this DokuWiki instance\n\n" .
+ "Status codes:\n" .
+ " i - installed\n" .
+ " b - bundled with DokuWiki\n" .
+ " g - installed via git\n" .
+ " d - disabled\n" .
+ " u - update available\n"
+ );
+
+ // search
+ $options->registerCommand('search', 'Search for an extension');
+ $options->registerOption('max', 'Maximum number of results (default 10)', 'm', 'number', 'search');
+ $options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'search');
+ $options->registerArgument('query', 'The keyword(s) to search for', true, 'search');
+
+ // list
+ $options->registerCommand('list', 'List installed extensions');
+ $options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'list');
+ $options->registerOption('filter', 'Filter by this status', 'f', 'status', 'list');
+
+ // upgrade
+ $options->registerCommand('upgrade', 'Update all installed extensions to their latest versions');
+
+ // install
+ $options->registerCommand('install', 'Install or upgrade extensions');
+ $options->registerArgument('extensions...', 'One or more extensions to install', true, 'install');
+
+ // uninstall
+ $options->registerCommand('uninstall', 'Uninstall a new extension');
+ $options->registerArgument('extensions...', 'One or more extensions to install', true, 'uninstall');
+
+ // enable
+ $options->registerCommand('enable', 'Enable installed extensions');
+ $options->registerArgument('extensions...', 'One or more extensions to enable', true, 'enable');
+
+ // disable
+ $options->registerCommand('disable', 'Disable installed extensions');
+ $options->registerArgument('extensions...', 'One or more extensions to disable', true, 'disable');
+
+
+ }
+
+ /** @inheritdoc */
+ protected function main(\splitbrain\phpcli\Options $options)
+ {
+ /** @var helper_plugin_extension_repository $repo */
+ $repo = plugin_load('helper', 'extension_repository');
+ if (!$repo->hasAccess(false)) {
+ $this->warning('Extension Repository API is not accessible, no remote info available!');
+ }
+
+ switch ($options->getCmd()) {
+ case 'list':
+ $ret = $this->cmdList($options->getOpt('verbose'), $options->getOpt('filter', ''));
+ break;
+ case 'search':
+ $ret = $this->cmdSearch(
+ implode(' ', $options->getArgs()),
+ $options->getOpt('verbose'),
+ (int)$options->getOpt('max', 10)
+ );
+ break;
+ case 'install':
+ $ret = $this->cmdInstall($options->getArgs());
+ break;
+ case 'uninstall':
+ $ret = $this->cmdUnInstall($options->getArgs());
+ break;
+ case 'enable':
+ $ret = $this->cmdEnable(true, $options->getArgs());
+ break;
+ case 'disable':
+ $ret = $this->cmdEnable(false, $options->getArgs());
+ break;
+ case 'upgrade':
+ $ret = $this->cmdUpgrade();
+ break;
+ default:
+ echo $options->help();
+ $ret = 0;
+ }
+
+ exit($ret);
+ }
+
+ /**
+ * Upgrade all extensions
+ *
+ * @return int
+ */
+ protected function cmdUpgrade()
+ {
+ /* @var helper_plugin_extension_extension $ext */
+ $ext = $this->loadHelper('extension_extension');
+ $list = $this->getInstalledExtensions();
+
+ $ok = 0;
+ foreach ($list as $extname) {
+ $ext->setExtension($extname);
+ $date = $ext->getInstalledVersion();
+ $avail = $ext->getLastUpdate();
+ if ($avail && $avail > $date) {
+ $ok += $this->cmdInstall([$extname]);
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Enable or disable one or more extensions
+ *
+ * @param bool $set
+ * @param string[] $extensions
+ * @return int
+ */
+ protected function cmdEnable($set, $extensions)
+ {
+ /* @var helper_plugin_extension_extension $ext */
+ $ext = $this->loadHelper('extension_extension');
+
+ $ok = 0;
+ foreach ($extensions as $extname) {
+ $ext->setExtension($extname);
+ if (!$ext->isInstalled()) {
+ $this->error(sprintf('Extension %s is not installed', $ext->getID()));
+ $ok += 1;
+ continue;
+ }
+
+ if ($set) {
+ $status = $ext->enable();
+ $msg = 'msg_enabled';
+ } else {
+ $status = $ext->disable();
+ $msg = 'msg_disabled';
+ }
+
+ if ($status !== true) {
+ $this->error($status);
+ $ok += 1;
+ continue;
+ } else {
+ $this->success(sprintf($this->getLang($msg), $ext->getID()));
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Uninstall one or more extensions
+ *
+ * @param string[] $extensions
+ * @return int
+ */
+ protected function cmdUnInstall($extensions)
+ {
+ /* @var helper_plugin_extension_extension $ext */
+ $ext = $this->loadHelper('extension_extension');
+
+ $ok = 0;
+ foreach ($extensions as $extname) {
+ $ext->setExtension($extname);
+ if (!$ext->isInstalled()) {
+ $this->error(sprintf('Extension %s is not installed', $ext->getID()));
+ $ok += 1;
+ continue;
+ }
+
+ $status = $ext->uninstall();
+ if ($status) {
+ $this->success(sprintf($this->getLang('msg_delete_success'), $ext->getID()));
+ } else {
+ $this->error(sprintf($this->getLang('msg_delete_failed'), hsc($ext->getID())));
+ $ok = 1;
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Install one or more extensions
+ *
+ * @param string[] $extensions
+ * @return int
+ */
+ protected function cmdInstall($extensions)
+ {
+ /* @var helper_plugin_extension_extension $ext */
+ $ext = $this->loadHelper('extension_extension');
+
+ $ok = 0;
+ foreach ($extensions as $extname) {
+ $ext->setExtension($extname);
+
+ if (!$ext->getDownloadURL()) {
+ $ok += 1;
+ $this->error(
+ sprintf('Could not find download for %s', $ext->getID())
+ );
+ continue;
+ }
+
+ try {
+ $installed = $ext->installOrUpdate();
+ foreach ($installed as $name => $info) {
+ $this->success(sprintf(
+ $this->getLang('msg_' . $info['type'] . '_' . $info['action'] . '_success'),
+ $info['base'])
+ );
+ }
+ } catch (Exception $e) {
+ $this->error($e->getMessage());
+ $ok += 1;
+ }
+ }
+ return $ok;
+ }
+
+ /**
+ * Search for an extension
+ *
+ * @param string $query
+ * @param bool $showdetails
+ * @param int $max
+ * @return int
+ * @throws \splitbrain\phpcli\Exception
+ */
+ protected function cmdSearch($query, $showdetails, $max)
+ {
+ /** @var helper_plugin_extension_repository $repository */
+ $repository = $this->loadHelper('extension_repository');
+ $result = $repository->search($query);
+ if ($max) {
+ $result = array_slice($result, 0, $max);
+ }
+
+ $this->listExtensions($result, $showdetails);
+ return 0;
+ }
+
+ /**
+ * @param bool $showdetails
+ * @param string $filter
+ * @return int
+ * @throws \splitbrain\phpcli\Exception
+ */
+ protected function cmdList($showdetails, $filter)
+ {
+ $list = $this->getInstalledExtensions();
+ $this->listExtensions($list, $showdetails, $filter);
+
+ return 0;
+ }
+
+ /**
+ * Get all installed extensions
+ *
+ * @return array
+ */
+ protected function getInstalledExtensions()
+ {
+ /** @var Doku_Plugin_Controller $plugin_controller */
+ global $plugin_controller;
+ $pluginlist = $plugin_controller->getList('', true);
+ $tpllist = glob(DOKU_INC . 'lib/tpl/*', GLOB_ONLYDIR);
+ $tpllist = array_map(function ($path) {
+ return 'template:' . basename($path);
+ }, $tpllist);
+ $list = array_merge($pluginlist, $tpllist);
+ sort($list);
+ return $list;
+ }
+
+ /**
+ * List the given extensions
+ *
+ * @param string[] $list
+ * @param bool $details display details
+ * @param string $filter filter for this status
+ * @throws \splitbrain\phpcli\Exception
+ */
+ protected function listExtensions($list, $details, $filter = '')
+ {
+ /** @var helper_plugin_extension_extension $ext */
+ $ext = $this->loadHelper('extension_extension');
+ $tr = new \splitbrain\phpcli\TableFormatter($this->colors);
+
+
+ foreach ($list as $name) {
+ $ext->setExtension($name);
+
+ $status = '';
+ if ($ext->isInstalled()) {
+ $date = $ext->getInstalledVersion();
+ $avail = $ext->getLastUpdate();
+ $status = 'i';
+ if ($avail && $avail > $date) {
+ $vcolor = Colors::C_RED;
+ $status .= 'u';
+ } else {
+ $vcolor = Colors::C_GREEN;
+ }
+ if ($ext->isGitControlled()) $status = 'g';
+ if ($ext->isBundled()) $status = 'b';
+ if ($ext->isEnabled()) {
+ $ecolor = Colors::C_BROWN;
+ } else {
+ $ecolor = Colors::C_DARKGRAY;
+ $status .= 'd';
+ }
+ } else {
+ $ecolor = null;
+ $date = $ext->getLastUpdate();
+ $vcolor = null;
+ }
+
+ if ($filter && strpos($status, $filter) === false) {
+ continue;
+ }
+
+ echo $tr->format(
+ [20, 3, 12, '*'],
+ [
+ $ext->getID(),
+ $status,
+ $date,
+ strip_tags(sprintf(
+ $this->getLang('extensionby'),
+ $ext->getDisplayName(),
+ $this->colors->wrap($ext->getAuthor(), Colors::C_PURPLE))
+ )
+ ],
+ [
+ $ecolor,
+ Colors::C_YELLOW,
+ $vcolor,
+ null,
+ ]
+ );
+
+ if (!$details) continue;
+
+ echo $tr->format(
+ [5, '*'],
+ ['', $ext->getDescription()],
+ [null, Colors::C_CYAN]
+ );
+ }
+ }
+}
diff --git a/platform/www/lib/plugins/extension/helper/extension.php b/platform/www/lib/plugins/extension/helper/extension.php
new file mode 100644
index 0000000..5ddf332
--- /dev/null
+++ b/platform/www/lib/plugins/extension/helper/extension.php
@@ -0,0 +1,1298 @@
+<?php
+/**
+ * DokuWiki Plugin extension (Helper Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Hamann <michael@content-space.de>
+ */
+
+use dokuwiki\HTTP\DokuHTTPClient;
+use dokuwiki\Extension\PluginController;
+
+/**
+ * Class helper_plugin_extension_extension represents a single extension (plugin or template)
+ */
+class helper_plugin_extension_extension extends DokuWiki_Plugin
+{
+ private $id;
+ private $base;
+ private $is_template = false;
+ private $localInfo;
+ private $remoteInfo;
+ private $managerData;
+ /** @var helper_plugin_extension_repository $repository */
+ private $repository = null;
+
+ /** @var array list of temporary directories */
+ private $temporary = array();
+
+ /** @var string where templates are installed to */
+ private $tpllib = '';
+
+ /**
+ * helper_plugin_extension_extension constructor.
+ */
+ public function __construct()
+ {
+ $this->tpllib = dirname(tpl_incdir()).'/';
+ }
+
+ /**
+ * Destructor
+ *
+ * deletes any dangling temporary directories
+ */
+ public function __destruct()
+ {
+ foreach ($this->temporary as $dir) {
+ io_rmdir($dir, true);
+ }
+ }
+
+ /**
+ * @return bool false, this component is not a singleton
+ */
+ public function isSingleton()
+ {
+ return false;
+ }
+
+ /**
+ * Set the name of the extension this instance shall represents, triggers loading the local and remote data
+ *
+ * @param string $id The id of the extension (prefixed with template: for templates)
+ * @return bool If some (local or remote) data was found
+ */
+ public function setExtension($id)
+ {
+ $id = cleanID($id);
+ $this->id = $id;
+ $this->base = $id;
+
+ if (substr($id, 0, 9) == 'template:') {
+ $this->base = substr($id, 9);
+ $this->is_template = true;
+ } else {
+ $this->is_template = false;
+ }
+
+ $this->localInfo = array();
+ $this->managerData = array();
+ $this->remoteInfo = array();
+
+ if ($this->isInstalled()) {
+ $this->readLocalData();
+ $this->readManagerData();
+ }
+
+ if ($this->repository == null) {
+ $this->repository = $this->loadHelper('extension_repository');
+ }
+
+ $this->remoteInfo = $this->repository->getData($this->getID());
+
+ return ($this->localInfo || $this->remoteInfo);
+ }
+
+ /**
+ * If the extension is installed locally
+ *
+ * @return bool If the extension is installed locally
+ */
+ public function isInstalled()
+ {
+ return is_dir($this->getInstallDir());
+ }
+
+ /**
+ * If the extension is under git control
+ *
+ * @return bool
+ */
+ public function isGitControlled()
+ {
+ if (!$this->isInstalled()) return false;
+ return is_dir($this->getInstallDir().'/.git');
+ }
+
+ /**
+ * If the extension is bundled
+ *
+ * @return bool If the extension is bundled
+ */
+ public function isBundled()
+ {
+ if (!empty($this->remoteInfo['bundled'])) return $this->remoteInfo['bundled'];
+ return in_array(
+ $this->id,
+ array(
+ 'authad', 'authldap', 'authpdo', 'authplain',
+ 'acl', 'config', 'extension', 'info', 'popularity', 'revert',
+ 'safefnrecode', 'styling', 'testing', 'usermanager',
+ 'template:dokuwiki',
+ )
+ );
+ }
+
+ /**
+ * If the extension is protected against any modification (disable/uninstall)
+ *
+ * @return bool if the extension is protected
+ */
+ public function isProtected()
+ {
+ // never allow deinstalling the current auth plugin:
+ global $conf;
+ if ($this->id == $conf['authtype']) return true;
+
+ /** @var PluginController $plugin_controller */
+ global $plugin_controller;
+ $cascade = $plugin_controller->getCascade();
+ return (isset($cascade['protected'][$this->id]) && $cascade['protected'][$this->id]);
+ }
+
+ /**
+ * If the extension is installed in the correct directory
+ *
+ * @return bool If the extension is installed in the correct directory
+ */
+ public function isInWrongFolder()
+ {
+ return $this->base != $this->getBase();
+ }
+
+ /**
+ * If the extension is enabled
+ *
+ * @return bool If the extension is enabled
+ */
+ public function isEnabled()
+ {
+ global $conf;
+ if ($this->isTemplate()) {
+ return ($conf['template'] == $this->getBase());
+ }
+
+ /* @var PluginController $plugin_controller */
+ global $plugin_controller;
+ return $plugin_controller->isEnabled($this->base);
+ }
+
+ /**
+ * If the extension should be updated, i.e. if an updated version is available
+ *
+ * @return bool If an update is available
+ */
+ public function updateAvailable()
+ {
+ if (!$this->isInstalled()) return false;
+ if ($this->isBundled()) return false;
+ $lastupdate = $this->getLastUpdate();
+ if ($lastupdate === false) return false;
+ $installed = $this->getInstalledVersion();
+ if ($installed === false || $installed === $this->getLang('unknownversion')) return true;
+ return $this->getInstalledVersion() < $this->getLastUpdate();
+ }
+
+ /**
+ * If the extension is a template
+ *
+ * @return bool If this extension is a template
+ */
+ public function isTemplate()
+ {
+ return $this->is_template;
+ }
+
+ /**
+ * Get the ID of the extension
+ *
+ * This is the same as getName() for plugins, for templates it's getName() prefixed with 'template:'
+ *
+ * @return string
+ */
+ public function getID()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Get the name of the installation directory
+ *
+ * @return string The name of the installation directory
+ */
+ public function getInstallName()
+ {
+ return $this->base;
+ }
+
+ // Data from plugin.info.txt/template.info.txt or the repo when not available locally
+ /**
+ * Get the basename of the extension
+ *
+ * @return string The basename
+ */
+ public function getBase()
+ {
+ if (!empty($this->localInfo['base'])) return $this->localInfo['base'];
+ return $this->base;
+ }
+
+ /**
+ * Get the display name of the extension
+ *
+ * @return string The display name
+ */
+ public function getDisplayName()
+ {
+ if (!empty($this->localInfo['name'])) return $this->localInfo['name'];
+ if (!empty($this->remoteInfo['name'])) return $this->remoteInfo['name'];
+ return $this->base;
+ }
+
+ /**
+ * Get the author name of the extension
+ *
+ * @return string|bool The name of the author or false if there is none
+ */
+ public function getAuthor()
+ {
+ if (!empty($this->localInfo['author'])) return $this->localInfo['author'];
+ if (!empty($this->remoteInfo['author'])) return $this->remoteInfo['author'];
+ return false;
+ }
+
+ /**
+ * Get the email of the author of the extension if there is any
+ *
+ * @return string|bool The email address or false if there is none
+ */
+ public function getEmail()
+ {
+ // email is only in the local data
+ if (!empty($this->localInfo['email'])) return $this->localInfo['email'];
+ return false;
+ }
+
+ /**
+ * Get the email id, i.e. the md5sum of the email
+ *
+ * @return string|bool The md5sum of the email if there is any, false otherwise
+ */
+ public function getEmailID()
+ {
+ if (!empty($this->remoteInfo['emailid'])) return $this->remoteInfo['emailid'];
+ if (!empty($this->localInfo['email'])) return md5($this->localInfo['email']);
+ return false;
+ }
+
+ /**
+ * Get the description of the extension
+ *
+ * @return string The description
+ */
+ public function getDescription()
+ {
+ if (!empty($this->localInfo['desc'])) return $this->localInfo['desc'];
+ if (!empty($this->remoteInfo['description'])) return $this->remoteInfo['description'];
+ return '';
+ }
+
+ /**
+ * Get the URL of the extension, usually a page on dokuwiki.org
+ *
+ * @return string The URL
+ */
+ public function getURL()
+ {
+ if (!empty($this->localInfo['url'])) return $this->localInfo['url'];
+ return 'https://www.dokuwiki.org/'.
+ ($this->isTemplate() ? 'template' : 'plugin').':'.$this->getBase();
+ }
+
+ /**
+ * Get the installed version of the extension
+ *
+ * @return string|bool The version, usually in the form yyyy-mm-dd if there is any
+ */
+ public function getInstalledVersion()
+ {
+ if (!empty($this->localInfo['date'])) return $this->localInfo['date'];
+ if ($this->isInstalled()) return $this->getLang('unknownversion');
+ return false;
+ }
+
+ /**
+ * Get the install date of the current version
+ *
+ * @return string|bool The date of the last update or false if not available
+ */
+ public function getUpdateDate()
+ {
+ if (!empty($this->managerData['updated'])) return $this->managerData['updated'];
+ return $this->getInstallDate();
+ }
+
+ /**
+ * Get the date of the installation of the plugin
+ *
+ * @return string|bool The date of the installation or false if not available
+ */
+ public function getInstallDate()
+ {
+ if (!empty($this->managerData['installed'])) return $this->managerData['installed'];
+ return false;
+ }
+
+ /**
+ * Get the names of the dependencies of this extension
+ *
+ * @return array The base names of the dependencies
+ */
+ public function getDependencies()
+ {
+ if (!empty($this->remoteInfo['dependencies'])) return $this->remoteInfo['dependencies'];
+ return array();
+ }
+
+ /**
+ * Get the names of the missing dependencies
+ *
+ * @return array The base names of the missing dependencies
+ */
+ public function getMissingDependencies()
+ {
+ /* @var PluginController $plugin_controller */
+ global $plugin_controller;
+ $dependencies = $this->getDependencies();
+ $missing_dependencies = array();
+ foreach ($dependencies as $dependency) {
+ if (!$plugin_controller->isEnabled($dependency)) {
+ $missing_dependencies[] = $dependency;
+ }
+ }
+ return $missing_dependencies;
+ }
+
+ /**
+ * Get the names of all conflicting extensions
+ *
+ * @return array The names of the conflicting extensions
+ */
+ public function getConflicts()
+ {
+ if (!empty($this->remoteInfo['conflicts'])) return $this->remoteInfo['conflicts'];
+ return array();
+ }
+
+ /**
+ * Get the names of similar extensions
+ *
+ * @return array The names of similar extensions
+ */
+ public function getSimilarExtensions()
+ {
+ if (!empty($this->remoteInfo['similar'])) return $this->remoteInfo['similar'];
+ return array();
+ }
+
+ /**
+ * Get the names of the tags of the extension
+ *
+ * @return array The names of the tags of the extension
+ */
+ public function getTags()
+ {
+ if (!empty($this->remoteInfo['tags'])) return $this->remoteInfo['tags'];
+ return array();
+ }
+
+ /**
+ * Get the popularity information as floating point number [0,1]
+ *
+ * @return float|bool The popularity information or false if it isn't available
+ */
+ public function getPopularity()
+ {
+ if (!empty($this->remoteInfo['popularity'])) return $this->remoteInfo['popularity'];
+ return false;
+ }
+
+
+ /**
+ * Get the text of the security warning if there is any
+ *
+ * @return string|bool The security warning if there is any, false otherwise
+ */
+ public function getSecurityWarning()
+ {
+ if (!empty($this->remoteInfo['securitywarning'])) return $this->remoteInfo['securitywarning'];
+ return false;
+ }
+
+ /**
+ * Get the text of the security issue if there is any
+ *
+ * @return string|bool The security issue if there is any, false otherwise
+ */
+ public function getSecurityIssue()
+ {
+ if (!empty($this->remoteInfo['securityissue'])) return $this->remoteInfo['securityissue'];
+ return false;
+ }
+
+ /**
+ * Get the URL of the screenshot of the extension if there is any
+ *
+ * @return string|bool The screenshot URL if there is any, false otherwise
+ */
+ public function getScreenshotURL()
+ {
+ if (!empty($this->remoteInfo['screenshoturl'])) return $this->remoteInfo['screenshoturl'];
+ return false;
+ }
+
+ /**
+ * Get the URL of the thumbnail of the extension if there is any
+ *
+ * @return string|bool The thumbnail URL if there is any, false otherwise
+ */
+ public function getThumbnailURL()
+ {
+ if (!empty($this->remoteInfo['thumbnailurl'])) return $this->remoteInfo['thumbnailurl'];
+ return false;
+ }
+ /**
+ * Get the last used download URL of the extension if there is any
+ *
+ * @return string|bool The previously used download URL, false if the extension has been installed manually
+ */
+ public function getLastDownloadURL()
+ {
+ if (!empty($this->managerData['downloadurl'])) return $this->managerData['downloadurl'];
+ return false;
+ }
+
+ /**
+ * Get the download URL of the extension if there is any
+ *
+ * @return string|bool The download URL if there is any, false otherwise
+ */
+ public function getDownloadURL()
+ {
+ if (!empty($this->remoteInfo['downloadurl'])) return $this->remoteInfo['downloadurl'];
+ return false;
+ }
+
+ /**
+ * If the download URL has changed since the last download
+ *
+ * @return bool If the download URL has changed
+ */
+ public function hasDownloadURLChanged()
+ {
+ $lasturl = $this->getLastDownloadURL();
+ $currenturl = $this->getDownloadURL();
+ return ($lasturl && $currenturl && $lasturl != $currenturl);
+ }
+
+ /**
+ * Get the bug tracker URL of the extension if there is any
+ *
+ * @return string|bool The bug tracker URL if there is any, false otherwise
+ */
+ public function getBugtrackerURL()
+ {
+ if (!empty($this->remoteInfo['bugtracker'])) return $this->remoteInfo['bugtracker'];
+ return false;
+ }
+
+ /**
+ * Get the URL of the source repository if there is any
+ *
+ * @return string|bool The URL of the source repository if there is any, false otherwise
+ */
+ public function getSourcerepoURL()
+ {
+ if (!empty($this->remoteInfo['sourcerepo'])) return $this->remoteInfo['sourcerepo'];
+ return false;
+ }
+
+ /**
+ * Get the donation URL of the extension if there is any
+ *
+ * @return string|bool The donation URL if there is any, false otherwise
+ */
+ public function getDonationURL()
+ {
+ if (!empty($this->remoteInfo['donationurl'])) return $this->remoteInfo['donationurl'];
+ return false;
+ }
+
+ /**
+ * Get the extension type(s)
+ *
+ * @return array The type(s) as array of strings
+ */
+ public function getTypes()
+ {
+ if (!empty($this->remoteInfo['types'])) return $this->remoteInfo['types'];
+ if ($this->isTemplate()) return array(32 => 'template');
+ return array();
+ }
+
+ /**
+ * Get a list of all DokuWiki versions this extension is compatible with
+ *
+ * @return array The versions in the form yyyy-mm-dd => ('label' => label, 'implicit' => implicit)
+ */
+ public function getCompatibleVersions()
+ {
+ if (!empty($this->remoteInfo['compatible'])) return $this->remoteInfo['compatible'];
+ return array();
+ }
+
+ /**
+ * Get the date of the last available update
+ *
+ * @return string|bool The last available update in the form yyyy-mm-dd if there is any, false otherwise
+ */
+ public function getLastUpdate()
+ {
+ if (!empty($this->remoteInfo['lastupdate'])) return $this->remoteInfo['lastupdate'];
+ return false;
+ }
+
+ /**
+ * Get the base path of the extension
+ *
+ * @return string The base path of the extension
+ */
+ public function getInstallDir()
+ {
+ if ($this->isTemplate()) {
+ return $this->tpllib.$this->base;
+ } else {
+ return DOKU_PLUGIN.$this->base;
+ }
+ }
+
+ /**
+ * The type of extension installation
+ *
+ * @return string One of "none", "manual", "git" or "automatic"
+ */
+ public function getInstallType()
+ {
+ if (!$this->isInstalled()) return 'none';
+ if (!empty($this->managerData)) return 'automatic';
+ if (is_dir($this->getInstallDir().'/.git')) return 'git';
+ return 'manual';
+ }
+
+ /**
+ * If the extension can probably be installed/updated or uninstalled
+ *
+ * @return bool|string True or error string
+ */
+ public function canModify()
+ {
+ if ($this->isInstalled()) {
+ if (!is_writable($this->getInstallDir())) {
+ return 'noperms';
+ }
+ }
+
+ if ($this->isTemplate() && !is_writable($this->tpllib)) {
+ return 'notplperms';
+ } elseif (!is_writable(DOKU_PLUGIN)) {
+ return 'nopluginperms';
+ }
+ return true;
+ }
+
+ /**
+ * Install an extension from a user upload
+ *
+ * @param string $field name of the upload file
+ * @param boolean $overwrite overwrite folder if the extension name is the same
+ * @throws Exception when something goes wrong
+ * @return array The list of installed extensions
+ */
+ public function installFromUpload($field, $overwrite = true)
+ {
+ if ($_FILES[$field]['error']) {
+ throw new Exception($this->getLang('msg_upload_failed').' ('.$_FILES[$field]['error'].')');
+ }
+
+ $tmp = $this->mkTmpDir();
+ if (!$tmp) throw new Exception($this->getLang('error_dircreate'));
+
+ // filename may contain the plugin name for old style plugins...
+ $basename = basename($_FILES[$field]['name']);
+ $basename = preg_replace('/\.(tar\.gz|tar\.bz|tar\.bz2|tar|tgz|tbz|zip)$/', '', $basename);
+ $basename = preg_replace('/[\W]+/', '', $basename);
+
+ if (!move_uploaded_file($_FILES[$field]['tmp_name'], "$tmp/upload.archive")) {
+ throw new Exception($this->getLang('msg_upload_failed'));
+ }
+
+ try {
+ $installed = $this->installArchive("$tmp/upload.archive", $overwrite, $basename);
+ $this->updateManagerData('', $installed);
+ $this->removeDeletedfiles($installed);
+ // purge cache
+ $this->purgeCache();
+ } catch (Exception $e) {
+ throw $e;
+ }
+ return $installed;
+ }
+
+ /**
+ * Install an extension from a remote URL
+ *
+ * @param string $url
+ * @param boolean $overwrite overwrite folder if the extension name is the same
+ * @throws Exception when something goes wrong
+ * @return array The list of installed extensions
+ */
+ public function installFromURL($url, $overwrite = true)
+ {
+ try {
+ $path = $this->download($url);
+ $installed = $this->installArchive($path, $overwrite);
+ $this->updateManagerData($url, $installed);
+ $this->removeDeletedfiles($installed);
+
+ // purge cache
+ $this->purgeCache();
+ } catch (Exception $e) {
+ throw $e;
+ }
+ return $installed;
+ }
+
+ /**
+ * Install or update the extension
+ *
+ * @throws \Exception when something goes wrong
+ * @return array The list of installed extensions
+ */
+ public function installOrUpdate()
+ {
+ $url = $this->getDownloadURL();
+ $path = $this->download($url);
+ $installed = $this->installArchive($path, $this->isInstalled(), $this->getBase());
+ $this->updateManagerData($url, $installed);
+
+ // refresh extension information
+ if (!isset($installed[$this->getID()])) {
+ throw new Exception('Error, the requested extension hasn\'t been installed or updated');
+ }
+ $this->removeDeletedfiles($installed);
+ $this->setExtension($this->getID());
+ $this->purgeCache();
+ return $installed;
+ }
+
+ /**
+ * Uninstall the extension
+ *
+ * @return bool If the plugin was sucessfully uninstalled
+ */
+ public function uninstall()
+ {
+ $this->purgeCache();
+ return io_rmdir($this->getInstallDir(), true);
+ }
+
+ /**
+ * Enable the extension
+ *
+ * @return bool|string True or an error message
+ */
+ public function enable()
+ {
+ if ($this->isTemplate()) return $this->getLang('notimplemented');
+ if (!$this->isInstalled()) return $this->getLang('notinstalled');
+ if ($this->isEnabled()) return $this->getLang('alreadyenabled');
+
+ /* @var PluginController $plugin_controller */
+ global $plugin_controller;
+ if ($plugin_controller->enable($this->base)) {
+ $this->purgeCache();
+ return true;
+ } else {
+ return $this->getLang('pluginlistsaveerror');
+ }
+ }
+
+ /**
+ * Disable the extension
+ *
+ * @return bool|string True or an error message
+ */
+ public function disable()
+ {
+ if ($this->isTemplate()) return $this->getLang('notimplemented');
+
+ /* @var PluginController $plugin_controller */
+ global $plugin_controller;
+ if (!$this->isInstalled()) return $this->getLang('notinstalled');
+ if (!$this->isEnabled()) return $this->getLang('alreadydisabled');
+ if ($plugin_controller->disable($this->base)) {
+ $this->purgeCache();
+ return true;
+ } else {
+ return $this->getLang('pluginlistsaveerror');
+ }
+ }
+
+ /**
+ * Purge the cache by touching the main configuration file
+ */
+ protected function purgeCache()
+ {
+ global $config_cascade;
+
+ // expire dokuwiki caches
+ // touching local.php expires wiki page, JS and CSS caches
+ @touch(reset($config_cascade['main']['local']));
+ }
+
+ /**
+ * Read local extension data either from info.txt or getInfo()
+ */
+ protected function readLocalData()
+ {
+ if ($this->isTemplate()) {
+ $infopath = $this->getInstallDir().'/template.info.txt';
+ } else {
+ $infopath = $this->getInstallDir().'/plugin.info.txt';
+ }
+
+ if (is_readable($infopath)) {
+ $this->localInfo = confToHash($infopath);
+ } elseif (!$this->isTemplate() && $this->isEnabled()) {
+ $path = $this->getInstallDir().'/';
+ $plugin = null;
+
+ foreach (PluginController::PLUGIN_TYPES as $type) {
+ if (file_exists($path.$type.'.php')) {
+ $plugin = plugin_load($type, $this->base);
+ if ($plugin) break;
+ }
+
+ if ($dh = @opendir($path.$type.'/')) {
+ while (false !== ($cp = readdir($dh))) {
+ if ($cp == '.' || $cp == '..' || strtolower(substr($cp, -4)) != '.php') continue;
+
+ $plugin = plugin_load($type, $this->base.'_'.substr($cp, 0, -4));
+ if ($plugin) break;
+ }
+ if ($plugin) break;
+ closedir($dh);
+ }
+ }
+
+ if ($plugin) {
+ /* @var DokuWiki_Plugin $plugin */
+ $this->localInfo = $plugin->getInfo();
+ }
+ }
+ }
+
+ /**
+ * Save the given URL and current datetime in the manager.dat file of all installed extensions
+ *
+ * @param string $url Where the extension was downloaded from. (empty for manual installs via upload)
+ * @param array $installed Optional list of installed plugins
+ */
+ protected function updateManagerData($url = '', $installed = null)
+ {
+ $origID = $this->getID();
+
+ if (is_null($installed)) {
+ $installed = array($origID);
+ }
+
+ foreach ($installed as $ext => $info) {
+ if ($this->getID() != $ext) $this->setExtension($ext);
+ if ($url) {
+ $this->managerData['downloadurl'] = $url;
+ } elseif (isset($this->managerData['downloadurl'])) {
+ unset($this->managerData['downloadurl']);
+ }
+ if (isset($this->managerData['installed'])) {
+ $this->managerData['updated'] = date('r');
+ } else {
+ $this->managerData['installed'] = date('r');
+ }
+ $this->writeManagerData();
+ }
+
+ if ($this->getID() != $origID) $this->setExtension($origID);
+ }
+
+ /**
+ * Read the manager.dat file
+ */
+ protected function readManagerData()
+ {
+ $managerpath = $this->getInstallDir().'/manager.dat';
+ if (is_readable($managerpath)) {
+ $file = @file($managerpath);
+ if (!empty($file)) {
+ foreach ($file as $line) {
+ list($key, $value) = explode('=', trim($line, DOKU_LF), 2);
+ $key = trim($key);
+ $value = trim($value);
+ // backwards compatible with old plugin manager
+ if ($key == 'url') $key = 'downloadurl';
+ $this->managerData[$key] = $value;
+ }
+ }
+ }
+ }
+
+ /**
+ * Write the manager.data file
+ */
+ protected function writeManagerData()
+ {
+ $managerpath = $this->getInstallDir().'/manager.dat';
+ $data = '';
+ foreach ($this->managerData as $k => $v) {
+ $data .= $k.'='.$v.DOKU_LF;
+ }
+ io_saveFile($managerpath, $data);
+ }
+
+ /**
+ * Returns a temporary directory
+ *
+ * The directory is registered for cleanup when the class is destroyed
+ *
+ * @return false|string
+ */
+ protected function mkTmpDir()
+ {
+ $dir = io_mktmpdir();
+ if (!$dir) return false;
+ $this->temporary[] = $dir;
+ return $dir;
+ }
+
+ /**
+ * downloads a file from the net and saves it
+ *
+ * - $file is the directory where the file should be saved
+ * - if successful will return the name used for the saved file, false otherwise
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ *
+ * @param string $url url to download
+ * @param string $file path to file or directory where to save
+ * @param string $defaultName fallback for name of download
+ * @return bool|string if failed false, otherwise true or the name of the file in the given dir
+ */
+ protected function downloadToFile($url, $file, $defaultName = '')
+ {
+ global $conf;
+ $http = new DokuHTTPClient();
+ $http->max_bodysize = 0;
+ $http->timeout = 25; //max. 25 sec
+ $http->keep_alive = false; // we do single ops here, no need for keep-alive
+ $http->agent = 'DokuWiki HTTP Client (Extension Manager)';
+
+ $data = $http->get($url);
+ if ($data === false) return false;
+
+ $name = '';
+ if (isset($http->resp_headers['content-disposition'])) {
+ $content_disposition = $http->resp_headers['content-disposition'];
+ $match = array();
+ if (is_string($content_disposition) &&
+ preg_match('/attachment;\s*filename\s*=\s*"([^"]*)"/i', $content_disposition, $match)
+ ) {
+ $name = \dokuwiki\Utf8\PhpString::basename($match[1]);
+ }
+
+ }
+
+ if (!$name) {
+ if (!$defaultName) return false;
+ $name = $defaultName;
+ }
+
+ $file = $file.$name;
+
+ $fileexists = file_exists($file);
+ $fp = @fopen($file,"w");
+ if (!$fp) return false;
+ fwrite($fp, $data);
+ fclose($fp);
+ if (!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']);
+ return $name;
+ }
+
+ /**
+ * Download an archive to a protected path
+ *
+ * @param string $url The url to get the archive from
+ * @throws Exception when something goes wrong
+ * @return string The path where the archive was saved
+ */
+ public function download($url)
+ {
+ // check the url
+ if (!preg_match('/https?:\/\//i', $url)) {
+ throw new Exception($this->getLang('error_badurl'));
+ }
+
+ // try to get the file from the path (used as plugin name fallback)
+ $file = parse_url($url, PHP_URL_PATH);
+ if (is_null($file)) {
+ $file = md5($url);
+ } else {
+ $file = \dokuwiki\Utf8\PhpString::basename($file);
+ }
+
+ // create tmp directory for download
+ if (!($tmp = $this->mkTmpDir())) {
+ throw new Exception($this->getLang('error_dircreate'));
+ }
+
+ // download
+ if (!$file = $this->downloadToFile($url, $tmp.'/', $file)) {
+ io_rmdir($tmp, true);
+ throw new Exception(sprintf($this->getLang('error_download'),
+ '<bdi>'.hsc($url).'</bdi>')
+ );
+ }
+
+ return $tmp.'/'.$file;
+ }
+
+ /**
+ * @param string $file The path to the archive that shall be installed
+ * @param bool $overwrite If an already installed plugin should be overwritten
+ * @param string $base The basename of the plugin if it's known
+ * @throws Exception when something went wrong
+ * @return array list of installed extensions
+ */
+ public function installArchive($file, $overwrite = false, $base = '')
+ {
+ $installed_extensions = array();
+
+ // create tmp directory for decompression
+ if (!($tmp = $this->mkTmpDir())) {
+ throw new Exception($this->getLang('error_dircreate'));
+ }
+
+ // add default base folder if specified to handle case where zip doesn't contain this
+ if ($base && !@mkdir($tmp.'/'.$base)) {
+ throw new Exception($this->getLang('error_dircreate'));
+ }
+
+ // decompress
+ $this->decompress($file, "$tmp/".$base);
+
+ // search $tmp/$base for the folder(s) that has been created
+ // move the folder(s) to lib/..
+ $result = array('old'=>array(), 'new'=>array());
+ $default = ($this->isTemplate() ? 'template' : 'plugin');
+ if (!$this->findFolders($result, $tmp.'/'.$base, $default)) {
+ throw new Exception($this->getLang('error_findfolder'));
+ }
+
+ // choose correct result array
+ if (count($result['new'])) {
+ $install = $result['new'];
+ } else {
+ $install = $result['old'];
+ }
+
+ if (!count($install)) {
+ throw new Exception($this->getLang('error_findfolder'));
+ }
+
+ // now install all found items
+ foreach ($install as $item) {
+ // where to install?
+ if ($item['type'] == 'template') {
+ $target_base_dir = $this->tpllib;
+ } else {
+ $target_base_dir = DOKU_PLUGIN;
+ }
+
+ if (!empty($item['base'])) {
+ // use base set in info.txt
+ } elseif ($base && count($install) == 1) {
+ $item['base'] = $base;
+ } else {
+ // default - use directory as found in zip
+ // plugins from github/master without *.info.txt will install in wrong folder
+ // but using $info->id will make 'code3' fail (which should install in lib/code/..)
+ $item['base'] = basename($item['tmp']);
+ }
+
+ // check to make sure we aren't overwriting anything
+ $target = $target_base_dir.$item['base'];
+ if (!$overwrite && file_exists($target)) {
+ // this info message is not being exposed via exception,
+ // so that it's not interrupting the installation
+ msg(sprintf($this->getLang('msg_nooverwrite'), $item['base']));
+ continue;
+ }
+
+ $action = file_exists($target) ? 'update' : 'install';
+
+ // copy action
+ if ($this->dircopy($item['tmp'], $target)) {
+ // return info
+ $id = $item['base'];
+ if ($item['type'] == 'template') {
+ $id = 'template:'.$id;
+ }
+ $installed_extensions[$id] = array(
+ 'base' => $item['base'],
+ 'type' => $item['type'],
+ 'action' => $action
+ );
+ } else {
+ throw new Exception(sprintf($this->getLang('error_copy').DOKU_LF,
+ '<bdi>'.$item['base'].'</bdi>')
+ );
+ }
+ }
+
+ // cleanup
+ if ($tmp) io_rmdir($tmp, true);
+
+ return $installed_extensions;
+ }
+
+ /**
+ * Find out what was in the extracted directory
+ *
+ * Correct folders are searched recursively using the "*.info.txt" configs
+ * as indicator for a root folder. When such a file is found, it's base
+ * setting is used (when set). All folders found by this method are stored
+ * in the 'new' key of the $result array.
+ *
+ * For backwards compatibility all found top level folders are stored as
+ * in the 'old' key of the $result array.
+ *
+ * When no items are found in 'new' the copy mechanism should fall back
+ * the 'old' list.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param array $result - results are stored here
+ * @param string $directory - the temp directory where the package was unpacked to
+ * @param string $default_type - type used if no info.txt available
+ * @param string $subdir - a subdirectory. do not set. used by recursion
+ * @return bool - false on error
+ */
+ protected function findFolders(&$result, $directory, $default_type = 'plugin', $subdir = '')
+ {
+ $this_dir = "$directory$subdir";
+ $dh = @opendir($this_dir);
+ if (!$dh) return false;
+
+ $found_dirs = array();
+ $found_files = 0;
+ $found_template_parts = 0;
+ while (false !== ($f = readdir($dh))) {
+ if ($f == '.' || $f == '..') continue;
+
+ if (is_dir("$this_dir/$f")) {
+ $found_dirs[] = "$subdir/$f";
+ } else {
+ // it's a file -> check for config
+ $found_files++;
+ switch ($f) {
+ case 'plugin.info.txt':
+ case 'template.info.txt':
+ // we have found a clear marker, save and return
+ $info = array();
+ $type = explode('.', $f, 2);
+ $info['type'] = $type[0];
+ $info['tmp'] = $this_dir;
+ $conf = confToHash("$this_dir/$f");
+ $info['base'] = basename($conf['base']);
+ $result['new'][] = $info;
+ return true;
+
+ case 'main.php':
+ case 'details.php':
+ case 'mediamanager.php':
+ case 'style.ini':
+ $found_template_parts++;
+ break;
+ }
+ }
+ }
+ closedir($dh);
+
+ // files where found but no info.txt - use old method
+ if ($found_files) {
+ $info = array();
+ $info['tmp'] = $this_dir;
+ // does this look like a template or should we use the default type?
+ if ($found_template_parts >= 2) {
+ $info['type'] = 'template';
+ } else {
+ $info['type'] = $default_type;
+ }
+
+ $result['old'][] = $info;
+ return true;
+ }
+
+ // we have no files yet -> recurse
+ foreach ($found_dirs as $found_dir) {
+ $this->findFolders($result, $directory, $default_type, "$found_dir");
+ }
+ return true;
+ }
+
+ /**
+ * Decompress a given file to the given target directory
+ *
+ * Determines the compression type from the file extension
+ *
+ * @param string $file archive to extract
+ * @param string $target directory to extract to
+ * @throws Exception
+ * @return bool
+ */
+ private function decompress($file, $target)
+ {
+ // decompression library doesn't like target folders ending in "/"
+ if (substr($target, -1) == "/") $target = substr($target, 0, -1);
+
+ $ext = $this->guessArchiveType($file);
+ if (in_array($ext, array('tar', 'bz', 'gz'))) {
+ try {
+ $tar = new \splitbrain\PHPArchive\Tar();
+ $tar->open($file);
+ $tar->extract($target);
+ } catch (\splitbrain\PHPArchive\ArchiveIOException $e) {
+ throw new Exception($this->getLang('error_decompress').' '.$e->getMessage());
+ }
+
+ return true;
+ } elseif ($ext == 'zip') {
+ try {
+ $zip = new \splitbrain\PHPArchive\Zip();
+ $zip->open($file);
+ $zip->extract($target);
+ } catch (\splitbrain\PHPArchive\ArchiveIOException $e) {
+ throw new Exception($this->getLang('error_decompress').' '.$e->getMessage());
+ }
+
+ return true;
+ }
+
+ // the only case when we don't get one of the recognized archive types is
+ // when the archive file can't be read
+ throw new Exception($this->getLang('error_decompress').' Couldn\'t read archive file');
+ }
+
+ /**
+ * Determine the archive type of the given file
+ *
+ * Reads the first magic bytes of the given file for content type guessing,
+ * if neither bz, gz or zip are recognized, tar is assumed.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $file The file to analyze
+ * @return string|false false if the file can't be read, otherwise an "extension"
+ */
+ private function guessArchiveType($file)
+ {
+ $fh = fopen($file, 'rb');
+ if (!$fh) return false;
+ $magic = fread($fh, 5);
+ fclose($fh);
+
+ if (strpos($magic, "\x42\x5a") === 0) return 'bz';
+ if (strpos($magic, "\x1f\x8b") === 0) return 'gz';
+ if (strpos($magic, "\x50\x4b\x03\x04") === 0) return 'zip';
+ return 'tar';
+ }
+
+ /**
+ * Copy with recursive sub-directory support
+ *
+ * @param string $src filename path to file
+ * @param string $dst filename path to file
+ * @return bool|int|string
+ */
+ private function dircopy($src, $dst)
+ {
+ global $conf;
+
+ if (is_dir($src)) {
+ if (!$dh = @opendir($src)) return false;
+
+ if ($ok = io_mkdir_p($dst)) {
+ while ($ok && (false !== ($f = readdir($dh)))) {
+ if ($f == '..' || $f == '.') continue;
+ $ok = $this->dircopy("$src/$f", "$dst/$f");
+ }
+ }
+
+ closedir($dh);
+ return $ok;
+ } else {
+ $existed = file_exists($dst);
+
+ if (!@copy($src, $dst)) return false;
+ if (!$existed && $conf['fperm']) chmod($dst, $conf['fperm']);
+ @touch($dst, filemtime($src));
+ }
+
+ return true;
+ }
+
+ /**
+ * Delete outdated files from updated plugins
+ *
+ * @param array $installed
+ */
+ private function removeDeletedfiles($installed)
+ {
+ foreach ($installed as $id => $extension) {
+ // only on update
+ if ($extension['action'] == 'install') continue;
+
+ // get definition file
+ if ($extension['type'] == 'template') {
+ $extensiondir = $this->tpllib;
+ } else {
+ $extensiondir = DOKU_PLUGIN;
+ }
+ $extensiondir = $extensiondir . $extension['base'] .'/';
+ $definitionfile = $extensiondir . 'deleted.files';
+ if (!file_exists($definitionfile)) continue;
+
+ // delete the old files
+ $list = file($definitionfile);
+
+ foreach ($list as $line) {
+ $line = trim(preg_replace('/#.*$/', '', $line));
+ if (!$line) continue;
+ $file = $extensiondir . $line;
+ if (!file_exists($file)) continue;
+
+ io_rmdir($file, true);
+ }
+ }
+ }
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/extension/helper/gui.php b/platform/www/lib/plugins/extension/helper/gui.php
new file mode 100644
index 0000000..919eb2c
--- /dev/null
+++ b/platform/www/lib/plugins/extension/helper/gui.php
@@ -0,0 +1,237 @@
+<?php
+/**
+ * DokuWiki Plugin extension (Helper Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+use dokuwiki\Form\Form;
+
+/**
+ * Class helper_plugin_extension_list takes care of the overall GUI
+ */
+class helper_plugin_extension_gui extends DokuWiki_Plugin
+{
+ protected $tabs = array('plugins', 'templates', 'search', 'install');
+
+ /** @var string the extension that should have an open info window FIXME currently broken */
+ protected $infoFor = '';
+
+ /**
+ * Constructor
+ *
+ * initializes requested info window
+ */
+ public function __construct()
+ {
+ global $INPUT;
+ $this->infoFor = $INPUT->str('info');
+ }
+
+ /**
+ * display the plugin tab
+ */
+ public function tabPlugins()
+ {
+ echo '<div class="panelHeader">';
+ echo $this->locale_xhtml('intro_plugins');
+ echo '</div>';
+
+ $pluginlist = plugin_list('', true);
+ /* @var helper_plugin_extension_extension $extension */
+ $extension = $this->loadHelper('extension_extension');
+ /* @var helper_plugin_extension_list $list */
+ $list = $this->loadHelper('extension_list');
+
+ $form = new Form([
+ 'action' => $this->tabURL('', [], '&'),
+ 'id' => 'extension__list',
+ ]);
+ $list->startForm();
+ foreach ($pluginlist as $name) {
+ $extension->setExtension($name);
+ $list->addRow($extension, $extension->getID() == $this->infoFor);
+ }
+ $list->endForm();
+ $form->addHTML($list->render(true));
+ echo $form->toHTML();
+ }
+
+ /**
+ * Display the template tab
+ */
+ public function tabTemplates()
+ {
+ echo '<div class="panelHeader">';
+ echo $this->locale_xhtml('intro_templates');
+ echo '</div>';
+
+ // FIXME do we have a real way?
+ $tpllist = glob(DOKU_INC.'lib/tpl/*', GLOB_ONLYDIR);
+ $tpllist = array_map('basename', $tpllist);
+ sort($tpllist);
+
+ /* @var helper_plugin_extension_extension $extension */
+ $extension = $this->loadHelper('extension_extension');
+ /* @var helper_plugin_extension_list $list */
+ $list = $this->loadHelper('extension_list');
+
+ $form = new Form([
+ 'action' => $this->tabURL('', [], '&'),
+ 'id' => 'extension__list',
+ ]);
+ $list->startForm();
+ foreach ($tpllist as $name) {
+ $extension->setExtension("template:$name");
+ $list->addRow($extension, $extension->getID() == $this->infoFor);
+ }
+ $list->endForm();
+ $form->addHTML($list->render(true));
+ echo $form->toHTML();
+ }
+
+ /**
+ * Display the search tab
+ */
+ public function tabSearch()
+ {
+ global $INPUT;
+ echo '<div class="panelHeader">';
+ echo $this->locale_xhtml('intro_search');
+ echo '</div>';
+
+ $form = new Form([
+ 'action' => $this->tabURL('', [], '&'),
+ 'class' => 'search',
+ ]);
+ $form->addTagOpen('div')->addClass('no');
+ $form->addTextInput('q', $this->getLang('search_for'))
+ ->addClass('edit')
+ ->val($INPUT->str('q'));
+ $form->addButton('submit', $this->getLang('search'))
+ ->attrs(['type' => 'submit', 'title' => $this->getLang('search')]);
+ $form->addTagClose('div');
+ echo $form->toHTML();
+
+ if (!$INPUT->bool('q')) return;
+
+ /* @var helper_plugin_extension_repository $repository FIXME should we use some gloabl instance? */
+ $repository = $this->loadHelper('extension_repository');
+ $result = $repository->search($INPUT->str('q'));
+
+ /* @var helper_plugin_extension_extension $extension */
+ $extension = $this->loadHelper('extension_extension');
+ /* @var helper_plugin_extension_list $list */
+ $list = $this->loadHelper('extension_list');
+
+ $form = new Form([
+ 'action' => $this->tabURL('', [], '&'),
+ 'id' => 'extension__list',
+ ]);
+ $list->startForm();
+ if ($result) {
+ foreach ($result as $name) {
+ $extension->setExtension($name);
+ $list->addRow($extension, $extension->getID() == $this->infoFor);
+ }
+ } else {
+ $list->nothingFound();
+ }
+ $list->endForm();
+ $form->addHTML($list->render(true));
+ echo $form->toHTML();
+ }
+
+ /**
+ * Display the template tab
+ */
+ public function tabInstall()
+ {
+ global $lang;
+ echo '<div class="panelHeader">';
+ echo $this->locale_xhtml('intro_install');
+ echo '</div>';
+
+ $form = new Form([
+ 'action' => $this->tabURL('', [], '&'),
+ 'enctype' => 'multipart/form-data',
+ 'class' => 'install',
+ ]);
+ $form->addTagOpen('div')->addClass('no');
+ $form->addTextInput('installurl', $this->getLang('install_url'))
+ ->addClass('block')
+ ->attrs(['type' => 'url']);
+ $form->addTag('br');
+ $form->addTextInput('installfile', $this->getLang('install_upload'))
+ ->addClass('block')
+ ->attrs(['type' => 'file']);
+ $form->addTag('br');
+ $form->addCheckbox('overwrite', $lang['js']['media_overwrt'])
+ ->addClass('block');
+ $form->addTag('br');
+ $form->addButton('', $this->getLang('btn_install'))
+ ->attrs(['type' => 'submit', 'title' => $this->getLang('btn_install')]);
+ $form->addTagClose('div');
+ echo $form->toHTML();
+ }
+
+ /**
+ * Print the tab navigation
+ *
+ * @fixme style active one
+ */
+ public function tabNavigation()
+ {
+ echo '<ul class="tabs">';
+ foreach ($this->tabs as $tab) {
+ $url = $this->tabURL($tab);
+ if ($this->currentTab() == $tab) {
+ $class = ' active';
+ } else {
+ $class = '';
+ }
+ echo '<li class="'.$tab.$class.'"><a href="'.$url.'">'.$this->getLang('tab_'.$tab).'</a></li>';
+ }
+ echo '</ul>';
+ }
+
+ /**
+ * Return the currently selected tab
+ *
+ * @return string
+ */
+ public function currentTab()
+ {
+ global $INPUT;
+
+ $tab = $INPUT->str('tab', 'plugins', true);
+ if (!in_array($tab, $this->tabs)) $tab = 'plugins';
+ return $tab;
+ }
+
+ /**
+ * Create an URL inside the extension manager
+ *
+ * @param string $tab tab to load, empty for current tab
+ * @param array $params associative array of parameter to set
+ * @param string $sep seperator to build the URL
+ * @param bool $absolute create absolute URLs?
+ * @return string
+ */
+ public function tabURL($tab = '', $params = [], $sep = '&', $absolute = false)
+ {
+ global $ID;
+ global $INPUT;
+
+ if (!$tab) $tab = $this->currentTab();
+ $defaults = array(
+ 'do' => 'admin',
+ 'page' => 'extension',
+ 'tab' => $tab,
+ );
+ if ($tab == 'search') $defaults['q'] = $INPUT->str('q');
+
+ return wl($ID, array_merge($defaults, $params), $absolute, $sep);
+ }
+}
diff --git a/platform/www/lib/plugins/extension/helper/list.php b/platform/www/lib/plugins/extension/helper/list.php
new file mode 100644
index 0000000..647575b
--- /dev/null
+++ b/platform/www/lib/plugins/extension/helper/list.php
@@ -0,0 +1,674 @@
+<?php
+/**
+ * DokuWiki Plugin extension (Helper Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Hamann <michael@content-space.de>
+ */
+
+/**
+ * Class helper_plugin_extension_list takes care of creating a HTML list of extensions
+ */
+class helper_plugin_extension_list extends DokuWiki_Plugin
+{
+ protected $form = '';
+ /** @var helper_plugin_extension_gui */
+ protected $gui;
+
+ /**
+ * Constructor
+ *
+ * loads additional helpers
+ */
+ public function __construct()
+ {
+ $this->gui = plugin_load('helper', 'extension_gui');
+ }
+
+ /**
+ * Initialize the extension table form
+ */
+ public function startForm()
+ {
+ $this->form .= '<ul class="extensionList">';
+ }
+
+ /**
+ * Build single row of extension table
+ *
+ * @param helper_plugin_extension_extension $extension The extension that shall be added
+ * @param bool $showinfo Show the info area
+ */
+ public function addRow(helper_plugin_extension_extension $extension, $showinfo = false)
+ {
+ $this->startRow($extension);
+ $this->populateColumn('legend', $this->makeLegend($extension, $showinfo));
+ $this->populateColumn('actions', $this->makeActions($extension));
+ $this->endRow();
+ }
+
+ /**
+ * Adds a header to the form
+ *
+ * @param string $id The id of the header
+ * @param string $header The content of the header
+ * @param int $level The level of the header
+ */
+ public function addHeader($id, $header, $level = 2)
+ {
+ $this->form .='<h'.$level.' id="'.$id.'">'.hsc($header).'</h'.$level.'>'.DOKU_LF;
+ }
+
+ /**
+ * Adds a paragraph to the form
+ *
+ * @param string $data The content
+ */
+ public function addParagraph($data)
+ {
+ $this->form .= '<p>'.hsc($data).'</p>'.DOKU_LF;
+ }
+
+ /**
+ * Add hidden fields to the form with the given data
+ *
+ * @param array $data key-value list of fields and their values to add
+ */
+ public function addHidden(array $data)
+ {
+ $this->form .= '<div class="no">';
+ foreach ($data as $key => $value) {
+ $this->form .= '<input type="hidden" name="'.hsc($key).'" value="'.hsc($value).'" />';
+ }
+ $this->form .= '</div>'.DOKU_LF;
+ }
+
+ /**
+ * Add closing tags
+ */
+ public function endForm()
+ {
+ $this->form .= '</ul>';
+ }
+
+ /**
+ * Show message when no results are found
+ */
+ public function nothingFound()
+ {
+ global $lang;
+ $this->form .= '<li class="notfound">'.$lang['nothingfound'].'</li>';
+ }
+
+ /**
+ * Print the form
+ *
+ * @param bool $returnonly whether to return html or print
+ */
+ public function render($returnonly = false)
+ {
+ if ($returnonly) return $this->form;
+ echo $this->form;
+ }
+
+ /**
+ * Start the HTML for the row for the extension
+ *
+ * @param helper_plugin_extension_extension $extension The extension
+ */
+ private function startRow(helper_plugin_extension_extension $extension)
+ {
+ $this->form .= '<li id="extensionplugin__'.hsc($extension->getID()).
+ '" class="'.$this->makeClass($extension).'">';
+ }
+
+ /**
+ * Add a column with the given class and content
+ * @param string $class The class name
+ * @param string $html The content
+ */
+ private function populateColumn($class, $html)
+ {
+ $this->form .= '<div class="'.$class.' col">'.$html.'</div>'.DOKU_LF;
+ }
+
+ /**
+ * End the row
+ */
+ private function endRow()
+ {
+ $this->form .= '</li>'.DOKU_LF;
+ }
+
+ /**
+ * Generate the link to the plugin homepage
+ *
+ * @param helper_plugin_extension_extension $extension The extension
+ * @return string The HTML code
+ */
+ public function makeHomepageLink(helper_plugin_extension_extension $extension)
+ {
+ global $conf;
+ $url = $extension->getURL();
+ if (strtolower(parse_url($url, PHP_URL_HOST)) == 'www.dokuwiki.org') {
+ $linktype = 'interwiki';
+ } else {
+ $linktype = 'extern';
+ }
+ $param = array(
+ 'href' => $url,
+ 'title' => $url,
+ 'class' => ($linktype == 'extern') ? 'urlextern' : 'interwiki iw_doku',
+ 'target' => $conf['target'][$linktype],
+ 'rel' => ($linktype == 'extern') ? 'noopener' : '',
+ );
+ if ($linktype == 'extern' && $conf['relnofollow']) {
+ $param['rel'] = implode(' ', [$param['rel'], 'ugc nofollow']);
+ }
+ $html = ' <a '. buildAttributes($param, true).'>'.
+ $this->getLang('homepage_link').'</a>';
+ return $html;
+ }
+
+ /**
+ * Generate the class name for the row of the extension
+ *
+ * @param helper_plugin_extension_extension $extension The extension object
+ * @return string The class name
+ */
+ public function makeClass(helper_plugin_extension_extension $extension)
+ {
+ $class = ($extension->isTemplate()) ? 'template' : 'plugin';
+ if ($extension->isInstalled()) {
+ $class.=' installed';
+ $class.= ($extension->isEnabled()) ? ' enabled':' disabled';
+ if ($extension->updateAvailable()) $class .= ' updatable';
+ }
+ if (!$extension->canModify()) $class.= ' notselect';
+ if ($extension->isProtected()) $class.= ' protected';
+ //if($this->showinfo) $class.= ' showinfo';
+ return $class;
+ }
+
+ /**
+ * Generate a link to the author of the extension
+ *
+ * @param helper_plugin_extension_extension $extension The extension object
+ * @return string The HTML code of the link
+ */
+ public function makeAuthor(helper_plugin_extension_extension $extension)
+ {
+ if ($extension->getAuthor()) {
+ $mailid = $extension->getEmailID();
+ if ($mailid) {
+ $url = $this->gui->tabURL('search', array('q' => 'authorid:'.$mailid));
+ $html = '<a href="'.$url.'" class="author" title="'.$this->getLang('author_hint').'" >'.
+ '<img src="//www.gravatar.com/avatar/'.$mailid.
+ '?s=20&amp;d=mm" width="20" height="20" alt="" /> '.
+ hsc($extension->getAuthor()).'</a>';
+ } else {
+ $html = '<span class="author">'.hsc($extension->getAuthor()).'</span>';
+ }
+ $html = '<bdi>'.$html.'</bdi>';
+ } else {
+ $html = '<em class="author">'.$this->getLang('unknown_author').'</em>'.DOKU_LF;
+ }
+ return $html;
+ }
+
+ /**
+ * Get the link and image tag for the screenshot/thumbnail
+ *
+ * @param helper_plugin_extension_extension $extension The extension object
+ * @return string The HTML code
+ */
+ public function makeScreenshot(helper_plugin_extension_extension $extension)
+ {
+ $screen = $extension->getScreenshotURL();
+ $thumb = $extension->getThumbnailURL();
+
+ if ($screen) {
+ // use protocol independent URLs for images coming from us #595
+ $screen = str_replace('http://www.dokuwiki.org', '//www.dokuwiki.org', $screen);
+ $thumb = str_replace('http://www.dokuwiki.org', '//www.dokuwiki.org', $thumb);
+
+ $title = sprintf($this->getLang('screenshot'), hsc($extension->getDisplayName()));
+ $img = '<a href="'.hsc($screen).'" target="_blank" class="extension_screenshot">'.
+ '<img alt="'.$title.'" width="120" height="70" src="'.hsc($thumb).'" />'.
+ '</a>';
+ } elseif ($extension->isTemplate()) {
+ $img = '<img alt="" width="120" height="70" src="'.DOKU_BASE.
+ 'lib/plugins/extension/images/template.png" />';
+ } else {
+ $img = '<img alt="" width="120" height="70" src="'.DOKU_BASE.
+ 'lib/plugins/extension/images/plugin.png" />';
+ }
+ $html = '<div class="screenshot" >'.$img.'<span></span></div>'.DOKU_LF;
+ return $html;
+ }
+
+ /**
+ * Extension main description
+ *
+ * @param helper_plugin_extension_extension $extension The extension object
+ * @param bool $showinfo Show the info section
+ * @return string The HTML code
+ */
+ public function makeLegend(helper_plugin_extension_extension $extension, $showinfo = false)
+ {
+ $html = '<div>';
+ $html .= '<h2>';
+ $html .= sprintf(
+ $this->getLang('extensionby'),
+ '<bdi>'.hsc($extension->getDisplayName()).'</bdi>',
+ $this->makeAuthor($extension)
+ );
+ $html .= '</h2>'.DOKU_LF;
+
+ $html .= $this->makeScreenshot($extension);
+
+ $popularity = $extension->getPopularity();
+ if ($popularity !== false && !$extension->isBundled()) {
+ $popularityText = sprintf($this->getLang('popularity'), round($popularity*100, 2));
+ $html .= '<div class="popularity" title="'.$popularityText.'">'.
+ '<div style="width: '.($popularity * 100).'%;">'.
+ '<span class="a11y">'.$popularityText.'</span>'.
+ '</div></div>'.DOKU_LF;
+ }
+
+ if ($extension->getDescription()) {
+ $html .= '<p><bdi>';
+ $html .= hsc($extension->getDescription()).' ';
+ $html .= '</bdi></p>'.DOKU_LF;
+ }
+
+ $html .= $this->makeLinkbar($extension);
+
+ if ($showinfo) {
+ $url = $this->gui->tabURL('');
+ $class = 'close';
+ } else {
+ $url = $this->gui->tabURL('', array('info' => $extension->getID()));
+ $class = '';
+ }
+ $html .= ' <a href="'.$url.'#extensionplugin__'.$extension->getID().
+ '" class="info '.$class.'" title="'.$this->getLang('btn_info').
+ '" data-extid="'.$extension->getID().'">'.$this->getLang('btn_info').'</a>';
+
+ if ($showinfo) {
+ $html .= $this->makeInfo($extension);
+ }
+ $html .= $this->makeNoticeArea($extension);
+ $html .= '</div>'.DOKU_LF;
+ return $html;
+ }
+
+ /**
+ * Generate the link bar HTML code
+ *
+ * @param helper_plugin_extension_extension $extension The extension instance
+ * @return string The HTML code
+ */
+ public function makeLinkbar(helper_plugin_extension_extension $extension)
+ {
+ global $conf;
+ $html = '<div class="linkbar">';
+ $html .= $this->makeHomepageLink($extension);
+
+ $bugtrackerURL = $extension->getBugtrackerURL();
+ if ($bugtrackerURL) {
+ if (strtolower(parse_url($bugtrackerURL, PHP_URL_HOST)) == 'www.dokuwiki.org') {
+ $linktype = 'interwiki';
+ } else {
+ $linktype = 'extern';
+ }
+ $param = array(
+ 'href' => $bugtrackerURL,
+ 'title' => $bugtrackerURL,
+ 'class' => 'bugs',
+ 'target' => $conf['target'][$linktype],
+ 'rel' => ($linktype == 'extern') ? 'noopener' : '',
+ );
+ if ($conf['relnofollow']) {
+ $param['rel'] = implode(' ', [$param['rel'], 'ugc nofollow']);
+ }
+ $html .= ' <a '.buildAttributes($param, true).'>'.
+ $this->getLang('bugs_features').'</a>';
+ }
+ if ($extension->getTags()) {
+ $first = true;
+ $html .= ' <span class="tags">'.$this->getLang('tags').' ';
+ foreach ($extension->getTags() as $tag) {
+ if (!$first) {
+ $html .= ', ';
+ } else {
+ $first = false;
+ }
+ $url = $this->gui->tabURL('search', ['q' => 'tag:'.$tag]);
+ $html .= '<bdi><a href="'.$url.'">'.hsc($tag).'</a></bdi>';
+ }
+ $html .= '</span>';
+ }
+ $html .= '</div>'.DOKU_LF;
+ return $html;
+ }
+
+ /**
+ * Notice area
+ *
+ * @param helper_plugin_extension_extension $extension The extension
+ * @return string The HTML code
+ */
+ public function makeNoticeArea(helper_plugin_extension_extension $extension)
+ {
+ $html = '';
+ $missing_dependencies = $extension->getMissingDependencies();
+ if (!empty($missing_dependencies)) {
+ $html .= '<div class="msg error">' .
+ sprintf(
+ $this->getLang('missing_dependency'),
+ '<bdi>' . implode(', ', $missing_dependencies) . '</bdi>'
+ ) .
+ '</div>';
+ }
+ if ($extension->isInWrongFolder()) {
+ $html .= '<div class="msg error">' .
+ sprintf(
+ $this->getLang('wrong_folder'),
+ '<bdi>' . hsc($extension->getInstallName()) . '</bdi>',
+ '<bdi>' . hsc($extension->getBase()) . '</bdi>'
+ ) .
+ '</div>';
+ }
+ if (($securityissue = $extension->getSecurityIssue()) !== false) {
+ $html .= '<div class="msg error">'.
+ sprintf($this->getLang('security_issue'), '<bdi>'.hsc($securityissue).'</bdi>').
+ '</div>';
+ }
+ if (($securitywarning = $extension->getSecurityWarning()) !== false) {
+ $html .= '<div class="msg notify">'.
+ sprintf($this->getLang('security_warning'), '<bdi>'.hsc($securitywarning).'</bdi>').
+ '</div>';
+ }
+ if ($extension->updateAvailable()) {
+ $html .= '<div class="msg notify">'.
+ sprintf($this->getLang('update_available'), hsc($extension->getLastUpdate())).
+ '</div>';
+ }
+ if ($extension->hasDownloadURLChanged()) {
+ $html .= '<div class="msg notify">' .
+ sprintf(
+ $this->getLang('url_change'),
+ '<bdi>' . hsc($extension->getDownloadURL()) . '</bdi>',
+ '<bdi>' . hsc($extension->getLastDownloadURL()) . '</bdi>'
+ ) .
+ '</div>';
+ }
+ return $html.DOKU_LF;
+ }
+
+ /**
+ * Create a link from the given URL
+ *
+ * Shortens the URL for display
+ *
+ * @param string $url
+ * @return string HTML link
+ */
+ public function shortlink($url)
+ {
+ $link = parse_url($url);
+
+ $base = $link['host'];
+ if (!empty($link['port'])) $base .= $base.':'.$link['port'];
+ $long = $link['path'];
+ if (!empty($link['query'])) $long .= $link['query'];
+
+ $name = shorten($base, $long, 55);
+
+ $html = '<a href="'.hsc($url).'" class="urlextern">'.hsc($name).'</a>';
+ return $html;
+ }
+
+ /**
+ * Plugin/template details
+ *
+ * @param helper_plugin_extension_extension $extension The extension
+ * @return string The HTML code
+ */
+ public function makeInfo(helper_plugin_extension_extension $extension)
+ {
+ $default = $this->getLang('unknown');
+ $html = '<dl class="details">';
+
+ $html .= '<dt>'.$this->getLang('status').'</dt>';
+ $html .= '<dd>'.$this->makeStatus($extension).'</dd>';
+
+ if ($extension->getDonationURL()) {
+ $html .= '<dt>'.$this->getLang('donate').'</dt>';
+ $html .= '<dd>';
+ $html .= '<a href="'.$extension->getDonationURL().'" class="donate">'.
+ $this->getLang('donate_action').'</a>';
+ $html .= '</dd>';
+ }
+
+ if (!$extension->isBundled()) {
+ $html .= '<dt>'.$this->getLang('downloadurl').'</dt>';
+ $html .= '<dd><bdi>';
+ $html .= ($extension->getDownloadURL()
+ ? $this->shortlink($extension->getDownloadURL())
+ : $default);
+ $html .= '</bdi></dd>';
+
+ $html .= '<dt>'.$this->getLang('repository').'</dt>';
+ $html .= '<dd><bdi>';
+ $html .= ($extension->getSourcerepoURL()
+ ? $this->shortlink($extension->getSourcerepoURL())
+ : $default);
+ $html .= '</bdi></dd>';
+ }
+
+ if ($extension->isInstalled()) {
+ if ($extension->getInstalledVersion()) {
+ $html .= '<dt>'.$this->getLang('installed_version').'</dt>';
+ $html .= '<dd>';
+ $html .= hsc($extension->getInstalledVersion());
+ $html .= '</dd>';
+ }
+ if (!$extension->isBundled()) {
+ $html .= '<dt>'.$this->getLang('install_date').'</dt>';
+ $html .= '<dd>';
+ $html .= ($extension->getUpdateDate()
+ ? hsc($extension->getUpdateDate())
+ : $this->getLang('unknown'));
+ $html .= '</dd>';
+ }
+ }
+ if (!$extension->isInstalled() || $extension->updateAvailable()) {
+ $html .= '<dt>'.$this->getLang('available_version').'</dt>';
+ $html .= '<dd>';
+ $html .= ($extension->getLastUpdate()
+ ? hsc($extension->getLastUpdate())
+ : $this->getLang('unknown'));
+ $html .= '</dd>';
+ }
+
+ $html .= '<dt>'.$this->getLang('provides').'</dt>';
+ $html .= '<dd><bdi>';
+ $html .= ($extension->getTypes()
+ ? hsc(implode(', ', $extension->getTypes()))
+ : $default);
+ $html .= '</bdi></dd>';
+
+ if (!$extension->isBundled() && $extension->getCompatibleVersions()) {
+ $html .= '<dt>'.$this->getLang('compatible').'</dt>';
+ $html .= '<dd>';
+ foreach ($extension->getCompatibleVersions() as $date => $version) {
+ $html .= '<bdi>'.$version['label'].' ('.$date.')</bdi>, ';
+ }
+ $html = rtrim($html, ', ');
+ $html .= '</dd>';
+ }
+ if ($extension->getDependencies()) {
+ $html .= '<dt>'.$this->getLang('depends').'</dt>';
+ $html .= '<dd>';
+ $html .= $this->makeLinkList($extension->getDependencies());
+ $html .= '</dd>';
+ }
+
+ if ($extension->getSimilarExtensions()) {
+ $html .= '<dt>'.$this->getLang('similar').'</dt>';
+ $html .= '<dd>';
+ $html .= $this->makeLinkList($extension->getSimilarExtensions());
+ $html .= '</dd>';
+ }
+
+ if ($extension->getConflicts()) {
+ $html .= '<dt>'.$this->getLang('conflicts').'</dt>';
+ $html .= '<dd>';
+ $html .= $this->makeLinkList($extension->getConflicts());
+ $html .= '</dd>';
+ }
+ $html .= '</dl>'.DOKU_LF;
+ return $html;
+ }
+
+ /**
+ * Generate a list of links for extensions
+ *
+ * @param array $ext The extensions
+ * @return string The HTML code
+ */
+ public function makeLinkList($ext)
+ {
+ $html = '';
+ foreach ($ext as $link) {
+ $html .= '<bdi><a href="'.
+ $this->gui->tabURL('search', array('q'=>'ext:'.$link)).'">'.
+ hsc($link).'</a></bdi>, ';
+ }
+ return rtrim($html, ', ');
+ }
+
+ /**
+ * Display the action buttons if they are possible
+ *
+ * @param helper_plugin_extension_extension $extension The extension
+ * @return string The HTML code
+ */
+ public function makeActions(helper_plugin_extension_extension $extension)
+ {
+ global $conf;
+ $html = '';
+ $errors = '';
+
+ if ($extension->isInstalled()) {
+ if (($canmod = $extension->canModify()) === true) {
+ if (!$extension->isProtected()) {
+ $html .= $this->makeAction('uninstall', $extension);
+ }
+ if ($extension->getDownloadURL()) {
+ if ($extension->updateAvailable()) {
+ $html .= $this->makeAction('update', $extension);
+ } else {
+ $html .= $this->makeAction('reinstall', $extension);
+ }
+ }
+ } else {
+ $errors .= '<p class="permerror">'.$this->getLang($canmod).'</p>';
+ }
+
+ if (!$extension->isProtected() && !$extension->isTemplate()) { // no enable/disable for templates
+ if ($extension->isEnabled()) {
+ $html .= $this->makeAction('disable', $extension);
+ } else {
+ $html .= $this->makeAction('enable', $extension);
+ }
+ }
+
+ if ($extension->isGitControlled()) {
+ $errors .= '<p class="permerror">'.$this->getLang('git').'</p>';
+ }
+
+ if ($extension->isEnabled() &&
+ in_array('Auth', $extension->getTypes()) &&
+ $conf['authtype'] != $extension->getID()
+ ) {
+ $errors .= '<p class="permerror">'.$this->getLang('auth').'</p>';
+ }
+ } else {
+ if (($canmod = $extension->canModify()) === true) {
+ if ($extension->getDownloadURL()) {
+ $html .= $this->makeAction('install', $extension);
+ }
+ } else {
+ $errors .= '<div class="permerror">'.$this->getLang($canmod).'</div>';
+ }
+ }
+
+ if (!$extension->isInstalled() && $extension->getDownloadURL()) {
+ $html .= ' <span class="version">'.$this->getLang('available_version').' ';
+ $html .= ($extension->getLastUpdate()
+ ? hsc($extension->getLastUpdate())
+ : $this->getLang('unknown')).'</span>';
+ }
+
+ return $html.' '.$errors.DOKU_LF;
+ }
+
+ /**
+ * Display an action button for an extension
+ *
+ * @param string $action The action
+ * @param helper_plugin_extension_extension $extension The extension
+ * @return string The HTML code
+ */
+ public function makeAction($action, $extension)
+ {
+ $title = '';
+
+ switch ($action) {
+ case 'install':
+ case 'reinstall':
+ $title = 'title="'.hsc($extension->getDownloadURL()).'"';
+ break;
+ }
+
+ $classes = 'button '.$action;
+ $name = 'fn['.$action.']['.hsc($extension->getID()).']';
+
+ $html = '<button class="'.$classes.'" name="'.$name.'" type="submit" '.$title.'>'.
+ $this->getLang('btn_'.$action).'</button> ';
+ return $html;
+ }
+
+ /**
+ * Plugin/template status
+ *
+ * @param helper_plugin_extension_extension $extension The extension
+ * @return string The description of all relevant statusses
+ */
+ public function makeStatus(helper_plugin_extension_extension $extension)
+ {
+ $status = array();
+
+ if ($extension->isInstalled()) {
+ $status[] = $this->getLang('status_installed');
+ if ($extension->isProtected()) {
+ $status[] = $this->getLang('status_protected');
+ } else {
+ $status[] = $extension->isEnabled()
+ ? $this->getLang('status_enabled')
+ : $this->getLang('status_disabled');
+ }
+ } else {
+ $status[] = $this->getLang('status_not_installed');
+ }
+ if (!$extension->canModify()) $status[] = $this->getLang('status_unmodifiable');
+ if ($extension->isBundled()) $status[] = $this->getLang('status_bundled');
+ $status[] = $extension->isTemplate()
+ ? $this->getLang('status_template')
+ : $this->getLang('status_plugin');
+ return implode(', ', $status);
+ }
+}
diff --git a/platform/www/lib/plugins/extension/helper/repository.php b/platform/www/lib/plugins/extension/helper/repository.php
new file mode 100644
index 0000000..712baa0
--- /dev/null
+++ b/platform/www/lib/plugins/extension/helper/repository.php
@@ -0,0 +1,203 @@
+<?php
+/**
+ * DokuWiki Plugin extension (Helper Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Hamann <michael@content-space.de>
+ */
+
+use dokuwiki\Cache\Cache;
+use dokuwiki\HTTP\DokuHTTPClient;
+use dokuwiki\Extension\PluginController;
+
+/**
+ * Class helper_plugin_extension_repository provides access to the extension repository on dokuwiki.org
+ */
+class helper_plugin_extension_repository extends DokuWiki_Plugin
+{
+
+ const EXTENSION_REPOSITORY_API = 'http://www.dokuwiki.org/lib/plugins/pluginrepo/api.php';
+
+ private $loaded_extensions = array();
+ private $has_access = null;
+
+ /**
+ * Initialize the repository (cache), fetches data for all installed plugins
+ */
+ public function init()
+ {
+ /* @var PluginController $plugin_controller */
+ global $plugin_controller;
+ if ($this->hasAccess()) {
+ $list = $plugin_controller->getList('', true);
+ $request_data = array('fmt' => 'php');
+ $request_needed = false;
+ foreach ($list as $name) {
+ $cache = new Cache('##extension_manager##'.$name, '.repo');
+
+ if (!isset($this->loaded_extensions[$name]) &&
+ $this->hasAccess() &&
+ !$cache->useCache(array('age' => 3600 * 24))
+ ) {
+ $this->loaded_extensions[$name] = true;
+ $request_data['ext'][] = $name;
+ $request_needed = true;
+ }
+ }
+
+ if ($request_needed) {
+ $httpclient = new DokuHTTPClient();
+ $data = $httpclient->post(self::EXTENSION_REPOSITORY_API, $request_data);
+ if ($data !== false) {
+ $extensions = unserialize($data);
+ foreach ($extensions as $extension) {
+ $cache = new Cache('##extension_manager##'.$extension['plugin'], '.repo');
+ $cache->storeCache(serialize($extension));
+ }
+ } else {
+ $this->has_access = false;
+ }
+ }
+ }
+ }
+
+ /**
+ * If repository access is available
+ *
+ * @param bool $usecache use cached result if still valid
+ * @return bool If repository access is available
+ */
+ public function hasAccess($usecache = true) {
+ if ($this->has_access === null) {
+ $cache = new Cache('##extension_manager###hasAccess', '.repo');
+
+ if (!$cache->useCache(array('age' => 60*10, 'purge' => !$usecache))) {
+ $httpclient = new DokuHTTPClient();
+ $httpclient->timeout = 5;
+ $data = $httpclient->get(self::EXTENSION_REPOSITORY_API.'?cmd=ping');
+ if ($data !== false) {
+ $this->has_access = true;
+ $cache->storeCache(1);
+ } else {
+ $this->has_access = false;
+ $cache->storeCache(0);
+ }
+ } else {
+ $this->has_access = ($cache->retrieveCache(false) == 1);
+ }
+ }
+ return $this->has_access;
+ }
+
+ /**
+ * Get the remote data of an individual plugin or template
+ *
+ * @param string $name The plugin name to get the data for, template names need to be prefix by 'template:'
+ * @return array The data or null if nothing was found (possibly no repository access)
+ */
+ public function getData($name)
+ {
+ $cache = new Cache('##extension_manager##'.$name, '.repo');
+
+ if (!isset($this->loaded_extensions[$name]) &&
+ $this->hasAccess() &&
+ !$cache->useCache(array('age' => 3600 * 24))
+ ) {
+ $this->loaded_extensions[$name] = true;
+ $httpclient = new DokuHTTPClient();
+ $data = $httpclient->get(self::EXTENSION_REPOSITORY_API.'?fmt=php&ext[]='.urlencode($name));
+ if ($data !== false) {
+ $result = unserialize($data);
+ $cache->storeCache(serialize($result[0]));
+ return $result[0];
+ } else {
+ $this->has_access = false;
+ }
+ }
+ if (file_exists($cache->cache)) {
+ return unserialize($cache->retrieveCache(false));
+ }
+ return array();
+ }
+
+ /**
+ * Search for plugins or templates using the given query string
+ *
+ * @param string $q the query string
+ * @return array a list of matching extensions
+ */
+ public function search($q)
+ {
+ $query = $this->parseQuery($q);
+ $query['fmt'] = 'php';
+
+ $httpclient = new DokuHTTPClient();
+ $data = $httpclient->post(self::EXTENSION_REPOSITORY_API, $query);
+ if ($data === false) return array();
+ $result = unserialize($data);
+
+ $ids = array();
+
+ // store cache info for each extension
+ foreach ($result as $ext) {
+ $name = $ext['plugin'];
+ $cache = new Cache('##extension_manager##'.$name, '.repo');
+ $cache->storeCache(serialize($ext));
+ $ids[] = $name;
+ }
+
+ return $ids;
+ }
+
+ /**
+ * Parses special queries from the query string
+ *
+ * @param string $q
+ * @return array
+ */
+ protected function parseQuery($q)
+ {
+ $parameters = array(
+ 'tag' => array(),
+ 'mail' => array(),
+ 'type' => array(),
+ 'ext' => array()
+ );
+
+ // extract tags
+ if (preg_match_all('/(^|\s)(tag:([\S]+))/', $q, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $m) {
+ $q = str_replace($m[2], '', $q);
+ $parameters['tag'][] = $m[3];
+ }
+ }
+ // extract author ids
+ if (preg_match_all('/(^|\s)(authorid:([\S]+))/', $q, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $m) {
+ $q = str_replace($m[2], '', $q);
+ $parameters['mail'][] = $m[3];
+ }
+ }
+ // extract extensions
+ if (preg_match_all('/(^|\s)(ext:([\S]+))/', $q, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $m) {
+ $q = str_replace($m[2], '', $q);
+ $parameters['ext'][] = $m[3];
+ }
+ }
+ // extract types
+ if (preg_match_all('/(^|\s)(type:([\S]+))/', $q, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $m) {
+ $q = str_replace($m[2], '', $q);
+ $parameters['type'][] = $m[3];
+ }
+ }
+
+ // FIXME make integer from type value
+
+ $parameters['q'] = trim($q);
+ return $parameters;
+ }
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/extension/images/bug.gif b/platform/www/lib/plugins/extension/images/bug.gif
new file mode 100644
index 0000000..08c1ca1
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/bug.gif
Binary files differ
diff --git a/platform/www/lib/plugins/extension/images/disabled.png b/platform/www/lib/plugins/extension/images/disabled.png
new file mode 100644
index 0000000..9c18b04
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/disabled.png
Binary files differ
diff --git a/platform/www/lib/plugins/extension/images/donate.png b/platform/www/lib/plugins/extension/images/donate.png
new file mode 100644
index 0000000..a76dfaa
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/donate.png
Binary files differ
diff --git a/platform/www/lib/plugins/extension/images/down.png b/platform/www/lib/plugins/extension/images/down.png
new file mode 100644
index 0000000..8e399a9
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/down.png
Binary files differ
diff --git a/platform/www/lib/plugins/extension/images/enabled.png b/platform/www/lib/plugins/extension/images/enabled.png
new file mode 100644
index 0000000..edbbb5b
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/enabled.png
Binary files differ
diff --git a/platform/www/lib/plugins/extension/images/icons.xcf b/platform/www/lib/plugins/extension/images/icons.xcf
new file mode 100644
index 0000000..ab69b30
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/icons.xcf
Binary files differ
diff --git a/platform/www/lib/plugins/extension/images/license.txt b/platform/www/lib/plugins/extension/images/license.txt
new file mode 100644
index 0000000..44e176a
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/license.txt
@@ -0,0 +1,4 @@
+enabled.png - CC0, (c) Tanguy Ortolo
+disabled.png - public domain, (c) Tango Desktop Project http://commons.wikimedia.org/wiki/File:Dialog-information.svg
+plugin.png - public domain, (c) nicubunu, http://openclipart.org/detail/15093/blue-jigsaw-piece-07-by-nicubunu
+template.png - public domain, (c) mathec, http://openclipart.org/detail/166596/palette-by-mathec
diff --git a/platform/www/lib/plugins/extension/images/overlay.png b/platform/www/lib/plugins/extension/images/overlay.png
new file mode 100644
index 0000000..5414206
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/overlay.png
Binary files differ
diff --git a/platform/www/lib/plugins/extension/images/plugin.png b/platform/www/lib/plugins/extension/images/plugin.png
new file mode 100644
index 0000000..62424b2
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/plugin.png
Binary files differ
diff --git a/platform/www/lib/plugins/extension/images/tag.png b/platform/www/lib/plugins/extension/images/tag.png
new file mode 100644
index 0000000..1b1dd75
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/tag.png
Binary files differ
diff --git a/platform/www/lib/plugins/extension/images/template.png b/platform/www/lib/plugins/extension/images/template.png
new file mode 100644
index 0000000..67240d1
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/template.png
Binary files differ
diff --git a/platform/www/lib/plugins/extension/images/up.png b/platform/www/lib/plugins/extension/images/up.png
new file mode 100644
index 0000000..531b2dd
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/up.png
Binary files differ
diff --git a/platform/www/lib/plugins/extension/images/warning.png b/platform/www/lib/plugins/extension/images/warning.png
new file mode 100644
index 0000000..c1af79f
--- /dev/null
+++ b/platform/www/lib/plugins/extension/images/warning.png
Binary files differ
diff --git a/platform/www/lib/plugins/extension/lang/en/intro_install.txt b/platform/www/lib/plugins/extension/lang/en/intro_install.txt
new file mode 100644
index 0000000..a5d5ab0
--- /dev/null
+++ b/platform/www/lib/plugins/extension/lang/en/intro_install.txt
@@ -0,0 +1 @@
+Here you can manually install plugins and templates by either uploading them or providing a direct download URL.
diff --git a/platform/www/lib/plugins/extension/lang/en/intro_plugins.txt b/platform/www/lib/plugins/extension/lang/en/intro_plugins.txt
new file mode 100644
index 0000000..4e42efe
--- /dev/null
+++ b/platform/www/lib/plugins/extension/lang/en/intro_plugins.txt
@@ -0,0 +1 @@
+These are the plugins currently installed in your DokuWiki. You can enable or disable or even completely uninstall them here. Plugin updates are shown here as well, be sure to read the plugin's documentation before updating. \ No newline at end of file
diff --git a/platform/www/lib/plugins/extension/lang/en/intro_search.txt b/platform/www/lib/plugins/extension/lang/en/intro_search.txt
new file mode 100644
index 0000000..81aa431
--- /dev/null
+++ b/platform/www/lib/plugins/extension/lang/en/intro_search.txt
@@ -0,0 +1 @@
+This tab gives you access to all available 3rd party [[doku>plugins|plugins]] and [[doku>template|templates]] for DokuWiki. Please be aware that installing 3rd party code may pose a **security risk**, you may want to read about [[doku>security#plugin_security|plugin security]] first.
diff --git a/platform/www/lib/plugins/extension/lang/en/intro_templates.txt b/platform/www/lib/plugins/extension/lang/en/intro_templates.txt
new file mode 100644
index 0000000..012a749
--- /dev/null
+++ b/platform/www/lib/plugins/extension/lang/en/intro_templates.txt
@@ -0,0 +1 @@
+These are the templates currently installed in your DokuWiki. You can select the template to be used in the [[?do=admin&page=config|Configuration Manager]].
diff --git a/platform/www/lib/plugins/extension/lang/en/lang.php b/platform/www/lib/plugins/extension/lang/en/lang.php
new file mode 100644
index 0000000..f9753ae
--- /dev/null
+++ b/platform/www/lib/plugins/extension/lang/en/lang.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * English language file for extension plugin
+ *
+ * @author Michael Hamann <michael@content-space.de>
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+
+$lang['menu'] = 'Extension Manager';
+
+$lang['tab_plugins'] = 'Installed Plugins';
+$lang['tab_templates'] = 'Installed Templates';
+$lang['tab_search'] = 'Search and Install';
+$lang['tab_install'] = 'Manual Install';
+
+$lang['notimplemented'] = 'This feature hasn\'t been implemented yet';
+$lang['notinstalled'] = 'This extension is not installed';
+$lang['alreadyenabled'] = 'This extension has already been enabled';
+$lang['alreadydisabled'] = 'This extension has already been disabled';
+$lang['pluginlistsaveerror'] = 'There was an error saving the plugin list';
+$lang['unknownauthor'] = 'Unknown author';
+$lang['unknownversion'] = 'Unknown version';
+
+$lang['btn_info'] = 'Show more info';
+$lang['btn_update'] = 'Update';
+$lang['btn_uninstall'] = 'Uninstall';
+$lang['btn_enable'] = 'Enable';
+$lang['btn_disable'] = 'Disable';
+$lang['btn_install'] = 'Install';
+$lang['btn_reinstall'] = 'Re-install';
+
+$lang['js']['reallydel'] = 'Really uninstall this extension?';
+
+$lang['search_for'] = 'Search Extension:';
+$lang['search'] = 'Search';
+
+$lang['extensionby'] = '<strong>%s</strong> by %s';
+$lang['screenshot'] = 'Screenshot of %s';
+$lang['popularity'] = 'Popularity: %s%%';
+$lang['homepage_link'] = 'Docs';
+$lang['bugs_features'] = 'Bugs';
+$lang['tags'] = 'Tags:';
+$lang['author_hint'] = 'Search extensions by this author';
+$lang['installed'] = 'Installed:';
+$lang['downloadurl'] = 'Download URL:';
+$lang['repository'] = 'Repository:';
+$lang['unknown'] = '<em>unknown</em>';
+$lang['installed_version'] = 'Installed version:';
+$lang['install_date'] = 'Your last update:';
+$lang['available_version'] = 'Available version:';
+$lang['compatible'] = 'Compatible with:';
+$lang['depends'] = 'Depends on:';
+$lang['similar'] = 'Similar to:';
+$lang['conflicts'] = 'Conflicts with:';
+$lang['donate'] = 'Like this?';
+$lang['donate_action'] = 'Buy the author a coffee!';
+$lang['repo_retry'] = 'Retry';
+$lang['provides'] = 'Provides:';
+$lang['status'] = 'Status:';
+$lang['status_installed'] = 'installed';
+$lang['status_not_installed'] = 'not installed';
+$lang['status_protected'] = 'protected';
+$lang['status_enabled'] = 'enabled';
+$lang['status_disabled'] = 'disabled';
+$lang['status_unmodifiable'] = 'unmodifiable';
+$lang['status_plugin'] = 'plugin';
+$lang['status_template'] = 'template';
+$lang['status_bundled'] = 'bundled';
+
+$lang['msg_enabled'] = 'Plugin %s enabled';
+$lang['msg_disabled'] = 'Plugin %s disabled';
+$lang['msg_delete_success'] = 'Extension %s uninstalled';
+$lang['msg_delete_failed'] = 'Uninstalling Extension %s failed';
+$lang['msg_template_install_success'] = 'Template %s installed successfully';
+$lang['msg_template_update_success'] = 'Template %s updated successfully';
+$lang['msg_plugin_install_success'] = 'Plugin %s installed successfully';
+$lang['msg_plugin_update_success'] = 'Plugin %s updated successfully';
+$lang['msg_upload_failed'] = 'Uploading the file failed';
+$lang['msg_nooverwrite'] = 'Extension %s already exists so it is not being overwritten; to overwrite, tick the overwrite option';
+
+$lang['missing_dependency'] = '<strong>Missing or disabled dependency:</strong> %s';
+$lang['security_issue'] = '<strong>Security Issue:</strong> %s';
+$lang['security_warning'] = '<strong>Security Warning:</strong> %s';
+$lang['update_available'] = '<strong>Update:</strong> New version %s is available.';
+$lang['wrong_folder'] = '<strong>Plugin installed incorrectly:</strong> Rename plugin directory "%s" to "%s".';
+$lang['url_change'] = '<strong>URL changed:</strong> Download URL has changed since last download. Check if the new URL is valid before updating the extension.<br />New: %s<br />Old: %s';
+
+$lang['error_badurl'] = 'URLs should start with http or https';
+$lang['error_dircreate'] = 'Unable to create temporary folder to receive download';
+$lang['error_download'] = 'Unable to download the file: %s';
+$lang['error_decompress'] = 'Unable to decompress the downloaded file. This maybe as a result of a bad download, in which case you should try again; or the compression format may be unknown, in which case you will need to download and install manually.';
+$lang['error_findfolder'] = 'Unable to identify extension directory, you need to download and install manually';
+$lang['error_copy'] = 'There was a file copy error while attempting to install files for directory <em>%s</em>: the disk could be full or file access permissions may be incorrect. This may have resulted in a partially installed plugin and leave your wiki installation unstable';
+
+$lang['noperms'] = 'Extension directory is not writable';
+$lang['notplperms'] = 'Template directory is not writable';
+$lang['nopluginperms'] = 'Plugin directory is not writable';
+$lang['git'] = 'This extension was installed via git, you may not want to update it here.';
+$lang['auth'] = 'This auth plugin is not enabled in configuration, consider disabling it.';
+
+$lang['install_url'] = 'Install from URL:';
+$lang['install_upload'] = 'Upload Extension:';
+
+$lang['repo_error'] = 'The plugin repository could not be contacted. Make sure your server is allowed to contact www.dokuwiki.org and check your proxy settings.';
+$lang['nossl'] = 'Your PHP seems to miss SSL support. Downloading will not work for many DokuWiki extensions.';
+
+$lang['js']['display_viewoptions'] = 'View Options:';
+$lang['js']['display_enabled'] = 'enabled';
+$lang['js']['display_disabled'] = 'disabled';
+$lang['js']['display_updatable'] = 'updatable';
diff --git a/platform/www/lib/plugins/extension/plugin.info.txt b/platform/www/lib/plugins/extension/plugin.info.txt
new file mode 100644
index 0000000..7ee84dc
--- /dev/null
+++ b/platform/www/lib/plugins/extension/plugin.info.txt
@@ -0,0 +1,7 @@
+base extension
+author Michael Hamann
+email michael@content-space.de
+date 2015-07-26
+name Extension Manager
+desc Allows managing and installing plugins and templates
+url https://www.dokuwiki.org/plugin:extension
diff --git a/platform/www/lib/plugins/extension/script.js b/platform/www/lib/plugins/extension/script.js
new file mode 100644
index 0000000..7c91580
--- /dev/null
+++ b/platform/www/lib/plugins/extension/script.js
@@ -0,0 +1,145 @@
+jQuery(function(){
+
+ var $extmgr = jQuery('#extension__manager');
+
+ /**
+ * Confirm uninstalling
+ */
+ $extmgr.find('button.uninstall').on('click', function(e){
+ if(!window.confirm(LANG.plugins.extension.reallydel)){
+ e.preventDefault();
+ return false;
+ }
+ return true;
+ });
+
+ /**
+ * very simple lightbox
+ * @link http://webdesign.tutsplus.com/tutorials/htmlcss-tutorials/super-simple-lightbox-with-css-and-jquery/
+ */
+ $extmgr.find('a.extension_screenshot').on('click', function(e) {
+ e.preventDefault();
+
+ //Get clicked link href
+ var image_href = jQuery(this).attr("href");
+
+ // create lightbox if needed
+ var $lightbox = jQuery('#plugin__extensionlightbox');
+ if(!$lightbox.length){
+ $lightbox = jQuery('<div id="plugin__extensionlightbox"><p>Click to close</p><div></div></div>')
+ .appendTo(jQuery('body'))
+ .hide()
+ .on('click', function(){
+ $lightbox.hide();
+ });
+ }
+
+ // fill and show it
+ $lightbox
+ .show()
+ .find('div').html('<img src="' + image_href + '" />');
+
+
+ return false;
+ });
+
+ /**
+ * Enable/Disable extension via AJAX
+ */
+ $extmgr.find('button.disable, button.enable').on('click', function (e) {
+ e.preventDefault();
+ var $btn = jQuery(this);
+
+ // get current state
+ var extension = $btn.attr('name').split('[')[2];
+ extension = extension.substr(0, extension.length - 1);
+ var act = ($btn.hasClass('disable')) ? 'disable' : 'enable';
+
+ // disable while we wait
+ $btn.attr('disabled', 'disabled');
+ $btn.css('cursor', 'wait');
+
+ // execute
+ jQuery.get(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ {
+ call: 'plugin_extension',
+ ext: extension,
+ act: act
+ },
+ function (data) {
+ $btn.css('cursor', '')
+ .removeAttr('disabled')
+ .removeClass('disable')
+ .removeClass('enable')
+ .text(data.label)
+ .addClass(data.reverse)
+ .parents('li')
+ .removeClass('disabled')
+ .removeClass('enabled')
+ .addClass(data.state);
+ }
+ );
+ });
+
+ /**
+ * AJAX detail infos
+ */
+ $extmgr.find('a.info').on('click', function(e){
+ e.preventDefault();
+
+ var $link = jQuery(this);
+ var $details = $link.parent().find('dl.details');
+ if($details.length){
+ $link.toggleClass('close');
+ $details.toggle();
+ return;
+ }
+
+ $link.addClass('close');
+ jQuery.get(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ {
+ call: 'plugin_extension',
+ ext: $link.data('extid'),
+ act: 'info'
+ },
+ function(data){
+ $link.parent().append(data);
+ }
+ );
+ });
+
+ /**
+ Create section for enabling/disabling viewing options
+ */
+ if ( $extmgr.find('.plugins, .templates').hasClass('active') ) {
+ var $extlist = jQuery('#extension__list');
+ $extlist.addClass('hasDisplayOptions');
+
+ var $displayOpts = jQuery('<p>', { id: 'extension__viewoptions'} ).appendTo($extmgr.find( '.panelHeader' ));
+ $displayOpts.append(LANG.plugins.extension.display_viewoptions);
+
+ var displayOptionsHandler = function(){
+ $extlist.toggleClass( this.name );
+ DokuCookie.setValue('ext_'+this.name, $extlist.hasClass(this.name) ? '1' : '0');
+ };
+
+ jQuery(['enabled', 'disabled', 'updatable']).each(function(index, chkName){
+ var $label = jQuery( '<label></label>' )
+ .appendTo($displayOpts);
+ var $input = jQuery( '<input />', { type: 'checkbox', name: chkName })
+ .on('change', displayOptionsHandler)
+ .appendTo($label);
+
+ var previous = DokuCookie.getValue('ext_'+chkName);
+ if(typeof previous === "undefined" || previous == '1') {
+ $input.trigger('click');
+ }
+
+ jQuery( '<span/>' )
+ .append(' '+LANG.plugins.extension['display_'+chkName])
+ .appendTo($label);
+ });
+ }
+});
diff --git a/platform/www/lib/plugins/extension/style.less b/platform/www/lib/plugins/extension/style.less
new file mode 100644
index 0000000..261fa1c
--- /dev/null
+++ b/platform/www/lib/plugins/extension/style.less
@@ -0,0 +1,386 @@
+/*
+ * Extension plugin styles
+ *
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ * @author Piyush Mishra <me@piyushmishra.com>
+ * @author Håkan Sandell <sandell.hakan@gmail.com>
+ * @author Anika Henke <anika@selfthinker.org>
+ */
+
+/**
+ * very simple lightbox
+ * @link http://webdesign.tutsplus.com/tutorials/htmlcss-tutorials/super-simple-lightbox-with-css-and-jquery/
+ */
+#plugin__extensionlightbox {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: url(images/overlay.png) repeat;
+ text-align: center;
+ cursor: pointer;
+ z-index: 9999;
+
+ p {
+ text-align: right;
+ color: #fff;
+ margin-right: 20px;
+ font-size: 12px;
+ }
+
+ img {
+ box-shadow: 0 0 25px #111;
+ max-width: 90%;
+ max-height: 90%;
+ }
+}
+
+/**
+ * general styles
+ */
+#extension__manager {
+ // tab layout - most of it is in the main template
+ ul.tabs li.active a {
+ background-color: @ini_background_alt;
+ border-bottom: solid 1px @ini_background_alt;
+ z-index: 2;
+ }
+ .panelHeader {
+ background-color: @ini_background_alt;
+ margin: 0 0 10px 0;
+ padding: 10px 10px 8px;
+ overflow: hidden;
+ }
+
+ // message spacing
+ div.msg {
+ margin: 0.4em 0 0 0;
+ }
+}
+
+/*
+ * extensions table
+ */
+#extension__list {
+ ul.extensionList {
+ margin-left: 0;
+ margin-right: 0;
+ padding: 0;
+ list-style: none;
+ }
+
+ ul.extensionList li {
+ margin: 0 0 .5em;
+ padding: 0 0 .5em;
+ color: @ini_text;
+ border-bottom: 1px solid @ini_border;
+ overflow: hidden;
+ }
+
+ button {
+ margin-bottom: .3em;
+ }
+}
+
+/**
+ * extension table left column
+ */
+#extension__list .legend {
+ position: relative;
+ width: 75%;
+ float: left;
+
+ // padding
+ > div {
+ padding: 0 .5em 0 132px;
+ border-right: 1px solid @ini_background_alt;
+ overflow: hidden;
+ }
+
+ // screenshot
+ div.screenshot {
+ margin-top: 4px;
+ margin-left: -132px;
+ max-width: 120px;
+ float: left;
+ position: relative;
+
+ img {
+ width: 120px;
+ height: 70px;
+ border-radius: 5px;
+ box-shadow: 2px 2px 2px #666;
+ }
+
+ span {
+ min-height: 24px;
+ min-width: 24px;
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
+ }
+
+ // plugin headline
+ h2 {
+ width: 100%;
+ float: right;
+ margin: 0.2em 0 0.5em;
+ font-size: 100%;
+ font-weight: normal;
+ border: none;
+
+ strong {
+ font-size: 120%;
+ font-weight: bold;
+ vertical-align: baseline;
+ }
+ }
+
+ // description
+ p {
+ margin: 0 0 0.6em 0;
+ }
+
+ // popularity bar
+ div.popularity {
+ background-color: @ini_background;
+ border: 1px solid silver;
+ height: .4em;
+ margin: 0 auto;
+ padding: 1px;
+ width: 5.5em;
+ position: absolute;
+ right: .5em;
+ top: 0.2em;
+
+ div {
+ background-color: @ini_border;
+ height: 100%;
+ }
+ }
+
+ // Docs, Bugs, Tags
+ div.linkbar {
+ font-size: 85%;
+
+ span.tags {
+ padding-left: 18px;
+ background: transparent url(images/tag.png) no-repeat 0 0;
+ }
+
+ a.bugs {
+ padding-left: 18px;
+ background: transparent url(images/bug.gif) no-repeat 0 0;
+ }
+ }
+
+ // more info button
+ a.info {
+ background: transparent url(images/down.png) no-repeat 0 0;
+ border-width: 0;
+ height: 13px;
+ width: 13px;
+ text-indent: -9999px;
+ float: right;
+ margin: .5em 0 0;
+ overflow: hidden;
+
+ &.close {
+ background: transparent url(images/up.png) no-repeat 0 0;
+ }
+ }
+
+ // detailed info box
+ dl.details {
+ margin: 0.4em 0 0 0;
+ font-size: 85%;
+ border-top: 1px solid @ini_background_alt;
+ clear: both;
+
+ dt {
+ clear: left;
+ float: left;
+ width: 25%;
+ margin: 0;
+ text-align: right;
+ font-weight: normal;
+ padding: 0.2em 5px 0 0;
+ font-weight: bold;
+ }
+
+ dd {
+ margin-left: 25%;
+ padding: 0.2em 0 0 5px;
+
+ a.donate {
+ padding-left: 18px;
+ background: transparent url(images/donate.png) left center no-repeat;
+ }
+ }
+ }
+}
+
+[dir=rtl] #extension__list .legend {
+ float: right;
+
+ > div {
+ padding: 0 132px 0 .5em;
+ border-left: 1px solid @ini_background_alt;
+ border-right-width: 0;
+ }
+
+ div.screenshot {
+ margin-left: 0;
+ margin-right: -132px;
+ float: right;
+
+ span {
+ left: auto;
+ right: 0;
+ }
+ }
+
+ h2 {
+ float: left;
+ }
+
+ div.popularity {
+ right: auto;
+ left: .5em;
+ }
+
+ div.linkbar span.tags,
+ dl.details dd a.donate {
+ padding-left: 0;
+ padding-right: 18px;
+ background-position: top right;
+ }
+
+ a.info {
+ float: left;
+ }
+
+ dl.details {
+ dt {
+ clear: right;
+ float: right;
+ text-align: left;
+ padding-left: 5px;
+ padding-right: 0;
+ }
+
+ dd {
+ margin-left: 0;
+ margin-right: 25%;
+ padding-left: 0;
+ padding-right: 5px;
+ }
+ }
+}
+
+/*
+ * Enabled/Disabled overrides
+ */
+#extension__list {
+
+ &.hasDisplayOptions {
+ .enabled,
+ .disabled,
+ .updatable {
+ display: none;
+ }
+
+ &.enabled .enabled,
+ &.disabled .disabled,
+ &.updatable .updatable {
+ display: block;
+ }
+ }
+
+ .enabled div.screenshot span {
+ background: transparent url(images/enabled.png) no-repeat 2px 2px;
+ }
+
+ .disabled div.screenshot span {
+ background: transparent url(images/disabled.png) no-repeat 2px 2px;
+ }
+
+ .disabled .legend {
+ opacity: 0.7;
+ }
+}
+
+/**
+ * extension table right column
+ */
+#extension__manager .actions {
+ padding: 0;
+ font-size: 95%;
+ width: 25%;
+ float: right;
+ text-align: right;
+
+ .version {
+ display: block;
+ }
+
+ p {
+ margin: 0.2em 0;
+ text-align: center;
+ }
+
+ p.permerror {
+ margin-left: 0.4em;
+ text-align: left;
+ padding-left: 19px;
+ background: transparent url(images/warning.png) center left no-repeat;
+ line-height: 18px;
+ font-size: 12px;
+ }
+}
+
+[dir=rtl] #extension__manager .actions {
+ float: left;
+ text-align: left;
+
+ p.permerror {
+ margin-left: 0;
+ margin-right: 0.4em;
+ text-align: right;
+ padding-left: 0;
+ padding-right: 19px;
+ background-position: center right;
+ }
+}
+
+/**
+ * Search form
+ */
+#extension__manager form.search {
+ display: block;
+ margin-bottom: 2em;
+
+ span {
+ font-weight: bold;
+ }
+
+ input.edit {
+ width: 25em;
+ }
+}
+
+/**
+ * Install form
+ */
+#extension__manager form.install {
+ text-align: center;
+ display: block;
+ width: 60%;
+}
+
+#extension__viewoptions label {
+ margin-left: 1em;
+ vertical-align: baseline;
+}
diff --git a/platform/www/lib/plugins/farmer/.github/auto-comment.yml b/platform/www/lib/plugins/farmer/.github/auto-comment.yml
new file mode 100644
index 0000000..f6a72e3
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/.github/auto-comment.yml
@@ -0,0 +1,9 @@
+# auto replies used by probot/auto-comment
+
+issuesOpened: >
+ Thank you for opening this issue.
+
+ [CosmoCode](https://www.cosmocode.de) is a software company in Berlin providing services for wiki, app and web development. As such we can't guarantee quick responses for issues opened on our Open Source projects.
+
+ If you require certain features or bugs fixed, you can always hire us. Feel free to contact us at dokuwiki@cosmocode.de for an offer.
+
diff --git a/platform/www/lib/plugins/farmer/.travis.yml b/platform/www/lib/plugins/farmer/.travis.yml
new file mode 100644
index 0000000..6814beb
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/.travis.yml
@@ -0,0 +1,15 @@
+# Config file for travis-ci.org
+
+language: php
+php:
+ - "7.3"
+ - "7.2"
+ - "7.1"
+ - "7.0"
+ - "5.6"
+env:
+ - DOKUWIKI=master
+ - DOKUWIKI=stable
+before_install: wget https://raw.github.com/splitbrain/dokuwiki-travis/master/travis.sh
+install: sh travis.sh
+script: cd _test && ./phpunit.phar --stderr --group plugin_farmer
diff --git a/platform/www/lib/plugins/farmer/3rdparty/PHPIco.php b/platform/www/lib/plugins/farmer/3rdparty/PHPIco.php
new file mode 100644
index 0000000..17b3b55
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/3rdparty/PHPIco.php
@@ -0,0 +1,248 @@
+<?php
+/*
+Copyright 2011-2013 Chris Jean & iThemes
+Licensed under GPLv2 or above
+
+Version 1.0.2
+
+Adjusted for DokuWiki Farmer Plugin
+*/
+
+namespace chrisbliss18\phpico;
+
+class PHPIco {
+ /**
+ * Images in the BMP format.
+ *
+ * @var array
+ * @access private
+ */
+ var $_images = array();
+
+ /**
+ * Constructor - Create a new ICO generator.
+ *
+ * If the constructor is not passed a file, a file will need to be supplied using the {@link PHP_ICO::add_image}
+ * function in order to generate an ICO file.
+ *
+ * @param bool|string $file Optional. Path to the source image file.
+ * @param array $sizes Optional. An array of sizes (each size is an array with a width and height) that the source image should be rendered at in the generated ICO file. If sizes are not supplied, the size of the source image will be used.
+ * @throws \Exception
+ */
+ function __construct( $file = false, $sizes = array() ) {
+ $required_functions = array(
+ 'getimagesize',
+ 'imagecreatefromstring',
+ 'imagecreatetruecolor',
+ 'imagecolortransparent',
+ 'imagecolorallocatealpha',
+ 'imagealphablending',
+ 'imagesavealpha',
+ 'imagesx',
+ 'imagesy',
+ 'imagecopyresampled',
+ );
+
+ foreach ( $required_functions as $function ) {
+ if ( ! function_exists( $function ) ) {
+ throw new \Exception( "The PHP_ICO class was unable to find the $function function, which is part of the GD library. Ensure that the system has the GD library installed and that PHP has access to it through a PHP interface, such as PHP's GD module. Since this function was not found, the library will be unable to create ICO files." );
+ }
+ }
+
+ if ( false != $file )
+ $this->add_image( $file, $sizes );
+ }
+
+ /**
+ * Add an image to the generator.
+ *
+ * This function adds a source image to the generator. It serves two main purposes: add a source image if one was
+ * not supplied to the constructor and to add additional source images so that different images can be supplied for
+ * different sized images in the resulting ICO file. For instance, a small source image can be used for the small
+ * resolutions while a larger source image can be used for large resolutions.
+ *
+ * @param string $file Path to the source image file.
+ * @param array $sizes Optional. An array of sizes (each size is an array with a width and height) that the source image should be rendered at in the generated ICO file. If sizes are not supplied, the size of the source image will be used.
+ * @return boolean true on success and false on failure.
+ */
+ function add_image( $file, $sizes = array() ) {
+ if ( false === ( $im = $this->_load_image_file( $file ) ) )
+ return false;
+
+
+ if ( empty( $sizes ) )
+ $sizes = array( imagesx( $im ), imagesy( $im ) );
+
+ // If just a single size was passed, put it in array.
+ if ( ! is_array( $sizes[0] ) )
+ $sizes = array( $sizes );
+
+ foreach ( (array) $sizes as $size ) {
+ list( $width, $height ) = $size;
+
+ $new_im = imagecreatetruecolor( $width, $height );
+
+ imagecolortransparent( $new_im, imagecolorallocatealpha( $new_im, 0, 0, 0, 127 ) );
+ imagealphablending( $new_im, false );
+ imagesavealpha( $new_im, true );
+
+ $source_width = imagesx( $im );
+ $source_height = imagesy( $im );
+
+ if ( false === imagecopyresampled( $new_im, $im, 0, 0, 0, 0, $width, $height, $source_width, $source_height ) )
+ continue;
+
+ $this->_add_image_data( $new_im );
+ }
+
+ return true;
+ }
+
+ /**
+ * Write the ICO file data to a file path.
+ *
+ * @param string $file Path to save the ICO file data into.
+ * @return boolean true on success and false on failure.
+ */
+ function save_ico( $file ) {
+ if ( false === ( $data = $this->_get_ico_data() ) )
+ return false;
+
+ if ( false === ( $fh = fopen( $file, 'w' ) ) )
+ return false;
+
+ if ( false === ( fwrite( $fh, $data ) ) ) {
+ fclose( $fh );
+ return false;
+ }
+
+ fclose( $fh );
+
+ return true;
+ }
+
+ /**
+ * Generate the final ICO data by creating a file header and adding the image data.
+ */
+ protected function _get_ico_data() {
+ if ( ! is_array( $this->_images ) || empty( $this->_images ) )
+ return false;
+
+
+ $data = pack( 'vvv', 0, 1, count( $this->_images ) );
+ $pixel_data = '';
+
+ $icon_dir_entry_size = 16;
+
+ $offset = 6 + ( $icon_dir_entry_size * count( $this->_images ) );
+
+ foreach ( $this->_images as $image ) {
+ $data .= pack( 'CCCCvvVV', $image['width'], $image['height'], $image['color_palette_colors'], 0, 1, $image['bits_per_pixel'], $image['size'], $offset );
+ $pixel_data .= $image['data'];
+
+ $offset += $image['size'];
+ }
+
+ $data .= $pixel_data;
+ unset( $pixel_data );
+
+
+ return $data;
+ }
+
+ /**
+ * Take a GD image resource and change it into a raw BMP format.
+ *
+ * @param resource $im
+ */
+ protected function _add_image_data( $im ) {
+ $width = imagesx( $im );
+ $height = imagesy( $im );
+
+
+ $pixel_data = array();
+
+ $opacity_data = array();
+ $current_opacity_val = 0;
+
+ for ( $y = $height - 1; $y >= 0; $y-- ) {
+ for ( $x = 0; $x < $width; $x++ ) {
+ $color = imagecolorat( $im, $x, $y );
+
+ $alpha = ( $color & 0x7F000000 ) >> 24;
+ $alpha = ( 1 - ( $alpha / 127 ) ) * 255;
+
+ $color &= 0xFFFFFF;
+ $color |= 0xFF000000 & ( $alpha << 24 );
+
+ $pixel_data[] = $color;
+
+
+ $opacity = ( $alpha <= 127 ) ? 1 : 0;
+
+ $current_opacity_val = ( $current_opacity_val << 1 ) | $opacity;
+
+ if ( ( ( $x + 1 ) % 32 ) == 0 ) {
+ $opacity_data[] = $current_opacity_val;
+ $current_opacity_val = 0;
+ }
+ }
+
+ if ( ( $x % 32 ) > 0 ) {
+ while ( ( $x++ % 32 ) > 0 )
+ $current_opacity_val = $current_opacity_val << 1;
+
+ $opacity_data[] = $current_opacity_val;
+ $current_opacity_val = 0;
+ }
+ }
+
+ $image_header_size = 40;
+ $color_mask_size = $width * $height * 4;
+ $opacity_mask_size = ( ceil( $width / 32 ) * 4 ) * $height;
+
+
+ $data = pack( 'VVVvvVVVVVV', 40, $width, ( $height * 2 ), 1, 32, 0, 0, 0, 0, 0, 0 );
+
+ foreach ( $pixel_data as $color )
+ $data .= pack( 'V', $color );
+
+ foreach ( $opacity_data as $opacity )
+ $data .= pack( 'N', $opacity );
+
+
+ $image = array(
+ 'width' => $width,
+ 'height' => $height,
+ 'color_palette_colors' => 0,
+ 'bits_per_pixel' => 32,
+ 'size' => $image_header_size + $color_mask_size + $opacity_mask_size,
+ 'data' => $data,
+ );
+
+ $this->_images[] = $image;
+ }
+
+ /**
+ * Read in the source image file and convert it into a GD image resource.
+ *
+ * @param string $file
+ * @return bool|resource
+ */
+ protected function _load_image_file( $file ) {
+ // Run a cheap check to verify that it is an image file.
+ if ( false === ( $size = getimagesize( $file ) ) )
+ return false;
+
+ if ( false === ( $file_data = file_get_contents( $file ) ) )
+ return false;
+
+ if ( false === ( $im = imagecreatefromstring( $file_data ) ) )
+ return false;
+
+ unset( $file_data );
+
+
+ return $im;
+ }
+}
diff --git a/platform/www/lib/plugins/farmer/3rdparty/RingIcon.php b/platform/www/lib/plugins/farmer/3rdparty/RingIcon.php
new file mode 100644
index 0000000..c8dfdbc
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/3rdparty/RingIcon.php
@@ -0,0 +1,186 @@
+<?php
+
+namespace splitbrain\RingIcon;
+
+/**
+ * Class RingIcon
+ *
+ * Generates a identicon/visiglyph like image based on concentric rings
+ *
+ * @todo add a mono color version
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @license MIT
+ * @package splitbrain\RingIcon
+ */
+class RingIcon
+{
+
+ protected $size;
+ protected $fullsize;
+ protected $rings;
+ protected $center;
+ protected $ringwidth;
+ protected $seed;
+ protected $ismono = false;
+ protected $monocolor = null;
+
+ /**
+ * RingIcon constructor.
+ * @param int $size width and height of the resulting image
+ * @param int $rings number of rings
+ */
+ public function __construct($size, $rings = 3)
+ {
+ $this->size = $size;
+ $this->fullsize = $this->size * 5;
+ $this->rings = 4;
+
+ $this->center = floor($this->fullsize / 2);
+ $this->ringwidth = floor($this->fullsize / $rings);
+
+ $this->seed = mt_rand() . time();
+ }
+
+ /**
+ * Generates an ring image
+ *
+ * If a seed is given, the image will be based on that seed
+ *
+ * @param string $seed initialize the genrator with this string
+ * @param string $file if given, the image is saved at that path, otherwise is printed to browser
+ */
+ public function createImage($seed = '', $file = '')
+ {
+ if (!$seed) {
+ $seed = mt_rand() . time();
+ }
+ $this->seed = $seed;
+
+ // monochrome wanted?
+ if($this->ismono) {
+ $this->monocolor = array(
+ $this->rand(20,255),
+ $this->rand(20,255),
+ $this->rand(20,255)
+ );
+ } else {
+ $this->monocolor = null;
+ }
+
+ // create
+ $image = $this->createTransparentImage($this->fullsize, $this->fullsize);
+ $arcwidth = $this->fullsize;
+ for ($i = $this->rings; $i > 0; $i--) {
+ $this->drawRing($image, $arcwidth);
+ $arcwidth -= $this->ringwidth;
+ }
+
+ // resample for antialiasing
+ $out = $this->createTransparentImage($this->size, $this->size);
+ imagecopyresampled($out, $image, 0, 0, 0, 0, $this->size, $this->size, $this->fullsize, $this->fullsize);
+ if ($file) {
+ imagepng($out, $file);
+ } else {
+ header("Content-type: image/png");
+ imagepng($out);
+ }
+ imagedestroy($out);
+ imagedestroy($image);
+ }
+
+ /**
+ * When set to true a monochrome version is returned
+ *
+ * @param bool $ismono
+ */
+ public function setMono($ismono) {
+ $this->ismono = $ismono;
+ }
+
+ /**
+ * Generate number from seed
+ *
+ * Each call runs MD5 on the seed again
+ *
+ * @param int $min
+ * @param int $max
+ * @return int
+ */
+ protected function rand($min, $max)
+ {
+ $this->seed = md5($this->seed);
+ $rand = hexdec(substr($this->seed, 0, 8));
+ return ($rand % ($max - $min + 1)) + $min;
+ }
+
+ /**
+ * Drawas a single ring
+ *
+ * @param resource $image
+ * @param int $arcwidth outer width of the ring
+ */
+ protected function drawRing($image, $arcwidth)
+ {
+ $color = $this->randomColor($image);
+ $transparency = $this->transparentColor($image);
+
+ $start = $this->rand(20, 360);
+ $stop = $this->rand(20, 360);
+ if($stop < $start) list($start, $stop) = array($stop, $start);
+
+ imagefilledarc($image, $this->center, $this->center, $arcwidth, $arcwidth, $stop, $start, $color, IMG_ARC_PIE);
+ imagefilledellipse($image, $this->center, $this->center, $arcwidth - $this->ringwidth,
+ $arcwidth - $this->ringwidth, $transparency);
+
+ imagecolordeallocate($image, $color);
+ imagecolordeallocate($image, $transparency);
+ }
+
+ /**
+ * Allocate a transparent color
+ *
+ * @param resource $image
+ * @return int
+ */
+ protected function transparentColor($image)
+ {
+ return imagecolorallocatealpha($image, 0, 0, 0, 127);
+ }
+
+ /**
+ * Allocate a random color
+ *
+ * @param $image
+ * @return int
+ */
+ protected function randomColor($image)
+ {
+ if($this->ismono) {
+ return imagecolorallocatealpha($image, $this->monocolor[0], $this->monocolor[1], $this->monocolor[2], $this->rand(0, 96));
+ }
+ return imagecolorallocate($image, $this->rand(0, 255), $this->rand(0, 255), $this->rand(0, 255));
+ }
+
+ /**
+ * Create a transparent image
+ *
+ * @param int $width
+ * @param int $height
+ * @return resource
+ * @throws \Exception
+ */
+ protected function createTransparentImage($width, $height)
+ {
+ $image = @imagecreatetruecolor($width, $height);
+ if (!$image) {
+ throw new \Exception('Missing libgd support');
+ }
+ imagealphablending($image, false);
+ $transparency = $this->transparentColor($image);
+ imagefill($image, 0, 0, $transparency);
+ imagecolordeallocate($image, $transparency);
+ imagesavealpha($image, true);
+ return $image;
+ }
+
+}
diff --git a/platform/www/lib/plugins/farmer/DokuWikiFarmCore.php b/platform/www/lib/plugins/farmer/DokuWikiFarmCore.php
new file mode 100644
index 0000000..6c8547a
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/DokuWikiFarmCore.php
@@ -0,0 +1,375 @@
+<?php
+
+/**
+ * Core Manager for the Farm functionality
+ *
+ * This class is initialized before any other DokuWiki code runs. Therefore it is
+ * completely selfcontained and does not use any of DokuWiki's utility functions.
+ *
+ * It's registered as a global $FARMCORE variable but you should not interact with
+ * it directly. Instead use the Farmer plugin's helper component.
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+class DokuWikiFarmCore {
+ /**
+ * @var array The default config - changed by loadConfig
+ */
+ protected $config = array(
+ 'base' => array(
+ 'farmdir' => '',
+ 'farmhost' => '',
+ 'basedomain' => '',
+ ),
+ 'notfound' => array(
+ 'show' => 'farmer',
+ 'url' => ''
+ ),
+ 'inherit' => array(
+ 'main' => 1,
+ 'acronyms' => 1,
+ 'entities' => 1,
+ 'interwiki' => 1,
+ 'license' => 1,
+ 'mime' => 1,
+ 'scheme' => 1,
+ 'smileys' => 1,
+ 'wordblock' => 1,
+ 'users' => 0,
+ 'plugins' => 0,
+ 'userstyle' => 0,
+ 'userscript' => 0,
+ 'styleini' => 0
+ )
+ );
+
+ /** @var string|false The current animal, false for farmer */
+ protected $animal = false;
+ /** @var bool true if an animal was requested but was not found */
+ protected $notfound = false;
+ /** @var bool true if the current animal was requested by host */
+ protected $hostbased = false;
+
+ /**
+ * DokuWikiFarmCore constructor.
+ *
+ * This initializes the whole farm by loading the configuration and setting
+ * DOKU_CONF depending on the requested animal
+ */
+ public function __construct() {
+ $this->loadConfig();
+ if($this->config['base']['farmdir'] === '') return; // farm setup not complete
+ $this->config['base']['farmdir'] = rtrim($this->config['base']['farmdir'], '/').'/'; // trailing slash always
+ define('DOKU_FARMDIR', $this->config['base']['farmdir']);
+
+ // animal?
+ $this->detectAnimal();
+
+ // setup defines
+ define('DOKU_FARM_ANIMAL', $this->animal);
+ if($this->animal) {
+ define('DOKU_CONF', DOKU_FARMDIR . $this->animal . '/conf/');
+ } else {
+ define('DOKU_CONF', DOKU_INC . '/conf/');
+ }
+
+ $this->setupCascade();
+ $this->adjustCascade();
+ }
+
+ /**
+ * @return array the current farm configuration
+ */
+ public function getConfig() {
+ return $this->config;
+ }
+
+ /**
+ * @return false|string
+ */
+ public function getAnimal() {
+ return $this->animal;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isHostbased() {
+ return $this->hostbased;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function wasNotfound() {
+ return $this->notfound;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAnimalDataDir() {
+ return DOKU_FARMDIR . $this->getAnimal() . '/data/';
+ }
+
+ /**
+ * @return string
+ */
+ public function getAnimalBaseDir() {
+ if($this->isHostbased()) return '/';
+ return getBaseURL() . '!' . $this->getAnimal();
+ }
+
+ /**
+ * Detect the current animal
+ *
+ * Sets internal members $animal, $notfound and $hostbased
+ *
+ * This borrows form DokuWiki's inc/farm.php but does not support a default conf dir
+ */
+ protected function detectAnimal() {
+ $farmdir = $this->config['base']['farmdir'];
+ $farmhost = $this->config['base']['farmhost'];
+
+ // check if animal was set via parameter (rewrite or CLI)
+ $animal = '';
+ if(isset($_REQUEST['animal'])) $animal = $_REQUEST['animal'];
+ if('cli' == php_sapi_name() && isset($_SERVER['animal'])) $animal = $_SERVER['animal'];
+ if($animal) {
+ // check that $animal is a string and just a directory name and not a path
+ if(!is_string($animal) || strpbrk($animal, '\\/') !== false) {
+ $this->notfound = true;
+ return;
+ };
+ $animal = strtolower($animal);
+
+ // check if animal exists
+ if(is_dir("$farmdir/$animal/conf")) {
+ $this->animal = $animal;
+ return;
+ } else {
+ $this->notfound = true;
+ return;
+ }
+ }
+
+ // no host - no host based setup. if we're still here then it's the farmer
+ if(!isset($_SERVER['HTTP_HOST'])) return;
+
+ // is this the farmer?
+ if(strtolower($_SERVER['HTTP_HOST']) == $farmhost) {
+ return;
+ }
+
+ // still here? check for host based
+ $this->hostbased = true;
+ $possible = $this->getAnimalNamesForHost($_SERVER['HTTP_HOST']);
+ foreach($possible as $animal) {
+ if(is_dir("$farmdir/$animal/conf/")) {
+ $this->animal = $animal;
+ return;
+ }
+ }
+
+ // no hit
+ $this->notfound = true;
+ return;
+ }
+
+ /**
+ * Return a list of possible animal names for the given host
+ *
+ * @param string $host the HTTP_HOST header
+ * @return array
+ */
+ protected function getAnimalNamesForHost($host) {
+ $animals = array();
+ $parts = explode('.', implode('.', explode(':', rtrim($host, '.'))));
+ for($j = count($parts); $j > 0; $j--) {
+ // strip from the end
+ $animals[] = implode('.', array_slice($parts, 0, $j));
+ // strip from the end without host part
+ $animals[] = implode('.', array_slice($parts, 1, $j));
+ }
+ $animals = array_unique($animals);
+ $animals = array_filter($animals);
+ usort(
+ $animals,
+ // compare by length, then alphabet
+ function ($a, $b) {
+ $ret = strlen($b) - strlen($a);
+ if($ret != 0) return $ret;
+ return $a > $b;
+ }
+ );
+ return $animals;
+ }
+
+ /**
+ * This sets up the default farming config cascade
+ */
+ protected function setupCascade() {
+ global $config_cascade;
+ $config_cascade = array(
+ 'main' => array(
+ 'default' => array(DOKU_INC . 'conf/dokuwiki.php',),
+ 'local' => array(DOKU_CONF . 'local.php',),
+ 'protected' => array(DOKU_CONF . 'local.protected.php',),
+ ),
+ 'acronyms' => array(
+ 'default' => array(DOKU_INC . 'conf/acronyms.conf',),
+ 'local' => array(DOKU_CONF . 'acronyms.local.conf',),
+ ),
+ 'entities' => array(
+ 'default' => array(DOKU_INC . 'conf/entities.conf',),
+ 'local' => array(DOKU_CONF . 'entities.local.conf',),
+ ),
+ 'interwiki' => array(
+ 'default' => array(DOKU_INC . 'conf/interwiki.conf',),
+ 'local' => array(DOKU_CONF . 'interwiki.local.conf',),
+ ),
+ 'license' => array(
+ 'default' => array(DOKU_INC . 'conf/license.php',),
+ 'local' => array(DOKU_CONF . 'license.local.php',),
+ ),
+ 'manifest' => array(
+ 'default' => array(DOKU_INC . 'conf/manifest.json',),
+ 'local' => array(DOKU_CONF . 'manifest.local.json',),
+ ),
+ 'mediameta' => array(
+ 'default' => array(DOKU_INC . 'conf/mediameta.php',),
+ 'local' => array(DOKU_CONF . 'mediameta.local.php',),
+ ),
+ 'mime' => array(
+ 'default' => array(DOKU_INC . 'conf/mime.conf',),
+ 'local' => array(DOKU_CONF . 'mime.local.conf',),
+ ),
+ 'scheme' => array(
+ 'default' => array(DOKU_INC . 'conf/scheme.conf',),
+ 'local' => array(DOKU_CONF . 'scheme.local.conf',),
+ ),
+ 'smileys' => array(
+ 'default' => array(DOKU_INC . 'conf/smileys.conf',),
+ 'local' => array(DOKU_CONF . 'smileys.local.conf',),
+ ),
+ 'wordblock' => array(
+ 'default' => array(DOKU_INC . 'conf/wordblock.conf',),
+ 'local' => array(DOKU_CONF . 'wordblock.local.conf',),
+ ),
+ 'acl' => array(
+ 'default' => DOKU_CONF . 'acl.auth.php',
+ ),
+ 'plainauth.users' => array(
+ 'default' => DOKU_CONF . 'users.auth.php',
+ ),
+ 'plugins' => array(
+ 'default' => array(DOKU_INC . 'conf/plugins.php',),
+ 'local' => array(DOKU_CONF . 'plugins.local.php',),
+ 'protected' => array(
+ DOKU_INC . 'conf/plugins.required.php',
+ DOKU_CONF . 'plugins.protected.php',
+ ),
+ ),
+ 'userstyle' => array(
+ 'screen' => array(DOKU_CONF . 'userstyle.css', DOKU_CONF . 'userstyle.less',),
+ 'print' => array(DOKU_CONF . 'userprint.css', DOKU_CONF . 'userprint.less',),
+ 'feed' => array(DOKU_CONF . 'userfeed.css', DOKU_CONF . 'userfeed.less',),
+ 'all' => array(DOKU_CONF . 'userall.css', DOKU_CONF . 'userall.less',),
+ ),
+ 'userscript' => array(
+ 'default' => array(DOKU_CONF . 'userscript.js',),
+ ),
+ 'styleini' => array(
+ 'default' => array(DOKU_INC . 'lib/tpl/%TEMPLATE%/' . 'style.ini'),
+ 'local' => array(DOKU_CONF . 'tpl/%TEMPLATE%/' . 'style.ini')
+ ),
+ );
+ }
+
+ /**
+ * This adds additional files to the config cascade based on the inheritence settings
+ *
+ * These are only added for animals, not the farmer
+ */
+ protected function adjustCascade() {
+ // nothing to do when on the farmer:
+ if(!$this->animal) return;
+
+ global $config_cascade;
+ foreach($this->config['inherit'] as $key => $val) {
+ if(!$val) continue;
+
+ // prepare what is to append or prepend
+ $append = array();
+ $prepend = array();
+ if($key == 'main') {
+ $append = array(
+ 'default' => array(DOKU_INC . 'conf/local.php'),
+ 'protected' => array(DOKU_INC . 'lib/plugins/farmer/includes/config.php')
+ );
+ } elseif($key == 'license') {
+ $append = array('default' => array(DOKU_INC . 'conf/' . $key . '.local.php'));
+ } elseif($key == 'userscript') {
+ $prepend = array('default' => array(DOKU_INC . 'conf/userscript.js'));
+ } elseif($key == 'userstyle') {
+ $prepend = array(
+ 'screen' => array(DOKU_INC . 'conf/userstyle.css', DOKU_INC . 'conf/userstyle.less',),
+ 'print' => array(DOKU_INC . 'conf/userprint.css', DOKU_INC . 'conf/userprint.less',),
+ 'feed' => array(DOKU_INC . 'conf/userfeed.css', DOKU_INC . 'conf/userfeed.less',),
+ 'all' => array(DOKU_INC . 'conf/userall.css', DOKU_INC . 'conf/userall.less',),
+ );
+ } elseif ($key == 'styleini') {
+ $append = array(
+ 'local' => array(
+ DOKU_INC . 'conf/tpl/%TEMPLATE%/style.ini'
+ )
+ );
+ } elseif($key == 'users') {
+ $config_cascade['plainauth.users']['protected'] = DOKU_INC . 'conf/users.auth.php';
+ } elseif($key == 'plugins') {
+ $append = array('default' => array(DOKU_INC . 'conf/plugins.local.php'));
+ } else {
+ $append = array('default' => array(DOKU_INC . 'conf/' . $key . '.local.conf'));
+ }
+
+ // add to cascade
+ foreach($prepend as $section => $data) {
+ $config_cascade[$key][$section] = array_merge($data, $config_cascade[$key][$section]);
+ }
+ foreach($append as $section => $data) {
+ $config_cascade[$key][$section] = array_merge($config_cascade[$key][$section], $data);
+ }
+ }
+
+ // add plugin overrides
+ $config_cascade['plugins']['protected'][] = DOKU_INC . 'lib/plugins/farmer/includes/plugins.php';
+ }
+
+ /**
+ * Loads the farm config
+ */
+ protected function loadConfig() {
+ $ini = DOKU_INC . 'conf/farm.ini';
+ if(!file_exists($ini)) return;
+ $config = parse_ini_file($ini, true);
+ foreach(array_keys($this->config) as $section) {
+ if(isset($config[$section])) {
+ $this->config[$section] = array_merge(
+ $this->config[$section],
+ $config[$section]
+ );
+ }
+ }
+
+ $this->config['base']['farmdir'] = trim($this->config['base']['farmdir']);
+ $this->config['base']['farmhost'] = strtolower(trim($this->config['base']['farmhost']));
+ }
+
+}
+
+// initialize it globally
+if(!defined('DOKU_UNITTEST')) {
+ global $FARMCORE;
+ $FARMCORE = new DokuWikiFarmCore();
+}
diff --git a/platform/www/lib/plugins/farmer/README b/platform/www/lib/plugins/farmer/README
new file mode 100644
index 0000000..0515a92
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/README
@@ -0,0 +1,27 @@
+farmer Plugin for DokuWiki
+
+A plugin to help with creating and administring wiki farm animals
+
+All documentation for this plugin can be found at
+https://dokuwiki.org/plugin:farmer
+
+If you install this plugin manually, make sure it is installed in
+lib/plugins/farmer/ - if the folder is called different it
+will not work!
+
+Please refer to http://www.dokuwiki.org/plugins for additional info
+on how to install plugins in DokuWiki.
+
+----
+Copyright (C) Michael Große, Andreas Gohr <dokuwiki@cosmocode.de>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; version 2 of the License
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+See the COPYING file in your DokuWiki folder for details
diff --git a/platform/www/lib/plugins/farmer/_animal/conf/acl.auth.php b/platform/www/lib/plugins/farmer/_animal/conf/acl.auth.php
new file mode 100644
index 0000000..14344d7
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/conf/acl.auth.php
@@ -0,0 +1,21 @@
+# acl.auth.php
+# <?php exit()?>
+# Don't modify the lines above
+#
+# Access Control Lists
+#
+# Editing this file by hand shouldn't be necessary. Use the ACL
+# Manager interface instead.
+#
+# If your auth backend allows special char like spaces in groups
+# or user names you need to urlencode them (only chars <128, leave
+# UTF-8 multibyte chars as is)
+#
+# none 0
+# read 1
+# edit 2
+# create 4
+# upload 8
+# delete 16
+
+* @ALL 8
diff --git a/platform/www/lib/plugins/farmer/_animal/conf/local.php b/platform/www/lib/plugins/farmer/_animal/conf/local.php
new file mode 100644
index 0000000..109a33a
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/conf/local.php
@@ -0,0 +1,6 @@
+<?php
+/**
+ * Minimal local config
+ */
+$conf['useacl'] = 1;
+$conf['superuser'] = '@admin';
diff --git a/platform/www/lib/plugins/farmer/_animal/data/_dummy b/platform/www/lib/plugins/farmer/_animal/data/_dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/_dummy
diff --git a/platform/www/lib/plugins/farmer/_animal/data/attic/_dummy b/platform/www/lib/plugins/farmer/_animal/data/attic/_dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/attic/_dummy
diff --git a/platform/www/lib/plugins/farmer/_animal/data/cache/_dummy b/platform/www/lib/plugins/farmer/_animal/data/cache/_dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/cache/_dummy
diff --git a/platform/www/lib/plugins/farmer/_animal/data/index/_dummy b/platform/www/lib/plugins/farmer/_animal/data/index/_dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/index/_dummy
diff --git a/platform/www/lib/plugins/farmer/_animal/data/locks/_dummy b/platform/www/lib/plugins/farmer/_animal/data/locks/_dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/locks/_dummy
diff --git a/platform/www/lib/plugins/farmer/_animal/data/log/_dummy b/platform/www/lib/plugins/farmer/_animal/data/log/_dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/log/_dummy
diff --git a/platform/www/lib/plugins/farmer/_animal/data/media/wiki/dokuwiki-128.png b/platform/www/lib/plugins/farmer/_animal/data/media/wiki/dokuwiki-128.png
new file mode 100644
index 0000000..b2306ac
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/media/wiki/dokuwiki-128.png
Binary files differ
diff --git a/platform/www/lib/plugins/farmer/_animal/data/media_attic/_dummy b/platform/www/lib/plugins/farmer/_animal/data/media_attic/_dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/media_attic/_dummy
diff --git a/platform/www/lib/plugins/farmer/_animal/data/media_meta/_dummy b/platform/www/lib/plugins/farmer/_animal/data/media_meta/_dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/media_meta/_dummy
diff --git a/platform/www/lib/plugins/farmer/_animal/data/meta/_dummy b/platform/www/lib/plugins/farmer/_animal/data/meta/_dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/meta/_dummy
diff --git a/platform/www/lib/plugins/farmer/_animal/data/pages/wiki/dokuwiki.txt b/platform/www/lib/plugins/farmer/_animal/data/pages/wiki/dokuwiki.txt
new file mode 100644
index 0000000..e6fac5b
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/pages/wiki/dokuwiki.txt
@@ -0,0 +1,64 @@
+====== DokuWiki ======
+
+[[doku>wiki:dokuwiki|{{wiki:dokuwiki-128.png }}]] DokuWiki is a standards compliant, simple to use [[wp>Wiki]], mainly aimed at creating documentation of any kind. It is targeted at developer teams, workgroups and small companies. It has a simple but powerful [[wiki:syntax]] which makes sure the datafiles remain readable outside the Wiki and eases the creation of structured texts. All data is stored in plain text files -- no database is required.
+
+Read the [[doku>manual|DokuWiki Manual]] to unleash the full power of DokuWiki.
+
+===== Download =====
+
+DokuWiki is available at http://www.splitbrain.org/go/dokuwiki
+
+
+===== Read More =====
+
+All documentation and additional information besides the [[syntax|syntax description]] is maintained in the DokuWiki at [[doku>|www.dokuwiki.org]].
+
+**About DokuWiki**
+
+ * [[doku>features|A feature list]] :!:
+ * [[doku>users|Happy Users]]
+ * [[doku>press|Who wrote about it]]
+ * [[doku>blogroll|What Bloggers think]]
+ * [[http://www.wikimatrix.org/show/DokuWiki|Compare it with other wiki software]]
+
+**Installing DokuWiki**
+
+ * [[doku>requirements|System Requirements]]
+ * [[http://www.splitbrain.org/go/dokuwiki|Download DokuWiki]] :!:
+ * [[doku>changes|Change Log]]
+ * [[doku>Install|How to install or upgrade]] :!:
+ * [[doku>config|Configuration]]
+
+**Using DokuWiki**
+
+ * [[doku>syntax|Wiki Syntax]]
+ * [[doku>manual|The manual]] :!:
+ * [[doku>FAQ|Frequently Asked Questions (FAQ)]]
+ * [[doku>glossary|Glossary]]
+ * [[http://search.dokuwiki.org|Search for DokuWiki help and documentation]]
+
+**Customizing DokuWiki**
+
+ * [[doku>tips|Tips and Tricks]]
+ * [[doku>Template|How to create and use templates]]
+ * [[doku>plugins|Installing plugins]]
+ * [[doku>development|Development Resources]]
+
+**DokuWiki Feedback and Community**
+
+ * [[doku>newsletter|Subscribe to the newsletter]] :!:
+ * [[doku>mailinglist|Join the mailing list]]
+ * [[http://forum.dokuwiki.org|Check out the user forum]]
+ * [[doku>irc|Talk to other users in the IRC channel]]
+ * [[http://bugs.splitbrain.org/index.php?project=1|Submit bugs and feature wishes]]
+ * [[http://www.wikimatrix.org/forum/viewforum.php?id=10|Share your experiences in the WikiMatrix forum]]
+ * [[doku>thanks|Some humble thanks]]
+
+
+===== Copyright =====
+
+2004-2010 (c) Andreas Gohr <andi@splitbrain.org>((Please do not contact me for help and support -- use the [[doku>mailinglist]] or [[http://forum.dokuwiki.org|forum]] instead)) and the DokuWiki Community
+
+The DokuWiki engine is licensed under [[http://www.gnu.org/licenses/gpl.html|GNU General Public License]] Version 2. If you use DokuWiki in your company, consider [[doku>donate|donating]] a few bucks ;-).
+
+Not sure what this means? See the [[doku>faq:license|FAQ on the Licenses]].
diff --git a/platform/www/lib/plugins/farmer/_animal/data/pages/wiki/syntax.txt b/platform/www/lib/plugins/farmer/_animal/data/pages/wiki/syntax.txt
new file mode 100644
index 0000000..dd3154e
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/pages/wiki/syntax.txt
@@ -0,0 +1,486 @@
+====== Formatting Syntax ======
+
+[[doku>DokuWiki]] supports some simple markup language, which tries to make the datafiles to be as readable as possible. This page contains all possible syntax you may use when editing the pages. Simply have a look at the source of this page by pressing the //Edit this page// button at the top or bottom of the page. If you want to try something, just use the [[playground:playground|playground]] page. The simpler markup is easily accessible via [[doku>toolbar|quickbuttons]], too.
+
+===== Basic Text Formatting =====
+
+DokuWiki supports **bold**, //italic//, __underlined__ and ''monospaced'' texts. Of course you can **__//''combine''//__** all these.
+
+ DokuWiki supports **bold**, //italic//, __underlined__ and ''monospaced'' texts.
+ Of course you can **__//''combine''//__** all these.
+
+You can use <sub>subscript</sub> and <sup>superscript</sup>, too.
+
+ You can use <sub>subscript</sub> and <sup>superscript</sup>, too.
+
+You can mark something as <del>deleted</del> as well.
+
+ You can mark something as <del>deleted</del> as well.
+
+**Paragraphs** are created from blank lines. If you want to **force a newline** without a paragraph, you can use two backslashes followed by a whitespace or the end of line.
+
+This is some text with some linebreaks\\ Note that the
+two backslashes are only recognized at the end of a line\\
+or followed by\\ a whitespace \\this happens without it.
+
+ This is some text with some linebreaks\\ Note that the
+ two backslashes are only recognized at the end of a line\\
+ or followed by\\ a whitespace \\this happens without it.
+
+You should use forced newlines only if really needed.
+
+===== Links =====
+
+DokuWiki supports multiple ways of creating links.
+
+==== External ====
+
+External links are recognized automagically: http://www.google.com or simply www.google.com - You can set the link text as well: [[http://www.google.com|This Link points to google]]. Email addresses like this one: <andi@splitbrain.org> are recognized, too.
+
+ DokuWiki supports multiple ways of creating links. External links are recognized
+ automagically: http://www.google.com or simply www.google.com - You can set
+ link text as well: [[http://www.google.com|This Link points to google]]. Email
+ addresses like this one: <andi@splitbrain.org> are recognized, too.
+
+==== Internal ====
+
+Internal links are created by using square brackets. You can either just give a [[pagename]] or use an additional [[pagename|link text]].
+
+ Internal links are created by using square brackets. You can either just give
+ a [[pagename]] or use an additional [[pagename|link text]].
+
+[[doku>pagename|Wiki pagenames]] are converted to lowercase automatically, special characters are not allowed.
+
+You can use [[some:namespaces]] by using a colon in the pagename.
+
+ You can use [[some:namespaces]] by using a colon in the pagename.
+
+For details about namespaces see [[doku>namespaces]].
+
+Linking to a specific section is possible, too. Just add the section name behind a hash character as known from HTML. This links to [[syntax#internal|this Section]].
+
+ This links to [[syntax#internal|this Section]].
+
+Notes:
+
+ * Links to [[syntax|existing pages]] are shown in a different style from [[nonexisting]] ones.
+ * DokuWiki does not use [[wp>CamelCase]] to automatically create links by default, but this behavior can be enabled in the [[doku>config]] file. Hint: If DokuWiki is a link, then it's enabled.
+ * When a section's heading is changed, its bookmark changes, too. So don't rely on section linking too much.
+
+==== Interwiki ====
+
+DokuWiki supports [[doku>Interwiki]] links. These are quick links to other Wikis. For example this is a link to Wikipedia's page about Wikis: [[wp>Wiki]].
+
+ DokuWiki supports [[doku>Interwiki]] links. These are quick links to other Wikis.
+ For example this is a link to Wikipedia's page about Wikis: [[wp>Wiki]].
+
+==== Windows Shares ====
+
+Windows shares like [[\\server\share|this]] are recognized, too. Please note that these only make sense in a homogeneous user group like a corporate [[wp>Intranet]].
+
+ Windows Shares like [[\\server\share|this]] are recognized, too.
+
+Notes:
+
+ * For security reasons direct browsing of windows shares only works in Microsoft Internet Explorer per default (and only in the "local zone").
+ * For Mozilla and Firefox it can be enabled through different workaround mentioned in the [[http://kb.mozillazine.org/Links_to_local_pages_do_not_work|Mozilla Knowledge Base]]. However, there will still be a JavaScript warning about trying to open a Windows Share. To remove this warning (for all users), put the following line in ''conf/local.protected.php'':
+
+ $lang['js']['nosmblinks'] = '';
+
+==== Image Links ====
+
+You can also use an image to link to another internal or external page by combining the syntax for links and [[#images_and_other_files|images]] (see below) like this:
+
+ [[http://www.php.net|{{wiki:dokuwiki-128.png}}]]
+
+[[http://www.php.net|{{wiki:dokuwiki-128.png}}]]
+
+Please note: The image formatting is the only formatting syntax accepted in link names.
+
+The whole [[#images_and_other_files|image]] and [[#links|link]] syntax is supported (including image resizing, internal and external images and URLs and interwiki links).
+
+===== Footnotes =====
+
+You can add footnotes ((This is a footnote)) by using double parentheses.
+
+ You can add footnotes ((This is a footnote)) by using double parentheses.
+
+===== Sectioning =====
+
+You can use up to five different levels of headlines to structure your content. If you have more than three headlines, a table of contents is generated automatically -- this can be disabled by including the string ''<nowiki>~~NOTOC~~</nowiki>'' in the document.
+
+==== Headline Level 3 ====
+=== Headline Level 4 ===
+== Headline Level 5 ==
+
+ ==== Headline Level 3 ====
+ === Headline Level 4 ===
+ == Headline Level 5 ==
+
+By using four or more dashes, you can make a horizontal line:
+
+----
+
+===== Images and Other Files =====
+
+You can include external and internal [[doku>images]] with curly brackets. Optionally you can specify the size of them.
+
+Real size: {{wiki:dokuwiki-128.png}}
+
+Resize to given width: {{wiki:dokuwiki-128.png?50}}
+
+Resize to given width and height((when the aspect ratio of the given width and height doesn't match that of the image, it will be cropped to the new ratio before resizing)): {{wiki:dokuwiki-128.png?200x50}}
+
+Resized external image: {{http://de3.php.net/images/php.gif?200x50}}
+
+ Real size: {{wiki:dokuwiki-128.png}}
+ Resize to given width: {{wiki:dokuwiki-128.png?50}}
+ Resize to given width and height: {{wiki:dokuwiki-128.png?200x50}}
+ Resized external image: {{http://de3.php.net/images/php.gif?200x50}}
+
+
+By using left or right whitespaces you can choose the alignment.
+
+{{ wiki:dokuwiki-128.png}}
+
+{{wiki:dokuwiki-128.png }}
+
+{{ wiki:dokuwiki-128.png }}
+
+ {{ wiki:dokuwiki-128.png}}
+ {{wiki:dokuwiki-128.png }}
+ {{ wiki:dokuwiki-128.png }}
+
+Of course, you can add a title (displayed as a tooltip by most browsers), too.
+
+{{ wiki:dokuwiki-128.png |This is the caption}}
+
+ {{ wiki:dokuwiki-128.png |This is the caption}}
+
+If you specify a filename (external or internal) that is not an image (''gif, jpeg, png''), then it will be displayed as a link instead.
+
+For linking an image to another page see [[#Image Links]] above.
+
+===== Lists =====
+
+Dokuwiki supports ordered and unordered lists. To create a list item, indent your text by two spaces and use a ''*'' for unordered lists or a ''-'' for ordered ones.
+
+ * This is a list
+ * The second item
+ * You may have different levels
+ * Another item
+
+ - The same list but ordered
+ - Another item
+ - Just use indention for deeper levels
+ - That's it
+
+<code>
+ * This is a list
+ * The second item
+ * You may have different levels
+ * Another item
+
+ - The same list but ordered
+ - Another item
+ - Just use indention for deeper levels
+ - That's it
+</code>
+
+Also take a look at the [[doku>faq:lists|FAQ on list items]].
+
+===== Text Conversions =====
+
+DokuWiki can convert certain pre-defined characters or strings into images or other text or HTML.
+
+The text to image conversion is mainly done for smileys. And the text to HTML conversion is used for typography replacements, but can be configured to use other HTML as well.
+
+==== Text to Image Conversions ====
+
+DokuWiki converts commonly used [[wp>emoticon]]s to their graphical equivalents. Those [[doku>Smileys]] and other images can be configured and extended. Here is an overview of Smileys included in DokuWiki:
+
+ * 8-) %% 8-) %%
+ * 8-O %% 8-O %%
+ * :-( %% :-( %%
+ * :-) %% :-) %%
+ * =) %% =) %%
+ * :-/ %% :-/ %%
+ * :-\ %% :-\ %%
+ * :-? %% :-? %%
+ * :-D %% :-D %%
+ * :-P %% :-P %%
+ * :-O %% :-O %%
+ * :-X %% :-X %%
+ * :-| %% :-| %%
+ * ;-) %% ;-) %%
+ * ^_^ %% ^_^ %%
+ * :?: %% :?: %%
+ * :!: %% :!: %%
+ * LOL %% LOL %%
+ * FIXME %% FIXME %%
+ * DELETEME %% DELETEME %%
+
+==== Text to HTML Conversions ====
+
+Typography: [[DokuWiki]] can convert simple text characters to their typographically correct entities. Here is an example of recognized characters.
+
+-> <- <-> => <= <=> >> << -- --- 640x480 (c) (tm) (r)
+"He thought 'It's a man's world'..."
+
+<code>
+-> <- <-> => <= <=> >> << -- --- 640x480 (c) (tm) (r)
+"He thought 'It's a man's world'..."
+</code>
+
+The same can be done to produce any kind of HTML, it just needs to be added to the [[doku>entities|pattern file]].
+
+There are three exceptions which do not come from that pattern file: multiplication entity (640x480), 'single' and "double quotes". They can be turned off through a [[doku>config:typography|config option]].
+
+===== Quoting =====
+
+Some times you want to mark some text to show it's a reply or comment. You can use the following syntax:
+
+ I think we should do it
+
+ > No we shouldn't
+
+ >> Well, I say we should
+
+ > Really?
+
+ >> Yes!
+
+ >>> Then lets do it!
+
+I think we should do it
+
+> No we shouldn't
+
+>> Well, I say we should
+
+> Really?
+
+>> Yes!
+
+>>> Then lets do it!
+
+===== Tables =====
+
+DokuWiki supports a simple syntax to create tables.
+
+^ Heading 1 ^ Heading 2 ^ Heading 3 ^
+| Row 1 Col 1 | Row 1 Col 2 | Row 1 Col 3 |
+| Row 2 Col 1 | some colspan (note the double pipe) ||
+| Row 3 Col 1 | Row 3 Col 2 | Row 3 Col 3 |
+
+Table rows have to start and end with a ''|'' for normal rows or a ''^'' for headers.
+
+ ^ Heading 1 ^ Heading 2 ^ Heading 3 ^
+ | Row 1 Col 1 | Row 1 Col 2 | Row 1 Col 3 |
+ | Row 2 Col 1 | some colspan (note the double pipe) ||
+ | Row 3 Col 1 | Row 3 Col 2 | Row 3 Col 3 |
+
+To connect cells horizontally, just make the next cell completely empty as shown above. Be sure to have always the same amount of cell separators!
+
+Vertical tableheaders are possible, too.
+
+| ^ Heading 1 ^ Heading 2 ^
+^ Heading 3 | Row 1 Col 2 | Row 1 Col 3 |
+^ Heading 4 | no colspan this time | |
+^ Heading 5 | Row 2 Col 2 | Row 2 Col 3 |
+
+As you can see, it's the cell separator before a cell which decides about the formatting:
+
+ | ^ Heading 1 ^ Heading 2 ^
+ ^ Heading 3 | Row 1 Col 2 | Row 1 Col 3 |
+ ^ Heading 4 | no colspan this time | |
+ ^ Heading 5 | Row 2 Col 2 | Row 2 Col 3 |
+
+You can have rowspans (vertically connected cells) by adding '':::'' into the cells below the one to which they should connect.
+
+^ Heading 1 ^ Heading 2 ^ Heading 3 ^
+| Row 1 Col 1 | this cell spans vertically | Row 1 Col 3 |
+| Row 2 Col 1 | ::: | Row 2 Col 3 |
+| Row 3 Col 1 | ::: | Row 2 Col 3 |
+
+Apart from the rowspan syntax those cells should not contain anything else.
+
+ ^ Heading 1 ^ Heading 2 ^ Heading 3 ^
+ | Row 1 Col 1 | this cell spans vertically | Row 1 Col 3 |
+ | Row 2 Col 1 | ::: | Row 2 Col 3 |
+ | Row 3 Col 1 | ::: | Row 2 Col 3 |
+
+You can align the table contents, too. Just add at least two whitespaces at the opposite end of your text: Add two spaces on the left to align right, two spaces on the right to align left and two spaces at least at both ends for centered text.
+
+^ Table with alignment ^^^
+| right| center |left |
+|left | right| center |
+| xxxxxxxxxxxx | xxxxxxxxxxxx | xxxxxxxxxxxx |
+
+This is how it looks in the source:
+
+ ^ Table with alignment ^^^
+ | right| center |left |
+ |left | right| center |
+ | xxxxxxxxxxxx | xxxxxxxxxxxx | xxxxxxxxxxxx |
+
+Note: Vertical alignment is not supported.
+
+===== No Formatting =====
+
+If you need to display text exactly like it is typed (without any formatting), enclose the area either with ''%%<nowiki>%%'' tags or even simpler, with double percent signs ''<nowiki>%%</nowiki>''.
+
+<nowiki>
+This is some text which contains addresses like this: http://www.splitbrain.org and **formatting**, but nothing is done with it.
+</nowiki>
+The same is true for %%//__this__ text// with a smiley ;-)%%.
+
+ <nowiki>
+ This is some text which contains addresses like this: http://www.splitbrain.org and **formatting**, but nothing is done with it.
+ </nowiki>
+ The same is true for %%//__this__ text// with a smiley ;-)%%.
+
+===== Code Blocks =====
+
+You can include code blocks into your documents by either indenting them by at least two spaces (like used for the previous examples) or by using the tags ''%%<code>%%'' or ''%%<file>%%''.
+
+ This is text is indented by two spaces.
+
+<code>
+This is preformatted code all spaces are preserved: like <-this
+</code>
+
+<file>
+This is pretty much the same, but you could use it to show that you quoted a file.
+</file>
+
+Those blocks were created by this source:
+
+ This is text is indented by two spaces.
+
+ <code>
+ This is preformatted code all spaces are preserved: like <-this
+ </code>
+
+ <file>
+ This is pretty much the same, but you could use it to show that you quoted a file.
+ </file>
+
+==== Syntax Highlighting ====
+
+[[wiki:DokuWiki]] can highlight sourcecode, which makes it easier to read. It uses the [[http://qbnz.com/highlighter/|GeSHi]] Generic Syntax Highlighter -- so any language supported by GeSHi is supported. The syntax uses the same code and file blocks described in the previous section, but this time the name of the language syntax to be highlighted is included inside the tag, e.g. ''<nowiki><code java></nowiki>'' or ''<nowiki><file java></nowiki>''.
+
+<code java>
+/**
+ * The HelloWorldApp class implements an application that
+ * simply displays "Hello World!" to the standard output.
+ */
+class HelloWorldApp {
+ public static void main(String[] args) {
+ System.out.println("Hello World!"); //Display the string.
+ }
+}
+</code>
+
+The following language strings are currently recognized: //4cs, 6502acme, 6502kickass, 6502tasm, 68000devpac, abap, actionscript-french, actionscript, actionscript3, ada, algol68, apache, applescript, asm, asp, autoconf, autohotkey, autoit, avisynth, awk, bascomavr, bash, basic4gl, bf, bibtex, blitzbasic, bnf, boo, c, c_loadrunner, c_mac, caddcl, cadlisp, cfdg, cfm, chaiscript, cil, clojure, cmake, cobol, coffeescript, cpp, cpp-qt, csharp, css, cuesheet, d, dcs, delphi, diff, div, dos, dot, e, epc, ecmascript, eiffel, email, erlang, euphoria, f1, falcon, fo, fortran, freebasic, fsharp, gambas, genero, genie, gdb, glsl, gml, gnuplot, go, groovy, gettext, gwbasic, haskell, hicest, hq9plus, html, html5, icon, idl, ini, inno, intercal, io, j, java5, java, javascript, jquery, kixtart, klonec, klonecpp, latex, lb, lisp, llvm, locobasic, logtalk, lolcode, lotusformulas, lotusscript, lscript, lsl2, lua, m68k, magiksf, make, mapbasic, matlab, mirc, modula2, modula3, mmix, mpasm, mxml, mysql, newlisp, nsis, oberon2, objc, objeck, ocaml-brief, ocaml, oobas, oracle8, oracle11, oxygene, oz, pascal, pcre, perl, perl6, per, pf, php-brief, php, pike, pic16, pixelbender, pli, plsql, postgresql, povray, powerbuilder, powershell, proftpd, progress, prolog, properties, providex, purebasic, pycon, python, q, qbasic, rails, rebol, reg, robots, rpmspec, rsplus, ruby, sas, scala, scheme, scilab, sdlbasic, smalltalk, smarty, sql, systemverilog, tcl, teraterm, text, thinbasic, tsql, typoscript, unicon, uscript, vala, vbnet, vb, verilog, vhdl, vim, visualfoxpro, visualprolog, whitespace, winbatch, whois, xbasic, xml, xorg_conf, xpp, yaml, z80, zxbasic//
+
+==== Downloadable Code Blocks ====
+
+When you use the ''%%<code>%%'' or ''%%<file>%%'' syntax as above, you might want to make the shown code available for download as well. You can do this by specifying a file name after language code like this:
+
+<code>
+<file php myexample.php>
+<?php echo "hello world!"; ?>
+</file>
+</code>
+
+<file php myexample.php>
+<?php echo "hello world!"; ?>
+</file>
+
+If you don't want any highlighting but want a downloadable file, specify a dash (''-'') as the language code: ''%%<code - myfile.foo>%%''.
+
+
+===== Embedding HTML and PHP =====
+
+You can embed raw HTML or PHP code into your documents by using the ''%%<html>%%'' or ''%%<php>%%'' tags. (Use uppercase tags if you need to enclose block level elements.)
+
+HTML example:
+
+<code>
+<html>
+This is some <span style="color:red;font-size:150%;">inline HTML</span>
+</html>
+<HTML>
+<p style="border:2px dashed red;">And this is some block HTML</p>
+</HTML>
+</code>
+
+<html>
+This is some <span style="color:red;font-size:150%;">inline HTML</span>
+</html>
+<HTML>
+<p style="border:2px dashed red;">And this is some block HTML</p>
+</HTML>
+
+PHP example:
+
+<code>
+<php>
+echo 'A logo generated by PHP:';
+echo '<img src="' . $_SERVER['PHP_SELF'] . '?=' . php_logo_guid() . '" alt="PHP Logo !" />';
+echo '(generated inline HTML)';
+</php>
+<PHP>
+echo '<table class="inline"><tr><td>The same, but inside a block level element:</td>';
+echo '<td><img src="' . $_SERVER['PHP_SELF'] . '?=' . php_logo_guid() . '" alt="PHP Logo !" /></td>';
+echo '</tr></table>';
+</PHP>
+</code>
+
+<php>
+echo 'A logo generated by PHP:';
+echo '<img src="' . $_SERVER['PHP_SELF'] . '?=' . php_logo_guid() . '" alt="PHP Logo !" />';
+echo '(inline HTML)';
+</php>
+<PHP>
+echo '<table class="inline"><tr><td>The same, but inside a block level element:</td>';
+echo '<td><img src="' . $_SERVER['PHP_SELF'] . '?=' . php_logo_guid() . '" alt="PHP Logo !" /></td>';
+echo '</tr></table>';
+</PHP>
+
+**Please Note**: HTML and PHP embedding is disabled by default in the configuration. If disabled, the code is displayed instead of executed.
+
+===== RSS/ATOM Feed Aggregation =====
+[[DokuWiki]] can integrate data from external XML feeds. For parsing the XML feeds, [[http://simplepie.org/|SimplePie]] is used. All formats understood by SimplePie can be used in DokuWiki as well. You can influence the rendering by multiple additional space separated parameters:
+
+^ Parameter ^ Description ^
+| any number | will be used as maximum number items to show, defaults to 8 |
+| reverse | display the last items in the feed first |
+| author | show item authors names |
+| date | show item dates |
+| description| show the item description. If [[doku>config:htmlok|HTML]] is disabled all tags will be stripped |
+| //n//[dhm] | refresh period, where d=days, h=hours, m=minutes. (e.g. 12h = 12 hours). |
+
+The refresh period defaults to 4 hours. Any value below 10 minutes will be treated as 10 minutes. [[wiki:DokuWiki]] will generally try to supply a cached version of a page, obviously this is inappropriate when the page contains dynamic external content. The parameter tells [[wiki:DokuWiki]] to re-render the page if it is more than //refresh period// since the page was last rendered.
+
+**Example:**
+
+ {{rss>http://slashdot.org/index.rss 5 author date 1h }}
+
+{{rss>http://slashdot.org/index.rss 5 author date 1h }}
+
+
+===== Control Macros =====
+
+Some syntax influences how DokuWiki renders a page without creating any output it self. The following control macros are availble:
+
+^ Macro ^ Description |
+| %%~~NOTOC~~%% | If this macro is found on the page, no table of contents will be created |
+| %%~~NOCACHE~~%% | DokuWiki caches all output by default. Sometimes this might not be wanted (eg. when the %%<php>%% syntax above is used), adding this macro will force DokuWiki to rerender a page on every call |
+
+===== Syntax Plugins =====
+
+DokuWiki's syntax can be extended by [[doku>plugins|Plugins]]. How the installed plugins are used is described on their appropriate description pages. The following syntax plugins are available in this particular DokuWiki installation:
+
+~~INFO:syntaxplugins~~
diff --git a/platform/www/lib/plugins/farmer/_animal/data/tmp/_dummy b/platform/www/lib/plugins/farmer/_animal/data/tmp/_dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_animal/data/tmp/_dummy
diff --git a/platform/www/lib/plugins/farmer/_test/core.test.php b/platform/www/lib/plugins/farmer/_test/core.test.php
new file mode 100644
index 0000000..d6a1dab
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_test/core.test.php
@@ -0,0 +1,40 @@
+<?php
+namespace plugin\struct\test;
+
+require_once(__DIR__ . '/../DokuWikiFarmCore.php');
+
+class DokuWikiFarmCore extends \DokuWikiFarmCore {
+ public function getAnimalNamesForHost($host) {
+ return parent::getAnimalNamesForHost($host);
+ }
+}
+
+
+/**
+ * @group plugin_farmer
+ * @group plugins
+ */
+class core_plugin_farmer_test extends \DokuWikiTest {
+
+ protected $pluginsEnabled = array('farmer');
+
+
+ public function test_hostsAnimals() {
+ $core = new DokuWikiFarmCore();
+
+ $input = 'www.foobar.example.com:8000';
+ $expect = array(
+ 'www.foobar.example.com.8000',
+ 'foobar.example.com.8000',
+ 'www.foobar.example.com',
+ 'foobar.example.com',
+ 'www.foobar.example',
+ 'foobar.example',
+ 'www.foobar',
+ 'foobar',
+ 'www',
+ );
+
+ $this->assertEquals($expect, $core->getAnimalNamesForHost($input));
+ }
+}
diff --git a/platform/www/lib/plugins/farmer/_test/general.test.php b/platform/www/lib/plugins/farmer/_test/general.test.php
new file mode 100644
index 0000000..af45139
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_test/general.test.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * General tests for the farmer plugin
+ *
+ * @group plugin_farmer
+ * @group plugins
+ */
+class general_plugin_farmer_test extends DokuWikiTest {
+
+ protected $pluginsEnabled = array('farmer');
+
+ /**
+ * Simple test to make sure the plugin.info.txt is in correct format
+ */
+ public function test_plugininfo() {
+ $file = __DIR__ . '/../plugin.info.txt';
+ $this->assertFileExists($file);
+
+ $info = confToHash($file);
+
+ $this->assertArrayHasKey('base', $info);
+ $this->assertArrayHasKey('author', $info);
+ $this->assertArrayHasKey('email', $info);
+ $this->assertArrayHasKey('date', $info);
+ $this->assertArrayHasKey('name', $info);
+ $this->assertArrayHasKey('desc', $info);
+ $this->assertArrayHasKey('url', $info);
+
+ $this->assertEquals('farmer', $info['base']);
+ $this->assertRegExp('/^https?:\/\//', $info['url']);
+ $this->assertTrue(mail_isvalid($info['email']));
+ $this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']);
+ $this->assertTrue(false !== strtotime($info['date']));
+ }
+}
diff --git a/platform/www/lib/plugins/farmer/_test/getUserLine.test.php b/platform/www/lib/plugins/farmer/_test/getUserLine.test.php
new file mode 100644
index 0000000..8f228cb
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_test/getUserLine.test.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace plugin\struct\test;
+
+class admin_plugin_farmer_new extends \admin_plugin_farmer_new {
+ public function getAdminLine() {
+ return parent::getAdminLine();
+ }
+
+}
+
+
+/**
+ * Tests for the validation functionality of the farmer plugin
+ *
+ * @group plugin_farmer
+ * @group plugins
+ */
+class getUserLine_plugin_farmer_test extends \DokuWikiTest {
+
+ protected $pluginsEnabled = array('farmer',);
+ private $usersfile;
+
+ public function setUp() {
+ parent::setUp();
+ $this->usersfile = DOKU_CONF . 'users.auth.php';
+ copy($this->usersfile, $this->usersfile . "org");
+ unlink($this->usersfile);
+ }
+
+ public function tearDown() {
+ parent::tearDown();
+ unlink($this->usersfile);
+ copy($this->usersfile . "org", $this->usersfile);
+ unlink($this->usersfile . "org");
+ }
+
+
+ public function test_getUserLine_oneUser () {
+ $helper = new admin_plugin_farmer_new();
+ $usersfileData = "# users.auth.php
+# <?php exit()?>
+# Don't modify the lines above
+#
+# Userfile
+#
+# Format:
+#
+# user:MD5password:Real Name:email:groups,comma,seperated
+#
+# testuser : testpass
+testuser:179ad45c6ce2cb97cf1029e212046e81:Arthur Dent:arthur@example.com:\n";
+ file_put_contents($this->usersfile,$usersfileData);
+
+ $_SERVER['REMOTE_USER'] = 'testuser';
+ $expected_result = 'testuser:179ad45c6ce2cb97cf1029e212046e81:Arthur Dent:arthur@example.com:' . "\n";
+ $actual_result = $helper->getAdminLine();
+
+ $this->assertSame($expected_result, $actual_result);
+ }
+
+ public function test_getUserLine_manyUser () {
+ $helper = new admin_plugin_farmer_new();
+ $usersfileData = "# users.auth.php
+# <?php exit()?>
+# Don't modify the lines above
+#
+# Userfile
+#
+# Format:
+#
+# user:MD5password:Real Name:email:groups,comma,seperated
+#
+# testuser : testpass
+1testuser:179ad45c6ce43897cf1029e212046e81:Arthur Dent:brthur@example.com:admin
+testuser:179ad45c6ce2cb97cf1029e212046e81:Arthur Dent:arthur@example.com:
+2testuser:179ad45c6ce2cb97cf10214712046e81:Arthur inDent:crthur@example.com:admin\n";
+ file_put_contents($this->usersfile,$usersfileData);
+
+ $_SERVER['REMOTE_USER'] = 'testuser';
+ $expected_result = 'testuser:179ad45c6ce2cb97cf1029e212046e81:Arthur Dent:arthur@example.com:' . "\n";
+ $actual_result = $helper->getAdminLine();
+
+ $this->assertSame($expected_result, $actual_result);
+ }
+}
diff --git a/platform/www/lib/plugins/farmer/_test/helper.test.php b/platform/www/lib/plugins/farmer/_test/helper.test.php
new file mode 100644
index 0000000..3fc689b
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/_test/helper.test.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * Tests for the validation functionality of the farmer plugin
+ *
+ * @group plugin_farmer
+ * @group plugins
+ */
+class helper_plugin_farmer_test extends DokuWikiTest {
+
+ protected $pluginsEnabled = array('farmer',);
+
+ public function validationProvider() {
+ return array(
+ array('ant', true),
+ array('ant.lion', true),
+ array('ant.lion.cow', true),
+ array('ant-lion', true),
+ array('ant-lion.cow', true),
+ array('4ant', true),
+ array('ant4', true),
+ array('ant44lion', true),
+ array('44', true),
+
+ array('ant.', false),
+ array('.ant', false),
+ array('ant-', false),
+ array('-ant', false),
+ array('ant--lion', false),
+ array('ant..lion', false),
+ array('ant.-lion', false),
+ array('ant/lion', false),
+ array('!ant', false),
+ array('ant lion', false),
+ );
+ }
+
+ /**
+ * @dataProvider validationProvider
+ * @param $input
+ * @param $expect
+ */
+ public function test_validateAnimalName($input, $expect) {
+ /** @var helper_plugin_farmer $helper */
+ $helper = plugin_load('helper', 'farmer');
+ $this->assertEquals($expect, $helper->validateAnimalName($input));
+ }
+
+ public function test_isInPath() {
+ /** @var helper_plugin_farmer $helper */
+ $helper = plugin_load('helper', 'farmer');
+
+ $this->assertTrue($helper->isInPath('/var/www/foo', '/var/www'));
+ $this->assertFalse($helper->isInPath('/var/www/../foo', '/var/www'));
+
+ // same dir should return false, too
+ $this->assertFalse($helper->isInPath('/var/www/foo', '/var/www/foo'));
+ $this->assertFalse($helper->isInPath('/var/www/foo/', '/var/www/foo'));
+ $this->assertFalse($helper->isInPath('/var/www/foo/bar/../', '/var/www/foo'));
+
+ // https://github.com/cosmocode/dokuwiki-plugin-farmer/issues/30
+ $this->assertFalse($helper->isInPath('/var/lib/dokuwiki.animals', '/var/lib/dokuwiki'));
+ }
+}
diff --git a/platform/www/lib/plugins/farmer/action/ajax.php b/platform/www/lib/plugins/farmer/action/ajax.php
new file mode 100644
index 0000000..ac20eae
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/action/ajax.php
@@ -0,0 +1,267 @@
+<?php
+/**
+ * DokuWiki Plugin farmer (Action Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Manage AJAX features
+ */
+class action_plugin_farmer_ajax extends DokuWiki_Action_Plugin {
+
+ /**
+ * plugin should use this method to register its handlers with the DokuWiki's event controller
+ *
+ * @param Doku_Event_Handler $controller DokuWiki's event controller object. Also available as global $EVENT_HANDLER
+ *
+ */
+ public function register(Doku_Event_Handler $controller) {
+ $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, '_ajax_call');
+ }
+
+ /**
+ * handle ajax requests
+ *
+ * @param Doku_Event $event
+ * @param $param
+ */
+ public function _ajax_call(Doku_Event $event, $param) {
+ if(substr($event->data, 0, 13) !== 'plugin_farmer') {
+ return;
+ }
+ //no other ajax call handlers needed
+ $event->stopPropagation();
+ $event->preventDefault();
+
+ if(!auth_isadmin()) die('Only admins allowed');
+
+ if(substr($event->data, 14) === 'getPluginMatrix') {
+ $this->get_plugin_matrix($event, $param);
+ return;
+ }
+ if(substr($event->data, 14) === 'modPlugin') {
+ $this->plugin_mod($event, $param);
+ return;
+ }
+ if(substr($event->data, 14, 10) === 'getPlugins') {
+ $this->get_animal_plugins($event, $param);
+ return;
+ }
+ if(substr($event->data, 14, 10) === 'checkSetup') {
+ $this->check_setup($event, $param);
+ }
+ }
+
+ /**
+ * This function exists in order to provide a positive (i.e. 200) response to an ajax request to a non-existing animal.
+ *
+ * @param Doku_Event $event
+ * @param $param
+ */
+ public function check_setup(Doku_Event $event, $param) {
+ $data = '';
+ $json = new JSON();
+ header('Content-Type: application/json');
+ echo $json->encode($data);
+ }
+
+ public function plugin_mod(Doku_Event $event, $param) {
+ global $INPUT;
+
+ /** @var helper_plugin_farmer $helper */
+ $helper = plugin_load('helper', 'farmer');
+
+ $pname = $INPUT->str('plugin');
+ $animal = $INPUT->str('ani');
+
+
+ $plugins = $helper->getAnimalPluginRealState($animal);
+ if(!isset($plugins[$pname])) die('no such plugin');
+ $plugin = $plugins[$pname];
+
+ // figure out what to toggle to
+ if($plugin['isdefault']) {
+ $new = (int) !$plugin['actual'];
+ } else {
+ $new = -1;
+ }
+ $helper->setPluginState($pname, $animal, $new);
+
+ // show new state
+ $plugins = $helper->getAnimalPluginRealState($animal);
+ $plugin = $plugins[$pname];
+ header('Content-Type: text/html; charset=utf-8');
+ echo $this->plugin_matrix_cell($plugin, $animal);
+ }
+
+ /**
+ * Create a matrix of all animals and plugin states
+ *
+ * @param Doku_Event $event
+ * @param $param
+ */
+ public function get_plugin_matrix(Doku_Event $event, $param) {
+ /** @var helper_plugin_farmer $helper */
+ $helper = plugin_load('helper', 'farmer');
+
+ $animals = $helper->getAllAnimals();
+ $plugins = $helper->getAnimalPluginRealState($animals[0]);
+
+ header('Content-Type: text/html; charset=utf-8');
+
+ echo '<div class="table pluginmatrix">';
+ echo '<table>';
+ echo '<thead>';
+ echo '<tr>';
+ echo '<th></th>';
+ foreach($plugins as $plugin) {
+ echo '<th><div>' . hsc($plugin['name']) . '</div></th>';
+ }
+ echo '</tr>';
+ echo '</thead>';
+
+ echo '<tbody>';
+
+ echo '<tr>';
+ echo '<th>Default</th>';
+ foreach($plugins as $plugin) {
+ echo $this->plugin_matrix_cell($plugin, $this->getLang('plugin_default'), true);
+ }
+ echo '</tr>';
+
+ foreach($animals as $animal) {
+ $plugins = $helper->getAnimalPluginRealState($animal);
+ echo '<tr>';
+ echo '<th>' . hsc($animal) . '</th>';
+ foreach($plugins as $plugin) {
+ echo $this->plugin_matrix_cell($plugin, $animal);
+ }
+ echo '</tr>';
+ }
+ echo '</tbody>';
+ echo '</table>';
+ echo '</div>';
+ }
+
+ /**
+ * create a single cell in the matrix
+ *
+ * @param array $plugin
+ * @param string $animal
+ * @param bool $defaults show the defaults
+ * @return string
+ */
+ protected function plugin_matrix_cell($plugin, $animal, $defaults=false) {
+ if($defaults) {
+ $current = $plugin['default'];
+ $isdefault = true;
+ $td = 'th';
+ } else {
+ $current = $plugin['actual'];
+ $isdefault = $plugin['isdefault'];
+ $td = 'td';
+ }
+
+ if($current) {
+ $class = 'on';
+ $lbl = '✓';
+ } else {
+ $class = 'off';
+ $lbl = '✗';
+ }
+ if($isdefault) $class .= ' default';
+
+
+ $attrs = array(
+ 'class' => $class,
+ 'title' => $animal . ': ' . $plugin['name'],
+ 'data-animal' => $animal,
+ 'data-plugin' => $plugin['name']
+ );
+ $attr = buildAttributes($attrs);
+
+ return "<$td $attr>$lbl</$td>";
+ }
+
+ /**
+ * @param Doku_Event $event
+ * @param $param
+ */
+ public function get_animal_plugins(Doku_Event $event, $param) {
+ $animal = substr($event->data, 25);
+ /** @var helper_plugin_farmer $helper */
+ $helper = plugin_load('helper', 'farmer');
+
+ $plugins = $helper->getAnimalPluginRealState($animal);
+
+ header('Content-Type: text/html; charset=utf-8');
+
+ echo '<table>';
+ echo '<tr>';
+ echo '<th>' . $this->getLang('plugin') . '</th>';
+ echo '<th>' . $this->getLang('plugin_default') . '</th>';
+ echo '<th>' . $this->getLang('plugin_enabled') . '</th>';
+ echo '<th>' . $this->getLang('plugin_disabled') . '</th>';
+ echo '</tr>';
+
+ foreach($plugins as $plugin) {
+ echo '<tr>';
+ echo '<th>' . hsc($plugin['name']) . '</th>';
+
+ echo '<td>';
+ $attr = array();
+ $attr['type'] = 'radio';
+ $attr['name'] = 'bulk_plugins[' . $plugin['name'] . ']';
+ $attr['value'] = '-1';
+ if($plugin['isdefault']) {
+ $attr['checked'] = 'checked';
+ }
+ echo '<label>';
+ echo '<input ' . buildAttributes($attr) . ' />';
+ if($plugin['default']) {
+ echo ' (' . $this->getLang('plugin_on') . ')';
+ } else {
+ echo ' (' . $this->getLang('plugin_off') . ')';
+ }
+ echo '</label>';
+ echo '</td>';
+
+ echo '<td>';
+ $attr = array();
+ $attr['type'] = 'radio';
+ $attr['name'] = 'bulk_plugins[' . $plugin['name'] . ']';
+ $attr['value'] = '1';
+ if(!$plugin['isdefault'] && $plugin['actual']) {
+ $attr['checked'] = 'checked';
+ }
+ echo '<label>';
+ echo '<input ' . buildAttributes($attr) . ' />';
+ echo ' ' . $this->getLang('plugin_on');
+ echo '</label>';
+ echo '</td>';
+
+ echo '<td>';
+ $attr = array();
+ $attr['type'] = 'radio';
+ $attr['name'] = 'bulk_plugins[' . $plugin['name'] . ']';
+ $attr['value'] = '0';
+ if(!$plugin['isdefault'] && !$plugin['actual']) {
+ $attr['checked'] = 'checked';
+ }
+ echo '<label>';
+ echo '<input ' . buildAttributes($attr) . ' />';
+ echo ' ' . $this->getLang('plugin_off');
+ echo '</label>';
+ echo '</td>';
+
+ echo '</tr>';
+ }
+ }
+
+}
+
diff --git a/platform/www/lib/plugins/farmer/action/disable.php b/platform/www/lib/plugins/farmer/action/disable.php
new file mode 100644
index 0000000..bf0c48a
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/action/disable.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * DokuWiki Plugin farmer (Action Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Disable Plugins on install
+ */
+class action_plugin_farmer_disable extends DokuWiki_Action_Plugin {
+
+ /**
+ * plugin should use this method to register its handlers with the DokuWiki's event controller
+ *
+ * @param Doku_Event_Handler $controller DokuWiki's event controller object. Also available as global $EVENT_HANDLER
+ *
+ */
+ public function register(Doku_Event_Handler $controller) {
+ /** @var helper_plugin_farmer $farmer */
+ $farmer = plugin_load('helper', 'farmer');
+ if($farmer->getAnimal()) return;
+
+ if($this->getConf('disable_new_plugins')) {
+ $controller->register_hook('PLUGIN_EXTENSION_CHANGE', 'AFTER', $this, 'handle_install');
+ }
+ }
+
+ /**
+ * handle install of new plugin
+ *
+ * @param Doku_Event $event
+ * @param $param
+ */
+ public function handle_install(Doku_Event $event, $param) {
+ if($event->data['action'] != 'install') return;
+
+ /* @var Doku_Plugin_Controller $plugin_controller */
+ global $plugin_controller;
+ $plugin_controller = new Doku_Plugin_Controller(); // we need to refresh the status
+
+ /** @var helper_plugin_extension_extension $ext */
+ $ext = $event->data['extension'];
+ $disabled = $ext->disable();
+ if($disabled === true) {
+ msg($this->getLang('disable_new_plugins'));
+ } else {
+ msg(hsc($disabled), -1);
+ }
+ }
+}
+
diff --git a/platform/www/lib/plugins/farmer/action/startup.php b/platform/www/lib/plugins/farmer/action/startup.php
new file mode 100644
index 0000000..4b9d38e
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/action/startup.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * DokuWiki Plugin farmer (Action Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Handles Farm mechanisms on DokuWiki startup
+ */
+class action_plugin_farmer_startup extends DokuWiki_Action_Plugin {
+
+ /** @var helper_plugin_farmer */
+ protected $helper;
+
+ /**
+ * action_plugin_farmer_startup constructor.
+ */
+ public function __construct() {
+ $this->helper = plugin_load('helper', 'farmer');
+ }
+
+ /**
+ * plugin should use this method to register its handlers with the DokuWiki's event controller
+ *
+ * @param Doku_Event_Handler $controller DokuWiki's event controller object. Also available as global $EVENT_HANDLER
+ *
+ */
+ public function register(Doku_Event_Handler $controller) {
+ $controller->register_hook('DOKUWIKI_STARTED', 'BEFORE', $this, 'before_start');
+ }
+
+ public function before_start(Doku_Event $event, $param) {
+ if($this->helper->wasNotfound()) $this->handleNotFound();
+ }
+
+ /**
+ * Handles the animal not found case
+ *
+ * Will abort the current script unless the farmer is wanted
+ */
+ protected function handleNotFound() {
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ global $conf, $lang;
+ $config = $this->helper->getConfig();
+ $show = $config['notfound']['show'];
+ $url = $config['notfound']['url'];
+ if($show == 'farmer') return;
+
+ if($show == '404' || $show == 'list') {
+ http_status(404);
+ $body = $this->locale_xhtml('notfound_' . $show);
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $title = '404';
+ if($show == 'list') {
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $body .= $this->animalList();
+ }
+
+ include __DIR__ . '/../includes/template.php';
+ exit;
+ }
+
+ if($show == 'redirect' && $url) {
+ send_redirect($url);
+ }
+ }
+
+ /**
+ * Retrun a HTML list of animals
+ *
+ * @return string
+ */
+ protected function animalList() {
+ $html = '<ul>';
+ $animals = $this->helper->getAllAnimals();
+ foreach($animals as $animal) {
+ $link = $this->helper->getAnimalURL($animal);
+ $html .= '<li><div class="li"><a href="' . $link . '">' . hsc($animal) . '</a></div></li>';
+ }
+ $html .= '</ul>';
+ return $html;
+ }
+
+}
+
diff --git a/platform/www/lib/plugins/farmer/admin.php b/platform/www/lib/plugins/farmer/admin.php
new file mode 100644
index 0000000..6f00039
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/admin.php
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * DokuWiki Plugin farmer (Admin Component)
+ *
+ * This is the main admin page. It displays the tabs and then loads the sub components
+ * according to the selected tab
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+class admin_plugin_farmer extends DokuWiki_Admin_Plugin {
+
+ /** @var helper_plugin_farmer */
+ protected $helper;
+ /** @var array The available pages for the current user in the current wiki */
+ protected $pages;
+ /** @var string The currently selected page */
+ protected $page;
+ /** @var DokuWiki_Admin_Plugin the plugin to use for the current page */
+ protected $adminplugin;
+
+ /**
+ * @return bool we're available for managers and admins
+ */
+ public function forAdminOnly() {
+ return false;
+ }
+
+ /**
+ * Initialize current page
+ */
+ public function __construct() {
+ global $INPUT;
+ $this->helper = plugin_load('helper', 'farmer');
+
+ // set available pages depending on user and animal
+ $isanimal = (bool) $this->helper->getAnimal();
+ if($isanimal || !auth_isadmin()) {
+ $this->pages = array(
+ 'info'
+ );
+ } else {
+ if(!$this->helper->checkFarmSetup()) {
+ $this->pages = array(
+ 'setup'
+ );
+ } else {
+ $this->pages = array(
+ 'info',
+ 'config',
+ 'new',
+ 'plugins',
+ 'delete'
+ );
+ }
+ }
+
+ // make sure current page requested is available
+ $this->page = $INPUT->str('sub');
+ if(!in_array($this->page, $this->pages)) {
+ $this->page = $this->pages[0];
+ }
+
+ // load the sub component
+ $this->adminplugin = plugin_load('admin', 'farmer_' . $this->page);
+ if(!$this->adminplugin) nice_die('Something went wrong loading the plugin component for ' . hsc($this->page));
+ }
+
+ /**
+ * handle user request
+ */
+ public function handle() {
+ $this->adminplugin->handle();
+ }
+
+ /**
+ * output appropriate tab
+ */
+ public function html() {
+ global $ID;
+
+ echo '<div id="plugin__farmer_admin">';
+ echo '<h1>' . $this->getLang('menu') . '</h1>';
+
+ echo '<ul class="tabs" id="plugin__farmer_tabs">';
+ foreach($this->pages as $page) {
+ $link = wl($ID, array('do' => 'admin', 'page' => 'farmer', 'sub' => $page));
+ $class = ($page == $this->page) ? 'active' : '';
+
+ echo '<li class="' . $class . '"><a href="' . $link . '">' . $this->getLang('tab_' . $page) . '</a></li>';
+ }
+ echo '</ul>';
+ echo '<div class="panelHeader">';
+ echo $this->locale_xhtml('tab_' . $this->page);
+ echo '</div>';
+ echo '<div class="panelMain">';
+ $this->adminplugin->html();
+ echo '</div>';
+ echo '<div class="panelFooter">';
+ echo $this->locale_xhtml('tab_' . $this->page . '_help');
+ echo '</div>';
+ echo '</div>';
+ }
+
+ /**
+ * @return int
+ */
+ public function getMenuSort() {
+ return 42;
+ }
+
+}
+
diff --git a/platform/www/lib/plugins/farmer/admin.svg b/platform/www/lib/plugins/farmer/admin.svg
new file mode 100644
index 0000000..e774207
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/admin.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10.5 18a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5m3 0a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5M10 11a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m4 7c0 2.21-2.69 4-6 4s-6-1.79-6-4c0-.9.45-1.73 1.2-2.4-.75-1-1.2-2.25-1.2-3.6l.12-1.22c-.54.15-1.19.15-1.72 0-1.02-.28-2.56-1.43-2.33-2.23.23-.8 2.14-.95 3.16-.65.59.17 1.22.6 1.59 1.06l.57-.81C6.79 7.05 7 4 10 3l-.09.14c-.28.44-1 1.83-.24 3.33a6.02 6.02 0 0 1 4.66 0c.76-1.5.04-2.89-.24-3.33L14 3c3 1 3.21 4.05 2.61 5.15l.57.81c.37-.46 1-.89 1.59-1.06 1.02-.3 2.93-.15 3.16.65.23.8-1.31 1.95-2.33 2.23-.53.15-1.18.15-1.72 0L18 12c0 1.35-.45 2.6-1.2 3.6.75.67 1.2 1.5 1.2 2.4m-6-2c-2.21 0-4 .9-4 2s1.79 2 4 2 4-.9 4-2-1.79-2-4-2m0-2c1.12 0 2.17.21 3.07.56.58-.69.93-1.56.93-2.56a4 4 0 0 0-4-4 4 4 0 0 0-4 4c0 1 .35 1.87.93 2.56.9-.35 1.95-.56 3.07-.56m2.09-10.86z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/plugins/farmer/admin/config.php b/platform/www/lib/plugins/farmer/admin/config.php
new file mode 100644
index 0000000..7d79970
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/admin/config.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * DokuWiki Plugin farmer (Admin Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+use dokuwiki\Form\Form;
+
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Configuration Interface for farm.ini
+ */
+class admin_plugin_farmer_config extends DokuWiki_Admin_Plugin {
+
+ /** @var helper_plugin_farmer */
+ protected $helper;
+
+ /**
+ * @return bool admin only!
+ */
+ public function forAdminOnly() {
+ return false;
+ }
+
+ /**
+ * admin_plugin_farmer_config constructor.
+ */
+ public function __construct() {
+ $this->helper = plugin_load('helper', 'farmer');
+ }
+
+ /**
+ * Should carry out any processing required by the plugin.
+ */
+ public function handle() {
+ global $INPUT;
+ global $ID;
+ if(!$INPUT->has('farmconf')) return;
+ if(!checkSecurityToken()) return;
+
+ $farmconf = $this->helper->getConfig();
+ $farmdir = $farmconf['base']['farmdir'];
+ $farmconf = array_merge($farmconf, $INPUT->arr('farmconf'));
+ $farmconf['base']['farmdir'] = $farmdir;
+
+ $farmconf['base']['basedomain'] = trim(trim($farmconf['base']['basedomain'], '.'));
+
+ $ini = DOKU_INC . 'conf/farm.ini';
+ $data = "; Farm config created by the farmer plugin\n";
+ $data .= $this->createIni($farmconf);
+ io_saveFile($ini, $data);
+
+ $self = wl($ID, array('do' => 'admin', 'page' => 'farmer', 'sub' => 'config'), true, '&');
+ send_redirect($self);
+ }
+
+ /**
+ * Render HTML output, e.g. helpful text and a form
+ */
+ public function html() {
+ $farmconf = $this->helper->getConfig();
+
+ $form = new Form(array('method' => 'post'));
+
+ $form->addFieldsetOpen($this->getLang('base'));
+ $form->addHTML('<label><span>' . $this->getLang('farm dir') . '</span>' . DOKU_FARMDIR);
+ $form->addTextInput('farmconf[base][farmhost]', $this->getLang('farm host'))->val($farmconf['base']['farmhost']);
+ $form->addTextInput('farmconf[base][basedomain]', $this->getLang('base domain'))->val($farmconf['base']['basedomain']);
+ $form->addFieldsetClose();
+
+ $form->addFieldsetOpen($this->getLang('conf_inherit'));
+ foreach($farmconf['inherit'] as $key => $val) {
+ $form->setHiddenField("farmconf[inherit][$key]", 0);
+ $chk = $form->addCheckbox("farmconf[inherit][$key]", $this->getLang('conf_inherit_' . $key))->useInput(false);
+ if($val) $chk->attr('checked', 'checked');
+ }
+ $form->addFieldsetClose();
+
+ $options = array(
+ 'farmer' => $this->getLang('conf_notfound_farmer'),
+ '404' => $this->getLang('conf_notfound_404'),
+ 'list' => $this->getLang('conf_notfound_list'),
+ 'redirect' => $this->getLang('conf_notfound_redirect')
+ );
+
+ $form->addFieldsetOpen($this->getLang('conf_notfound'));
+ $form->addDropdown('farmconf[notfound][show]', $options, $this->getLang('conf_notfound'))->val($farmconf['notfound']['show']);
+ $form->addTextInput('farmconf[notfound][url]', $this->getLang('conf_notfound_url'))->val($farmconf['notfound']['url']);
+ $form->addFieldsetClose();
+
+ $form->addButton('save', $this->getLang('save'));
+ echo $form->toHTML();
+ }
+
+ /**
+ * Simple function to create an ini file
+ *
+ * Does no escaping, but should suffice for our use case
+ *
+ * @link http://stackoverflow.com/a/5695202/172068
+ * @param array $data The data to transform
+ * @return string
+ */
+ public function createIni($data) {
+ $res = array();
+ foreach($data as $key => $val) {
+ if(is_array($val)) {
+ $res[] = '';
+ $res[] = "[$key]";
+ foreach($val as $skey => $sval) {
+ $res[] = "$skey = " . (is_numeric($sval) ? $sval : '"' . $sval . '"');
+ }
+ } else {
+ $res[] = "$key = " . (is_numeric($val) ? $val : '"' . $val . '"');
+ }
+ }
+ $res[] = '';
+ return join("\n", $res);
+ }
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/farmer/admin/delete.php b/platform/www/lib/plugins/farmer/admin/delete.php
new file mode 100644
index 0000000..d8b3450
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/admin/delete.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * DokuWiki Plugin farmer (Admin Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+// must be run within Dokuwiki
+use dokuwiki\Form\Form;
+
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Information about the farm and the current instance
+ */
+class admin_plugin_farmer_delete extends DokuWiki_Admin_Plugin {
+
+ /** @var helper_plugin_farmer */
+ protected $helper;
+
+ /**
+ * admin_plugin_farmer_info constructor.
+ */
+ public function __construct() {
+ $this->helper = plugin_load('helper', 'farmer');
+ }
+
+ /**
+ * @return bool admin only!
+ */
+ public function forAdminOnly() {
+ return true;
+ }
+
+ /**
+ * Should carry out any processing required by the plugin.
+ */
+ public function handle() {
+ global $INPUT;
+ global $ID;
+ if(!$INPUT->has('delete')) return;
+
+ if($INPUT->filter('trim')->str('delanimal') === '') {
+ msg($this->getLang('delete_noanimal'), -1);
+ return;
+ }
+
+ if($INPUT->str('delanimal') != $INPUT->str('confirm')) {
+ msg($this->getLang('delete_mismatch'), -1);
+ return;
+ }
+
+ $animaldir = DOKU_FARMDIR . $INPUT->str('delanimal');
+
+ if(!$this->helper->isInPath($animaldir, DOKU_FARMDIR) || !is_dir($animaldir)) {
+ msg($this->getLang('delete_invalid'), -1);
+ return;
+ }
+
+ // let's delete it
+ $ok = io_rmdir($animaldir, true);
+ if($ok) {
+ msg($this->getLang('delete_success'), 1);
+ } else {
+ msg($this->getLang('delete_fail'), -1);
+ }
+
+ $link = wl($ID, array('do'=>'admin', 'page'=>'farmer', 'sub' => 'delete'), true, '&');
+ send_redirect($link);
+ }
+
+ /**
+ * Render HTML output, e.g. helpful text and a form
+ */
+ public function html() {
+
+ $form = new Form();
+ $form->addFieldsetOpen($this->getLang('delete_animal'));
+
+ $animals = $this->helper->getAllAnimals();
+ array_unshift($animals, '');
+ $form->addDropdown('delanimal', $animals)->addClass('farmer_chosen_animals');
+ $form->addTextInput('confirm', $this->getLang('delete_confirm'));
+ $form->addButton('delete', $this->getLang('delete'));
+ $form->addFieldsetClose();
+ echo $form->toHTML();
+
+ }
+
+
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/farmer/admin/info.php b/platform/www/lib/plugins/farmer/admin/info.php
new file mode 100644
index 0000000..3bf2938
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/admin/info.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * DokuWiki Plugin farmer (Admin Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Information about the farm and the current instance
+ */
+class admin_plugin_farmer_info extends DokuWiki_Admin_Plugin {
+
+ /** @var helper_plugin_farmer */
+ protected $helper;
+
+ /**
+ * admin_plugin_farmer_info constructor.
+ */
+ public function __construct() {
+ $this->helper = plugin_load('helper', 'farmer');
+ }
+
+ /**
+ * @return bool admin only!
+ */
+ public function forAdminOnly() {
+ return false;
+ }
+
+ /**
+ * Should carry out any processing required by the plugin.
+ */
+ public function handle() {
+ }
+
+ /**
+ * Render HTML output, e.g. helpful text and a form
+ */
+ public function html() {
+ global $conf;
+ global $INPUT;
+
+ $animal = $this->helper->getAnimal();
+ $config = $this->helper->getConfig();
+
+ echo '<table class="inline">';
+
+ $this->line('thisis', $animal ? $this->getLang('thisis.animal') : $this->getLang('thisis.farmer'));
+ if($animal) {
+ $this->line('animal', $animal);
+ }
+ $this->line('confdir', fullpath(DOKU_CONF) . '/');
+ $this->line('savedir', fullpath($conf['savedir']) . '/');
+ $this->line('baseinstall', DOKU_INC);
+ $this->line('farm host', $config['base']['farmhost']);
+ $this->line('farm dir', DOKU_FARMDIR);
+
+ $this->line('animals', $this->animals($INPUT->bool('list')));
+
+ foreach($config['inherit'] as $key => $value) {
+ $this->line('conf_inherit_' . $key, $this->getLang($value ? 'conf_inherit_yes' : 'conf_inherit_no'));
+ }
+
+ $this->line('plugins', join(', ', $this->helper->getAllPlugins(false)));
+
+ echo '</table>';
+ }
+
+ /**
+ * List or count the animals
+ *
+ * @param bool $list
+ * @return string
+ */
+ protected function animals($list) {
+ global $ID;
+
+ $animals = $this->helper->getAllAnimals();
+ $html = '';
+ if(!$list) {
+ $html = count($animals);
+ $self = wl($ID, array('do' => 'admin', 'page' => 'farmer', 'sub' => 'info', 'list' => 1));
+ $html .= ' [<a href="' . $self . '">' . $this->getLang('conf_notfound_list') . '</a>]';
+ return $html;
+ }
+
+ $html .= '<ol>';
+ foreach($animals as $animal) {
+ $link = $this->helper->getAnimalURL($animal);
+ $html .= '<li><div class="li"><a href="' . $link . '">' . $animal . '</a></div></li>';
+ }
+ $html .= '</ol>';
+ return $html;
+ }
+
+ /**
+ * Output a table line
+ *
+ * @param string $langkey
+ * @param string $value
+ */
+ protected function line($langkey, $value) {
+ echo '<tr>';
+ echo '<th>' . $this->getLang($langkey) . '</th>';
+ echo '<td>' . $value . '</td>';
+ echo '</tr>';
+ }
+
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/farmer/admin/new.php b/platform/www/lib/plugins/farmer/admin/new.php
new file mode 100644
index 0000000..8cbf94f
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/admin/new.php
@@ -0,0 +1,338 @@
+<?php
+/**
+ * DokuWiki Plugin farmer (Admin Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Große <grosse@cosmocode.de>
+ */
+
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+class admin_plugin_farmer_new extends DokuWiki_Admin_Plugin {
+
+ /** @var helper_plugin_farmer $helper */
+ protected $helper;
+
+ /**
+ * @return bool true if only access for superuser, false is for superusers and moderators
+ */
+ public function forAdminOnly() {
+ return true;
+ }
+
+ /**
+ * admin_plugin_farmer_new constructor.
+ */
+ public function __construct() {
+ $this->helper = plugin_load('helper', 'farmer');
+ }
+
+ /**
+ * Should carry out any processing required by the plugin.
+ */
+ public function handle() {
+ global $INPUT;
+ global $ID;
+ if(!$INPUT->has('farmer__submit')) return;
+
+ $data = $this->validateAnimalData();
+ if(!$data) return;
+ if($this->createNewAnimal($data['name'], $data['admin'], $data['pass'], $data['template'], $data['aclpolicy'], $data['allowreg'])) {
+ $url = $this->helper->getAnimalURL($data['name']);
+ $link = '<a href="' . $url . '">' . hsc($data['name']) . '</a>';
+
+ msg(sprintf($this->getLang('animal creation success'), $link), 1);
+ $link = wl($ID, array('do' => 'admin', 'page' => 'farmer', 'sub' => 'new'), true, '&');
+ send_redirect($link);
+ }
+ }
+
+ /**
+ * Render HTML output, e.g. helpful text and a form
+ */
+ public function html() {
+ global $lang;
+ $farmconfig = $this->helper->getConfig();
+
+ $form = new \dokuwiki\Form\Form();
+ $form->addClass('plugin_farmer')->id('farmer__create_animal_form');
+
+ $form->addFieldsetOpen($this->getLang('animal configuration'));
+ $form->addTextInput('animalname', $this->getLang('animal'));
+ $form->addFieldsetClose();
+
+ $animals = $this->helper->getAllAnimals();
+ array_unshift($animals, '');
+ $form->addFieldsetOpen($this->getLang('animal template'));
+ $form->addDropdown('animaltemplate', $animals)->addClass('farmer_chosen_animals');
+ $form->addFieldsetClose();
+
+ $form->addFieldsetOpen($lang['i_policy'])->attr('id', 'aclPolicyFieldset');
+ $policyOptions = array('open' => $lang['i_pol0'],'public' => $lang['i_pol1'], 'closed' => $lang['i_pol2']);
+ $form->addDropdown('aclpolicy', $policyOptions)->addClass('acl_chosen');
+ if ($farmconfig['inherit']['main']) {
+ $form->addRadioButton('allowreg',$this->getLang('inherit user registration'))->val('inherit')->attr('checked', 'checked');
+ $form->addRadioButton('allowreg',$this->getLang('enable user registration'))->val('allow');
+ $form->addRadioButton('allowreg',$this->getLang('disable user registration'))->val('disable');
+ } else {
+ $form->addCheckbox('allowreg', $lang['i_allowreg'])->attr('checked', 'checked');
+ }
+
+ $form->addFieldsetClose();
+
+ $form->addFieldsetOpen($this->getLang('animal administrator'));
+ $btn = $form->addRadioButton('adminsetup', $this->getLang('noUsers'))->val('noUsers');
+ if($farmconfig['inherit']['users']) {
+ $btn->attr('checked', 'checked'); // default when inherit available
+ } else {
+ // no user copying when inheriting
+ $form->addRadioButton('adminsetup', $this->getLang('importUsers'))->val('importUsers');
+ $form->addRadioButton('adminsetup', $this->getLang('currentAdmin'))->val('currentAdmin');
+ }
+ $btn = $form->addRadioButton('adminsetup', $this->getLang('newAdmin'))->val('newAdmin');
+ if(!$farmconfig['inherit']['users']) {
+ $btn->attr('checked', 'checked'); // default when inherit not available
+ }
+ $form->addPasswordInput('adminPassword', $this->getLang('admin password'));
+ $form->addFieldsetClose();
+
+ $form->addButton('farmer__submit', $this->getLang('submit'))->attr('type', 'submit')->val('newAnimal');
+ echo $form->toHTML();
+ }
+
+ /**
+ * Validate the data for a new animal
+ *
+ * @return array|bool false on errors, clean data otherwise
+ */
+ protected function validateAnimalData() {
+ global $INPUT;
+
+ $animalname = $INPUT->filter('trim')->str('animalname');
+ $adminsetup = $INPUT->str('adminsetup');
+ $adminpass = $INPUT->filter('trim')->str('adminPassword');
+ $template = $INPUT->filter('trim')->str('animaltemplate');
+ $aclpolicy = $INPUT->filter('trim')->str('aclpolicy');
+ $allowreg = $INPUT->str('allowreg');
+
+ $errors = array();
+
+ if($animalname === '') {
+ $errors[] = $this->getLang('animalname_missing');
+ } elseif(!$this->helper->validateAnimalName($animalname)) {
+ $errors[] = $this->getLang('animalname_invalid');
+ }
+
+ if($adminsetup === 'newAdmin' && $adminpass === '') {
+ $errors[] = $this->getLang('adminPassword_empty');
+ }
+
+ if($animalname !== '' && file_exists(DOKU_FARMDIR . '/' . $animalname)) {
+ $errors[] = $this->getLang('animalname_preexisting');
+ }
+
+ if (!is_dir(DOKU_FARMDIR . $template) && !in_array($aclpolicy,array('open', 'public', 'closed'))) {
+ $errors[] = $this->getLang('aclpolicy missing/bad');
+ }
+
+ if($errors) {
+ foreach($errors as $error) {
+ msg($error, -1);
+ }
+ return false;
+ }
+
+ if(!is_dir(DOKU_FARMDIR . $template)) {
+ $template = '';
+ }
+ if ($template != '') {
+ $aclpolicy = '';
+ }
+
+ return array(
+ 'name' => $animalname,
+ 'admin' => $adminsetup,
+ 'pass' => $adminpass,
+ 'template' => $template,
+ 'aclpolicy' => $aclpolicy,
+ 'allowreg' => $allowreg
+ );
+ }
+
+ /**
+ * Create a new animal
+ *
+ * @param string $name name/title of the animal, will be the directory name for htaccess setup
+ * @param string $adminSetup newAdmin, currentAdmin or importUsers
+ * @param string $adminPassword required if $adminSetup is newAdmin
+ * @param string $template name of animal to copy
+ * @param $aclpolicy
+ * @param $userreg
+ * @return bool true if successful
+ * @throws Exception
+ */
+ protected function createNewAnimal($name, $adminSetup, $adminPassword, $template, $aclpolicy, $userreg) {
+ $animaldir = DOKU_FARMDIR . $name;
+
+ // copy basic template
+ $ok = $this->helper->io_copyDir(__DIR__ . '/../_animal', $animaldir);
+ if(!$ok) {
+ msg($this->getLang('animal creation error'), -1);
+ return false;
+ }
+
+ // copy animal template
+ if($template != '') {
+ foreach(array('conf', 'data/pages', 'data/media', 'data/meta', 'data/media_meta', 'index') as $dir) {
+ $templatedir = DOKU_FARMDIR . $template . '/' . $dir;
+ if(!is_dir($templatedir)) continue;
+ // do not copy changelogs in meta
+ if(substr($dir, -4) == 'meta') {
+ $exclude = '/\.changes$/';
+ } else {
+ $exclude = '';
+ }
+ if(!$this->helper->io_copyDir($templatedir, $animaldir . '/' . $dir, $exclude)) {
+ msg(sprintf($this->getLang('animal template copy error'), $dir), -1);
+ // we go on anyway
+ }
+ }
+ }
+
+ // append title to local config
+ $ok &= io_saveFile($animaldir . '/conf/local.php', "\n" . '$conf[\'title\'] = \'' . $name . '\';' . "\n", true);
+
+ // create a random logo and favicon
+ if(!class_exists('\splitbrain\RingIcon\RingIcon', false)) {
+ require(__DIR__ . '/../3rdparty/RingIcon.php');
+ }
+ if(!class_exists('\chrisbliss18\phpico\PHPIco', false)) {
+ require(__DIR__ . '/../3rdparty/PHPIco.php');
+ }
+ try {
+ if(function_exists('imagecreatetruecolor')) {
+ $logo = $animaldir . '/data/media/wiki/logo.png';
+ if(!file_exists($logo)) {
+ $ringicon = new \splitbrain\RingIcon\RingIcon(64);
+ $ringicon->createImage($animaldir, $logo);
+ }
+
+ $icon = $animaldir . '/data/media/wiki/favicon.ico';
+ if(!file_exists($icon)) {
+ $icongen = new \chrisbliss18\phpico\PHPIco($logo);
+ $icongen->save_ico($icon);
+ }
+ }
+ } catch(\Exception $ignore) {
+ // something went wrong, but we don't care. this is a nice to have feature only
+ }
+
+ // create admin user
+ if($adminSetup === 'newAdmin') {
+ $users = "# <?php exit()?>\n" . $this->makeAdminLine($adminPassword) . "\n";
+ } elseif($adminSetup === 'currentAdmin') {
+ $users = "# <?php exit()?>\n" . $this->getAdminLine() . "\n";
+ } elseif($adminSetup === 'noUsers') {
+ if(file_exists($animaldir . '/conf/users.auth.php')) {
+ // a user file exists already, probably from animal template - don't overwrite
+ $users = '';
+ } else {
+ // create empty user file
+ $users = "# <?php exit()?>\n";
+ }
+ } else {
+ $users = io_readFile(DOKU_CONF . 'users.auth.php');
+ }
+ if($users) {
+ $ok &= io_saveFile($animaldir . '/conf/users.auth.php', $users);
+ }
+
+ if ($aclpolicy != '') {
+ $aclfile = file($animaldir . '/conf/acl.auth.php');
+ $aclfile = array_map('trim', $aclfile);
+ array_pop($aclfile);
+ switch ($aclpolicy) {
+ case 'open':
+ $aclfile[] = "* @ALL 8";
+ break;
+ case 'public':
+ $aclfile[] = "* @ALL 1";
+ $aclfile[] = "* @user 8";
+ break;
+ case 'closed':
+ $aclfile[] = "* @ALL 0";
+ $aclfile[] = "* @user 8";
+ break;
+ default:
+ throw new Exception('Undefined aclpolicy given');
+ }
+ $ok &= io_saveFile($animaldir . '/conf/acl.auth.php', join("\n", $aclfile)."\n");
+
+ global $conf;
+ switch ($userreg) {
+ case 'allow':
+ $disableactions = join(',', array_diff(explode(',', $conf['disableactions']), array('register')));
+ $ok &= io_saveFile($animaldir . '/conf/local.php', "\n" . '$conf[\'disableactions\'] = \''.$disableactions.'\';' . "\n", true);
+ break;
+ case 'disable':
+ $disableactions = join(',', array_merge(explode(',', $conf['disableactions']), array('register')));
+ $ok &= io_saveFile($animaldir . '/conf/local.php', "\n" . '$conf[\'disableactions\'] = \''.$disableactions.'\';' . "\n", true);
+ break;
+ case 'inherit':
+ case true:
+ // nothing needs to be done
+ break;
+ default:
+ $ok &= io_saveFile($animaldir . '/conf/local.php', "\n" . '$conf[\'disableactions\'] = \'register\';' . "\n", true);
+ }
+ }
+
+ // deactivate plugins by default FIXME this should be nicer
+ $deactivatedPluginsList = explode(',', $this->getConf('deactivated plugins'));
+ $deactivatedPluginsList = array_map('trim', $deactivatedPluginsList);
+ $deactivatedPluginsList = array_unique($deactivatedPluginsList);
+ $deactivatedPluginsList = array_filter($deactivatedPluginsList);
+ foreach($deactivatedPluginsList as $plugin) {
+ $this->helper->setPluginState(trim($plugin), $name, 0);
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Creates a new user line
+ *
+ * @param $password
+ * @return string
+ */
+ protected function makeAdminLine($password) {
+ $pass = auth_cryptPassword($password);
+ $line = join(
+ ':', array(
+ 'admin',
+ $pass,
+ 'Administrator',
+ 'admin@example.org',
+ 'admin,user'
+ )
+ );
+ return $line;
+ }
+
+ /**
+ * Copies the current user as new admin line
+ *
+ * @return string
+ */
+ protected function getAdminLine() {
+ $currentAdmin = $_SERVER['REMOTE_USER'];
+ $masterUsers = file_get_contents(DOKU_CONF . 'users.auth.php');
+ $masterUsers = ltrim(strstr($masterUsers, "\n" . $currentAdmin . ":"));
+ $newAdmin = substr($masterUsers, 0, strpos($masterUsers, "\n") + 1);
+ return $newAdmin;
+ }
+
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/farmer/admin/plugins.php b/platform/www/lib/plugins/farmer/admin/plugins.php
new file mode 100644
index 0000000..74f0e60
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/admin/plugins.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * DokuWiki Plugin farmer (Admin Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Manage Animal Plugin settings
+ */
+class admin_plugin_farmer_plugins extends DokuWiki_Admin_Plugin {
+
+ /** @var helper_plugin_farmer $helper */
+ private $helper;
+
+ public function __construct() {
+ $this->helper = plugin_load('helper', 'farmer');
+ }
+
+ /**
+ * handle user request
+ */
+ public function handle() {
+ global $INPUT;
+ global $ID;
+
+ $self = wl($ID, array('do' => 'admin', 'page' => 'farmer', 'sub' => 'plugins'), true, '&');
+
+ if($INPUT->has('bulk_plugin') && $INPUT->has('state')) {
+ $animals = $this->helper->getAllAnimals();
+ $plugin = $INPUT->str('bulk_plugin');
+ foreach($animals as $animal) {
+ $this->helper->setPluginState($plugin, $animal, $INPUT->int('state'));
+ }
+ msg($this->getLang('plugindone'), 1);
+ send_redirect($self);
+ }
+
+ if($INPUT->has('bulk_animal') && $INPUT->has('bulk_plugins')) {
+ $animal = $INPUT->str('bulk_animal');
+ $activePlugins = $INPUT->arr('bulk_plugins');
+ foreach($activePlugins as $plugin => $state) {
+ $this->helper->setPluginState($plugin, $animal, $state);
+ }
+ msg($this->getLang('plugindone'), 1);
+ send_redirect($self);
+ }
+ }
+
+ /**
+ * output appropriate html
+ */
+ public function html() {
+
+ echo $this->locale_xhtml('plugins');
+ $switchForm = new \dokuwiki\Form\Form();
+ $switchForm->addClass('plugin_farmer');
+ $switchForm->addFieldsetOpen($this->getLang('bulkSingleSwitcher'));
+ $switchForm->addRadioButton('bulkSingleSwitch', $this->getLang('bulkEdit'))->id('farmer__bulk')->attr('type', 'radio');
+ $switchForm->addRadioButton('bulkSingleSwitch', $this->getLang('singleEdit'))->id('farmer__single')->attr('type', 'radio');
+ $switchForm->addRadioButton('bulkSingleSwitch', $this->getLang('matrixEdit'))->id('farmer__matrix')->attr('type', 'radio');
+ $switchForm->addFieldsetClose();
+ echo $switchForm->toHTML();
+
+ /** @var helper_plugin_farmer $helper */
+ $helper = plugin_load('helper', 'farmer');
+ $plugins = $helper->getAllPlugins();
+ array_unshift($plugins, '');
+
+ // All Animals at once
+ $bulkForm = new \dokuwiki\Form\Form();
+ $bulkForm->id('farmer__pluginsforall');
+ $bulkForm->addFieldsetOpen($this->getLang('bulkEditForm'));
+ $bulkForm->addDropdown('bulk_plugin', $plugins);
+ $bulkForm->addButton('state', $this->getLang('default'))->attr('value', '-1')->attr('type', 'submit')->attr('disabled', 'disabled');
+ $bulkForm->addButton('state', $this->getLang('activate'))->attr('value', '1')->attr('type', 'submit')->attr('disabled', 'disabled');
+ $bulkForm->addButton('state', $this->getLang('deactivate'))->attr('value', '0')->attr('type', 'submit')->attr('disabled', 'disabled');
+ $bulkForm->addFieldsetClose();
+ echo $bulkForm->toHTML();
+
+ $animals = $helper->getAllAnimals();
+ array_unshift($animals, '');
+
+ // One Animal, all the plugins
+ $singleForm = new \dokuwiki\Form\Form();
+ $singleForm->id('farmer__pluginsforone');
+ $singleForm->addFieldsetOpen($this->getLang('singleEditForm'));
+ $singleForm->addDropdown('bulk_animal', $animals);
+ $singleForm->addTagOpen('div')->addClass('output');
+ $singleForm->addTagClose('div');
+ $singleForm->addButton('save', $this->getLang('save'))->attr('disabled', 'disabled');
+
+ echo $singleForm->toHTML();
+
+
+ echo '<div id="farmer__pluginmatrix"></div>';
+ }
+}
+
diff --git a/platform/www/lib/plugins/farmer/admin/setup.php b/platform/www/lib/plugins/farmer/admin/setup.php
new file mode 100644
index 0000000..f836ae5
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/admin/setup.php
@@ -0,0 +1,151 @@
+<?php
+/**
+ * DokuWiki Plugin farmer (Admin Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Setup the farm by creating preload.php etc
+ */
+class admin_plugin_farmer_setup extends DokuWiki_Admin_Plugin {
+
+ /** @var helper_plugin_farmer $helper */
+ private $helper;
+
+ /**
+ * @return bool admin only!
+ */
+ public function forAdminOnly() {
+ return true;
+ }
+
+ /**
+ * Should carry out any processing required by the plugin.
+ */
+ public function handle() {
+ global $INPUT;
+ global $ID;
+
+ if(!$INPUT->bool('farmdir')) return;
+ if(!checkSecurityToken()) return;
+
+ $this->helper = plugin_load('helper', 'farmer');
+
+ $farmdir = trim($INPUT->str('farmdir', ''));
+ if($farmdir[0] !== '/') $farmdir = DOKU_INC . $farmdir;
+ $farmdir = fullpath($farmdir);
+
+ $errors = array();
+ if($farmdir === '') {
+ $errors[] = $this->getLang('farmdir_missing');
+ } elseif($this->helper->isInPath($farmdir, DOKU_INC) !== false) {
+ $errors[] = sprintf($this->getLang('farmdir_in_dokuwiki'), hsc($farmdir), hsc(DOKU_INC));
+ } elseif(!io_mkdir_p($farmdir)) {
+ $errors[] = sprintf($this->getLang('farmdir_uncreatable'), hsc($farmdir));
+ } elseif(!is_writeable($farmdir)) {
+ $errors[] = sprintf($this->getLang('farmdir_unwritable'), hsc($farmdir));
+ } elseif(count(scandir($farmdir)) > 2) {
+ $errors[] = sprintf($this->getLang('farmdir_notEmpty'), hsc($farmdir));
+ }
+
+ if($errors) {
+ foreach($errors as $error) {
+ msg($error, -1);
+ }
+ return;
+ }
+
+ // create the files
+ $ok = $this->createPreloadPHP();
+ if($ok && $INPUT->bool('htaccess')) $ok &= $this->createHtaccess();
+ if($ok) $ok &= $this->createFarmIni($farmdir);
+
+ if($ok) {
+ msg($this->getLang('preload creation success'), 1);
+ $link = wl($ID, array('do' => 'admin', 'page' => 'farmer', 'sub' => 'config'), true, '&');
+ send_redirect($link);
+ } else {
+ msg($this->getLang('preload creation error'), -1);
+ }
+ }
+
+ /**
+ * Render HTML output, e.g. helpful text and a form
+ */
+ public function html() {
+ // Is preload.php already enabled?
+ if(file_exists(DOKU_INC . 'inc/preload.php')) {
+ msg($this->getLang('overwrite_preload'), -1);
+ }
+
+ $form = new \dokuwiki\Form\Form();
+ $form->addClass('plugin_farmer');
+ $form->addFieldsetOpen($this->getLang('preloadPHPForm'));
+ $form->addTextInput('farmdir', $this->getLang('farm dir'));
+ $form->addCheckbox('htaccess', $this->getLang('htaccess setup'))->attr('checked', 'checked');
+ $form->addButton('farmer__submit', $this->getLang('submit'))->attr('type', 'submit');
+ $form->addFieldsetClose();
+ echo $form->toHTML();
+
+ }
+
+ /**
+ * Creates the preload that loads our farm controller
+ * @return bool true if saving was successful
+ */
+ protected function createPreloadPHP() {
+ $content = "<?php\n";
+ $content .= "# farm setup by farmer plugin\n";
+ $content .= "if(file_exists(__DIR__ . '/../lib/plugins/farmer/DokuWikiFarmCore.php')) {\n";
+ $content .= " include(__DIR__ . '/../lib/plugins/farmer/DokuWikiFarmCore.php');\n";
+ $content .= "}\n";
+ return io_saveFile(DOKU_INC . 'inc/preload.php', $content);
+ }
+
+ /**
+ * Prepends the needed config to the main .htaccess for htaccess type setups
+ *
+ * @return bool true if saving was successful
+ */
+ protected function createHtaccess() {
+ // load existing or template
+ if(file_exists(DOKU_INC . '.htaccess')) {
+ $old = io_readFile(DOKU_INC . '.htaccess');
+ } elseif(file_exists(DOKU_INC . '.htaccess.dist')) {
+ $old = io_readFile(DOKU_INC . '.htaccess.dist');
+ } else {
+ $old = '';
+ }
+
+ $content = "# Options added for farm setup by farmer plugin:\n";
+ $content .= "RewriteEngine On\n";
+ $content .= 'RewriteRule ^!([^/]+)/(.*) $2?animal=$1 [QSA,DPI]' . "\n";
+ $content .= 'RewriteRule ^!([^/]+)$ ?animal=$1 [QSA,DPI]' . "\n";
+ $content .= 'Options +FollowSymLinks' . "\n";
+ $content .= '# end of farm configuration' . "\n\n";
+ $content .= $old;
+ return io_saveFile(DOKU_INC . '.htaccess', $content);
+ }
+
+ /**
+ * Creates the initial configuration
+ *
+ * @param $animalpath
+ * @return bool true if saving was successful
+ */
+ protected function createFarmIni($animalpath) {
+ $content = "; farm config created by the farmer plugin\n\n";
+ $content .= "[base]\n";
+ $content .= "farmdir = $animalpath\n";
+ $content .= "farmhost = {$_SERVER['HTTP_HOST']}\n";
+ return io_saveFile(DOKU_INC . 'conf/farm.ini', $content);
+ }
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/farmer/all.less b/platform/www/lib/plugins/farmer/all.less
new file mode 100644
index 0000000..effff6f
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/all.less
@@ -0,0 +1,13 @@
+@import "css/chosen.less";
+
+.chosen-container-single .chosen-single abbr,
+.chosen-container-single .chosen-single div b,
+.chosen-container-single .chosen-search input[type="text"],
+.chosen-container-multi .chosen-choices li.search-choice .search-choice-close,
+.chosen-rtl .chosen-search input[type="text"] {
+ background-image: url(css/chosen-sprite.png);
+}
+
+@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi), only screen and (min-resolution: 1.5dppx) {
+ background-image: url(css/chosen-sprite.png);
+}
diff --git a/platform/www/lib/plugins/farmer/conf/default.php b/platform/www/lib/plugins/farmer/conf/default.php
new file mode 100644
index 0000000..33e10d4
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/conf/default.php
@@ -0,0 +1,9 @@
+<?php
+/**
+ * Default settings for the farmer plugin
+ *
+ * @author Michael Große <grosse@cosmocode.de>
+ */
+
+$conf['deactivated plugins'] = '';
+$conf['disable_new_plugins'] = 0;
diff --git a/platform/www/lib/plugins/farmer/conf/metadata.php b/platform/www/lib/plugins/farmer/conf/metadata.php
new file mode 100644
index 0000000..9b81338
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/conf/metadata.php
@@ -0,0 +1,9 @@
+<?php
+/**
+ * Options for the farmer plugin
+ *
+ * @author Michael Große <grosse@cosmocode.de>
+ */
+
+$meta['deactivated plugins'] = array('string');
+$meta['disable_new_plugins'] = array('onoff');
diff --git a/platform/www/lib/plugins/farmer/css/chosen-sprite.png b/platform/www/lib/plugins/farmer/css/chosen-sprite.png
new file mode 100644
index 0000000..c57da70
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/css/chosen-sprite.png
Binary files differ
diff --git a/platform/www/lib/plugins/farmer/css/chosen.less b/platform/www/lib/plugins/farmer/css/chosen.less
new file mode 100644
index 0000000..e7ea092
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/css/chosen.less
@@ -0,0 +1,450 @@
+/*!
+Chosen, a Select Box Enhancer for jQuery and Prototype
+by Patrick Filler for Harvest, http://getharvest.com
+
+Version 1.4.2
+Full source at https://github.com/harvesthq/chosen
+Copyright (c) 2011-2015 Harvest http://getharvest.com
+
+MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
+This file is generated by `grunt build`, do not edit it by hand.
+*/
+
+/* @group Base */
+.chosen-container {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ font-size: 13px;
+ zoom: 1;
+ *display: inline;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+.chosen-container * {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.chosen-container .chosen-drop {
+ position: absolute;
+ top: 100%;
+ left: -9999px;
+ z-index: 1010;
+ width: 100%;
+ border: 1px solid #aaa;
+ border-top: 0;
+ background: #fff;
+ box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15);
+}
+.chosen-container.chosen-with-drop .chosen-drop {
+ left: 0;
+}
+.chosen-container a {
+ cursor: pointer;
+}
+.chosen-container .search-choice .group-name, .chosen-container .chosen-single .group-name {
+ margin-right: 4px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ font-weight: normal;
+ color: #999999;
+}
+.chosen-container .search-choice .group-name:after, .chosen-container .chosen-single .group-name:after {
+ content: ":";
+ padding-left: 2px;
+ vertical-align: top;
+}
+
+/* @end */
+/* @group Single Chosen */
+.chosen-container-single .chosen-single {
+ position: relative;
+ display: block;
+ overflow: hidden;
+ padding: 0 0 0 8px;
+ height: 25px;
+ border: 1px solid #aaa;
+ border-radius: 5px;
+ background-color: #fff;
+ background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4));
+ background: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+ background: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+ background: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+ background: linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+ background-clip: padding-box;
+ box-shadow: 0 0 3px white inset, 0 1px 1px rgba(0, 0, 0, 0.1);
+ color: #444;
+ text-decoration: none;
+ white-space: nowrap;
+ line-height: 24px;
+}
+.chosen-container-single .chosen-default {
+ color: #999;
+}
+.chosen-container-single .chosen-single span {
+ display: block;
+ overflow: hidden;
+ margin-right: 26px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.chosen-container-single .chosen-single-with-deselect span {
+ margin-right: 38px;
+}
+.chosen-container-single .chosen-single abbr {
+ position: absolute;
+ top: 6px;
+ right: 26px;
+ display: block;
+ width: 12px;
+ height: 12px;
+ background: url('chosen-sprite.png') -42px 1px no-repeat;
+ font-size: 1px;
+}
+.chosen-container-single .chosen-single abbr:hover {
+ background-position: -42px -10px;
+}
+.chosen-container-single.chosen-disabled .chosen-single abbr:hover {
+ background-position: -42px -10px;
+}
+.chosen-container-single .chosen-single div {
+ position: absolute;
+ top: 0;
+ right: 0;
+ display: block;
+ width: 18px;
+ height: 100%;
+}
+.chosen-container-single .chosen-single div b {
+ display: block;
+ width: 100%;
+ height: 100%;
+ background: url('chosen-sprite.png') no-repeat 0px 2px;
+}
+.chosen-container-single .chosen-search {
+ position: relative;
+ z-index: 1010;
+ margin: 0;
+ padding: 3px 4px;
+ white-space: nowrap;
+}
+.chosen-container-single .chosen-search input[type="text"] {
+ margin: 1px 0;
+ padding: 4px 20px 4px 5px;
+ width: 100%;
+ height: auto;
+ outline: 0;
+ border: 1px solid #aaa;
+ background: white url('chosen-sprite.png') no-repeat 100% -20px;
+ background: url('chosen-sprite.png') no-repeat 100% -20px;
+ font-size: 1em;
+ font-family: sans-serif;
+ line-height: normal;
+ border-radius: 0;
+}
+.chosen-container-single .chosen-drop {
+ margin-top: -1px;
+ border-radius: 0 0 4px 4px;
+ background-clip: padding-box;
+}
+.chosen-container-single.chosen-container-single-nosearch .chosen-search {
+ position: absolute;
+ left: -9999px;
+}
+
+/* @end */
+/* @group Results */
+.chosen-container .chosen-results {
+ color: #444;
+ position: relative;
+ overflow-x: hidden;
+ overflow-y: auto;
+ margin: 0 4px 4px 0;
+ padding: 0 0 0 4px;
+ max-height: 240px;
+ -webkit-overflow-scrolling: touch;
+}
+.chosen-container .chosen-results li {
+ display: none;
+ margin: 0;
+ padding: 5px 6px;
+ list-style: none;
+ line-height: 15px;
+ word-wrap: break-word;
+ -webkit-touch-callout: none;
+}
+.chosen-container .chosen-results li.active-result {
+ display: list-item;
+ cursor: pointer;
+}
+.chosen-container .chosen-results li.disabled-result {
+ display: list-item;
+ color: #ccc;
+ cursor: default;
+}
+.chosen-container .chosen-results li.highlighted {
+ background-color: #3875d7;
+ background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc));
+ background-image: -webkit-linear-gradient(#3875d7 20%, #2a62bc 90%);
+ background-image: -moz-linear-gradient(#3875d7 20%, #2a62bc 90%);
+ background-image: -o-linear-gradient(#3875d7 20%, #2a62bc 90%);
+ background-image: linear-gradient(#3875d7 20%, #2a62bc 90%);
+ color: #fff;
+}
+.chosen-container .chosen-results li.no-results {
+ color: #777;
+ display: list-item;
+ background: #f4f4f4;
+}
+.chosen-container .chosen-results li.group-result {
+ display: list-item;
+ font-weight: bold;
+ cursor: default;
+}
+.chosen-container .chosen-results li.group-option {
+ padding-left: 15px;
+}
+.chosen-container .chosen-results li em {
+ font-style: normal;
+ text-decoration: underline;
+}
+
+/* @end */
+/* @group Multi Chosen */
+.chosen-container-multi .chosen-choices {
+ position: relative;
+ overflow: hidden;
+ margin: 0;
+ padding: 0 5px;
+ width: 100%;
+ height: auto !important;
+ height: 1%;
+ border: 1px solid #aaa;
+ background-color: #fff;
+ background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
+ background-image: -webkit-linear-gradient(#eeeeee 1%, #ffffff 15%);
+ background-image: -moz-linear-gradient(#eeeeee 1%, #ffffff 15%);
+ background-image: -o-linear-gradient(#eeeeee 1%, #ffffff 15%);
+ background-image: linear-gradient(#eeeeee 1%, #ffffff 15%);
+ cursor: text;
+}
+.chosen-container-multi .chosen-choices li {
+ float: left;
+ list-style: none;
+}
+.chosen-container-multi .chosen-choices li.search-field {
+ margin: 0;
+ padding: 0;
+ white-space: nowrap;
+}
+.chosen-container-multi .chosen-choices li.search-field input[type="text"] {
+ margin: 1px 0;
+ padding: 0;
+ height: 25px;
+ outline: 0;
+ border: 0 !important;
+ background: transparent !important;
+ box-shadow: none;
+ color: #999;
+ font-size: 100%;
+ font-family: sans-serif;
+ line-height: normal;
+ border-radius: 0;
+}
+.chosen-container-multi .chosen-choices li.search-choice {
+ position: relative;
+ margin: 3px 5px 3px 0;
+ padding: 3px 20px 3px 5px;
+ border: 1px solid #aaa;
+ max-width: 100%;
+ border-radius: 3px;
+ background-color: #eeeeee;
+ background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
+ background-image: -webkit-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: -moz-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: -o-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-size: 100% 19px;
+ background-repeat: repeat-x;
+ background-clip: padding-box;
+ box-shadow: 0 0 2px white inset, 0 1px 0 rgba(0, 0, 0, 0.05);
+ color: #333;
+ line-height: 13px;
+ cursor: default;
+}
+.chosen-container-multi .chosen-choices li.search-choice span {
+ word-wrap: break-word;
+}
+.chosen-container-multi .chosen-choices li.search-choice .search-choice-close {
+ position: absolute;
+ top: 4px;
+ right: 3px;
+ display: block;
+ width: 12px;
+ height: 12px;
+ background: url('chosen-sprite.png') -42px 1px no-repeat;
+ font-size: 1px;
+}
+.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover {
+ background-position: -42px -10px;
+}
+.chosen-container-multi .chosen-choices li.search-choice-disabled {
+ padding-right: 5px;
+ border: 1px solid #ccc;
+ background-color: #e4e4e4;
+ background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
+ background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ color: #666;
+}
+.chosen-container-multi .chosen-choices li.search-choice-focus {
+ background: #d4d4d4;
+}
+.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close {
+ background-position: -42px -10px;
+}
+.chosen-container-multi .chosen-results {
+ margin: 0;
+ padding: 0;
+}
+.chosen-container-multi .chosen-drop .result-selected {
+ display: list-item;
+ color: #ccc;
+ cursor: default;
+}
+
+/* @end */
+/* @group Active */
+.chosen-container-active .chosen-single {
+ border: 1px solid #5897fb;
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
+}
+.chosen-container-active.chosen-with-drop .chosen-single {
+ border: 1px solid #aaa;
+ -moz-border-radius-bottomright: 0;
+ border-bottom-right-radius: 0;
+ -moz-border-radius-bottomleft: 0;
+ border-bottom-left-radius: 0;
+ background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #eeeeee), color-stop(80%, #ffffff));
+ background-image: -webkit-linear-gradient(#eeeeee 20%, #ffffff 80%);
+ background-image: -moz-linear-gradient(#eeeeee 20%, #ffffff 80%);
+ background-image: -o-linear-gradient(#eeeeee 20%, #ffffff 80%);
+ background-image: linear-gradient(#eeeeee 20%, #ffffff 80%);
+ box-shadow: 0 1px 0 #fff inset;
+}
+.chosen-container-active.chosen-with-drop .chosen-single div {
+ border-left: none;
+ background: transparent;
+}
+.chosen-container-active.chosen-with-drop .chosen-single div b {
+ background-position: -18px 2px;
+}
+.chosen-container-active .chosen-choices {
+ border: 1px solid #5897fb;
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
+}
+.chosen-container-active .chosen-choices li.search-field input[type="text"] {
+ color: #222 !important;
+}
+
+/* @end */
+/* @group Disabled Support */
+.chosen-disabled {
+ opacity: 0.5 !important;
+ cursor: default;
+}
+.chosen-disabled .chosen-single {
+ cursor: default;
+}
+.chosen-disabled .chosen-choices .search-choice .search-choice-close {
+ cursor: default;
+}
+
+/* @end */
+/* @group Right to Left */
+.chosen-rtl {
+ text-align: right;
+}
+.chosen-rtl .chosen-single {
+ overflow: visible;
+ padding: 0 8px 0 0;
+}
+.chosen-rtl .chosen-single span {
+ margin-right: 0;
+ margin-left: 26px;
+ direction: rtl;
+}
+.chosen-rtl .chosen-single-with-deselect span {
+ margin-left: 38px;
+}
+.chosen-rtl .chosen-single div {
+ right: auto;
+ left: 3px;
+}
+.chosen-rtl .chosen-single abbr {
+ right: auto;
+ left: 26px;
+}
+.chosen-rtl .chosen-choices li {
+ float: right;
+}
+.chosen-rtl .chosen-choices li.search-field input[type="text"] {
+ direction: rtl;
+}
+.chosen-rtl .chosen-choices li.search-choice {
+ margin: 3px 5px 3px 0;
+ padding: 3px 5px 3px 19px;
+}
+.chosen-rtl .chosen-choices li.search-choice .search-choice-close {
+ right: auto;
+ left: 4px;
+}
+.chosen-rtl.chosen-container-single-nosearch .chosen-search,
+.chosen-rtl .chosen-drop {
+ left: 9999px;
+}
+.chosen-rtl.chosen-container-single .chosen-results {
+ margin: 0 0 4px 4px;
+ padding: 0 4px 0 0;
+}
+.chosen-rtl .chosen-results li.group-option {
+ padding-right: 15px;
+ padding-left: 0;
+}
+.chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div {
+ border-right: none;
+}
+.chosen-rtl .chosen-search input[type="text"] {
+ padding: 4px 5px 4px 20px;
+ background: white url('chosen-sprite.png') no-repeat -30px -20px;
+ background: url('chosen-sprite.png') no-repeat -30px -20px;
+ direction: rtl;
+}
+.chosen-rtl.chosen-container-single .chosen-single div b {
+ background-position: 6px 2px;
+}
+.chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b {
+ background-position: -12px 2px;
+}
+
+/* @end */
+/* @group Retina compatibility */
+@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi), only screen and (min-resolution: 1.5dppx) {
+ .chosen-rtl .chosen-search input[type="text"],
+ .chosen-container-single .chosen-single abbr,
+ .chosen-container-single .chosen-single div b,
+ .chosen-container-single .chosen-search input[type="text"],
+ .chosen-container-multi .chosen-choices .search-choice .search-choice-close,
+ .chosen-container .chosen-results-scroll-down span,
+ .chosen-container .chosen-results-scroll-up span {
+ background-image: url('chosen-sprite@2x.png') !important;
+ background-size: 52px 37px !important;
+ background-repeat: no-repeat !important;
+ }
+}
+/* @end */
diff --git a/platform/www/lib/plugins/farmer/deleted.files b/platform/www/lib/plugins/farmer/deleted.files
new file mode 100644
index 0000000..fea8b5e
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/deleted.files
@@ -0,0 +1,11 @@
+# This is a list of files that were present in previous plugin releases
+# but were removed later. An up to date plugin should not have any of
+# the files installed
+_test/validation.test.php
+action/handleAjax.php
+admin/createAnimal.php
+farm.php
+lang/en/createAnimal.txt
+lang/en/preload.txt
+scripts/plugins.js
+style.css
diff --git a/platform/www/lib/plugins/farmer/helper.php b/platform/www/lib/plugins/farmer/helper.php
new file mode 100644
index 0000000..53e7153
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/helper.php
@@ -0,0 +1,334 @@
+<?php
+/**
+ * DokuWiki Plugin farmer (Helper Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+class helper_plugin_farmer extends DokuWiki_Plugin {
+
+ protected $defaultPluginState = null;
+ protected $animalPluginState = array();
+
+ /**
+ * Returns the name of the current animal if any, false otherwise
+ *
+ * @return string|false
+ */
+ public function getAnimal() {
+ if(!isset($GLOBALS['FARMCORE'])) return false;
+ return $GLOBALS['FARMCORE']->getAnimal();
+ }
+
+ /**
+ * Get the farm config
+ *
+ * @return array
+ */
+ public function getConfig() {
+ if(!isset($GLOBALS['FARMCORE'])) return array();
+ return $GLOBALS['FARMCORE']->getConfig();
+ }
+
+ /**
+ * Was the current animal requested by host?
+ *
+ * @return bool
+ */
+ public function isHostbased() {
+ if(!isset($GLOBALS['FARMCORE'])) return false;
+ return $GLOBALS['FARMCORE']->isHostbased();
+ }
+
+ /**
+ * Was an animal requested that could not be found?
+ *
+ * @return bool
+ */
+ public function wasNotfound() {
+ if(!isset($GLOBALS['FARMCORE'])) return false;
+ return $GLOBALS['FARMCORE']->wasNotfound();
+ }
+
+ /**
+ * Guess the URL for an animal
+ *
+ * @param $animal
+ * @return string
+ */
+ public function getAnimalURL($animal) {
+ $config = $this->getConfig();
+
+ if(strpos($animal, '.') !== false) {
+ return 'http://' . $animal;
+ } elseif($config['base']['basedomain']) {
+ return 'http://' . $animal . '.' . $config['base']['basedomain'];
+ } else {
+ return DOKU_URL . '!' . $animal . '/';
+ }
+ }
+
+ /**
+ * List of all animals, i.e. directories within DOKU_FARMDIR without the template.
+ *
+ * @return array
+ */
+ public function getAllAnimals() {
+ $animals = array();
+ $list = glob(DOKU_FARMDIR . '*/conf/', GLOB_ONLYDIR);
+ foreach($list as $path) {
+ $animal = basename(dirname($path));
+ if($animal == '_animal') continue; // old template
+ $animals[] = $animal;
+ }
+ sort($animals);
+ return $animals;
+ }
+
+ /**
+ * checks wether $path is in under $container
+ *
+ * Also returns false if $path and $container are the same directory
+ *
+ * @param string $path
+ * @param string $container
+ * @return bool
+ */
+ public function isInPath($path, $container) {
+ $path = fullpath($path).'/';
+ $container = fullpath($container).'/';
+ if($path == $container) return false;
+ return (strpos($path, $container) === 0);
+ }
+
+ /**
+ * Check if the farm is correctly configured for this farmer plugin
+ *
+ * @return bool
+ */
+ public function checkFarmSetup() {
+ return defined('DOKU_FARMDIR') && isset($GLOBALS['FARMCORE']);
+ }
+
+ /**
+ * @param string $animalname
+ *
+ * @return bool
+ */
+ public function validateAnimalName($animalname) {
+ return preg_match("/^[a-z0-9]+([\\.\\-][a-z0-9]+)*$/i", $animalname) === 1;
+ }
+
+ /**
+ * Copy a file, or recursively copy a folder and its contents. Adapted for DokuWiki.
+ *
+ * @todo: needs tests
+ *
+ * @author Aidan Lister <aidan@php.net>
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ * @link http://aidanlister.com/2004/04/recursively-copying-directories-in-php/
+ *
+ * @param string $source Source path
+ * @param string $destination Destination path
+ * @param string $exclude Regular expression to exclude files or directories (complete with delimiters)
+ * @return bool Returns TRUE on success, FALSE on failure
+ */
+ function io_copyDir($source, $destination, $exclude = '') {
+ if($exclude && preg_match($exclude, $source)) {
+ return true;
+ }
+
+ if(is_link($source)) {
+ io_lock($destination);
+ $result = symlink(readlink($source), $destination);
+ io_unlock($destination);
+ return $result;
+ }
+
+ if(is_file($source)) {
+ io_lock($destination);
+ $result = copy($source, $destination);
+ io_unlock($destination);
+ return $result;
+ }
+
+ if(!is_dir($destination)) {
+ io_mkdir_p($destination);
+ }
+
+ $dir = @dir($source);
+ if($dir === false) return false;
+ while(false !== ($entry = $dir->read())) {
+ if($entry == '.' || $entry == '..') {
+ continue;
+ }
+
+ // recurse into directories
+ $this->io_copyDir("$source/$entry", "$destination/$entry", $exclude);
+ }
+
+ $dir->close();
+ return true;
+ }
+
+ /**
+ * get a list of all Plugins installed in the farmer wiki, regardless whether they are active or not.
+ *
+ * @param bool $all get all plugins, even disabled ones
+ * @return array
+ */
+ public function getAllPlugins($all = true) {
+
+ /** @var Doku_Plugin_Controller $plugin_controller */
+ global $plugin_controller;
+
+ $plugins = $plugin_controller->getList('', $all);
+
+ // filter out a few plugins
+ $plugins = array_filter(
+ $plugins, function ($item) {
+ if($item == 'farmer') return false;
+ if($item == 'extension') return false;
+ if($item == 'upgrade') return false;
+ if($item == 'testing') return false;
+ return true;
+ }
+ );
+
+ sort($plugins);
+ return $plugins;
+ }
+
+ /**
+ * Get the plugin states configured locally in the given animal
+ *
+ * Response is cached
+ *
+ * @param $animal
+ * @return array
+ */
+ public function getAnimalPluginLocalStates($animal) {
+ if(isset($this->animalPluginState[$animal])) return $this->animalPluginState[$animal];
+
+ $localfile = DOKU_FARMDIR . $animal . '/conf/plugins.local.php';
+ $plugins = array();
+ if(file_exists($localfile)) {
+ include($localfile);
+ }
+
+ $this->animalPluginState[$animal] = $plugins;
+ return $plugins;
+ }
+
+ /**
+ * Return the default state plugins would have in animals
+ *
+ * Response is cached
+ *
+ * @return array
+ */
+ public function getDefaultPluginStates() {
+ if(!is_null($this->defaultPluginState)) return $this->defaultPluginState;
+
+ $farmconf = $this->getConfig();
+ $all = $this->getAllPlugins();
+
+ $plugins = array();
+ foreach($all as $one) {
+ if($farmconf['inherit']['plugins']) {
+ $plugins[$one] = !plugin_isdisabled($one);
+ } else {
+ $plugins[$one] = true; // default state is enabled
+ }
+ }
+
+ ksort($plugins);
+ $this->defaultPluginState = $plugins;
+ return $plugins;
+ }
+
+ /**
+ * Return a structure giving detailed info about the state of all plugins in an animal
+ *
+ * @param $animal
+ * @return array
+ */
+ public function getAnimalPluginRealState($animal) {
+ $info = array();
+
+ $defaults = $this->getDefaultPluginStates();
+ $local = $this->getAnimalPluginLocalStates($animal);
+
+ foreach($defaults as $plugin => $set) {
+ $current = array(
+ 'name' => $plugin,
+ 'default' => $set,
+ 'actual' => $set,
+ 'isdefault' => true
+ );
+
+ if(isset($local[$plugin])) {
+ $current['actual'] = (bool) $local[$plugin];
+ $current['isdefault'] = false;
+ }
+
+ $info[$plugin] = $current;
+ }
+
+ ksort($info);
+ return $info;
+ }
+
+ /**
+ * Set the state of a plugin in an animal
+ *
+ * @param string $plugin
+ * @param string $animal
+ * @param int $state -1 = default, 1 = enabled, 0 = disabled
+ */
+ public function setPluginState($plugin, $animal, $state) {
+ $state = (int) $state;
+
+ $plugins = $this->getAnimalPluginLocalStates($animal);
+ if($state < 0) {
+ if(isset($plugins[$plugin])) unset($plugins[$plugin]);
+ } else {
+ $plugins[$plugin] = $state;
+ }
+
+ $this->writePluginConf($plugins, $animal);
+
+ // clear state cache
+ if(isset($this->animalPluginState[$animal])) unset($this->animalPluginState[$animal]);
+ }
+
+ /**
+ * Write the list of (deactivated) plugins as plugin configuration of an animal to file
+ *
+ * updates the plugin state cache
+ *
+ * @param array $plugins associative array with the key being the plugin name and the value 0 or 1
+ * @param string $animal Directory of the animal within DOKU_FARMDIR
+ */
+ public function writePluginConf($plugins, $animal) {
+ $pluginConf = '<?php' . "\n# plugins enabled and disabled by the farmer plugin\n";
+ foreach($plugins as $plugin => $status) {
+ $pluginConf .= '$plugins[\'' . $plugin . '\'] = ' . $status . ";\n";
+ }
+ io_saveFile(DOKU_FARMDIR . $animal . '/conf/plugins.local.php', $pluginConf);
+ touch(DOKU_FARMDIR . $animal . '/conf/local.php');
+
+ if(function_exists('opcache_invalidate')) {
+ opcache_invalidate(DOKU_FARMDIR . $animal . '/conf/plugins.local.php');
+ opcache_invalidate(DOKU_FARMDIR . $animal . '/conf/local.php');
+ }
+
+ $this->animalPluginState[$animal] = $plugins;
+ }
+}
diff --git a/platform/www/lib/plugins/farmer/includes/config.php b/platform/www/lib/plugins/farmer/includes/config.php
new file mode 100644
index 0000000..cd9aa67
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/includes/config.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * This overrides some values for the animals without having to configure it
+ *
+ * This file is added to the protected cascade for animals only.
+ * You should not edit it!
+ */
+global $FARMCORE;
+$conf['savedir'] = $FARMCORE->getAnimalDataDir();
+$conf['basedir'] = $FARMCORE->getAnimalBaseDir();
+$conf['upgradecheck'] = 0;
+
+
diff --git a/platform/www/lib/plugins/farmer/includes/plugins.php b/platform/www/lib/plugins/farmer/includes/plugins.php
new file mode 100644
index 0000000..cd8d7b2
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/includes/plugins.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * This overrides some values for the animals without having to configure it
+ *
+ * This file is added to the protected cascade for animals only.
+ * You should not edit it!
+ */
+$plugins['extension'] = 0;
+$plugins['upgrade'] = 0;
+$plugins['testing'] = 0;
+$plugins['farmer'] = 1;
+$plugins['farmsync'] = 0;
diff --git a/platform/www/lib/plugins/farmer/includes/template.php b/platform/www/lib/plugins/farmer/includes/template.php
new file mode 100644
index 0000000..1b0075c
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/includes/template.php
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="<?php echo $conf['lang'] ?>" dir="<?php echo $lang['direction'] ?>" class="popup no-js">
+<head>
+ <meta charset="utf-8"/>
+ <title>
+ <?php echo $title ?>
+ </title>
+ <script>(function (H) {
+ H.className = H.className.replace(/\bno-js\b/, 'js')
+ })(document.documentElement)</script>
+ <?php tpl_metaheaders() ?>
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
+ <?php echo tpl_favicon(array('favicon', 'mobile')) ?>
+ <?php tpl_includeFile('meta.html') ?>
+</head>
+
+<body>
+<!--[if lte IE 8 ]>
+<div id="IE8"><![endif]-->
+<div class="dokuwiki">
+ <div class="page">
+ <?php echo $body ?>
+ </div>
+</div>
+<!--[if lte IE 8 ]></div><![endif]-->
+</body>
+</html>
diff --git a/platform/www/lib/plugins/farmer/lang/de/lang.php b/platform/www/lib/plugins/farmer/lang/de/lang.php
new file mode 100644
index 0000000..b97a850
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/de/lang.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * German language file for farmer plugin
+ *
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+// menu entry for admin plugins
+$lang['menu'] = 'Farming';
+
+// tabs
+$lang['tab_setup'] = 'Farm Setup';
+$lang['tab_info'] = 'Info';
+$lang['tab_config'] = 'Konfiguration';
+$lang['tab_plugins'] = 'Plugins verwalten';
+$lang['tab_new'] = 'Neues Animal hinzufügen';
+$lang['tab_delete'] = 'Animal löschen';
+
+// setup
+$lang['preloadPHPForm'] = 'Farm aufsetzen';
+$lang['farm dir'] = 'Animal-Verzeichnis';
+$lang['htaccess setup'] = 'Farm code zu .htaccess hinzufügen?';
+$lang['submit'] = 'Abschicken';
+$lang['farmdir_missing'] = 'Bitte geben Sie das Verzeichnis an in dem die Animals gespeichert werden sollen.';
+$lang['farmdir_in_dokuwiki'] = 'Das Animal-Verzeichnis (%s) muss außerhalb des Farm-DokuWikis (%s) liegen.';
+$lang['farmdir_uncreatable'] = 'Das Animal-Verzeichnis (%s) konnte nicht erzeugt werden. Sind die Dateiberechtigungen korrekt?';
+$lang['farmdir_unwritable'] = 'Bitte stellen Sie sicher, dass der Webserver in das Animal-Verzeichnis (%s) schreiben darf!';
+$lang['farmdir_notEmpty'] = 'Das Animal-Verzeichnis (%s) muss leer sein!';
+$lang['preload creation success'] = 'Die Farm wurde erfolgreich angelegt.';
+$lang['preload creation error'] = 'Es ist ein Fehler beim Aufsetzen der Farm ausfgetreten.';
+$lang['overwrite_preload'] = 'Achtung: Ihre existierende: inc/preload.php wird überschrieben, wenn Sie diesen hier weitermachen!';
+
+// info
+$lang['animal'] = 'Animal Name / Domain';
+$lang['thisis'] = 'Diese Instanz ist';
+$lang['thisis.farmer'] = 'Der Farmer!';
+$lang['thisis.animal'] = 'Ein Animal!';
+$lang['baseinstall'] = 'Farmer Installation';
+$lang['animals'] = 'Animals';
+$lang['confdir'] = 'Konfigurationsverzeichnis dieser Instanz';
+$lang['savedir'] = 'data-Verzeichnis dieser Instanz';
+$lang['plugins'] = 'In dieser Instanz aktivierte Plugins';
+
+// config
+$lang['base'] = 'Grundkonfiguration';
+$lang['farm host'] = 'Farmer Host Name';
+$lang['base domain'] = 'Basis-Domain für Subdomain-Animals';
+$lang['conf_inherit'] = 'Farmer-Einstellungen die von Animals geerbt werden sollen';
+$lang['conf_inherit_main'] = 'Konfigurationseinstellungen';
+$lang['conf_inherit_acronyms'] = 'Abkürzungs-Definitionen';
+$lang['conf_inherit_entities'] = 'Entity-Definitionionen';
+$lang['conf_inherit_interwiki'] = 'Interwiki-Definitionen';
+$lang['conf_inherit_license'] = 'Lizenz-Definitionen';
+$lang['conf_inherit_mime'] = 'MIME-Type-Definitionen';
+$lang['conf_inherit_scheme'] = 'URL-Scheme-Definitionen';
+$lang['conf_inherit_smileys'] = 'Smiley-Definitionen';
+$lang['conf_inherit_wordblock'] = 'Spamfiltereinträge';
+$lang['conf_inherit_userstyle'] = 'Nutzer-Styles';
+$lang['conf_inherit_userscript'] = 'Nutzer-Scripts';
+$lang['conf_inherit_styleini'] = 'Anpassungen an Template-Styles';
+$lang['conf_inherit_users'] = 'Benutzer (nur Plain Auth)';
+$lang['conf_inherit_plugins'] = 'Plugin-Zustand';
+$lang['conf_inherit_yes'] = 'vom Farmer geerbt';
+$lang['conf_inherit_no'] = 'unabhängig vom Farmer';
+$lang['conf_notfound'] = 'Verhalten bei zugriff auf nicht-existierende Animals';
+$lang['conf_notfound_farmer'] = 'Zeige das Farmer-Wiki';
+$lang['conf_notfound_404'] = 'Zeige eine 404-Fehlerseite';
+$lang['conf_notfound_list'] = 'Zeige eine Liste der existierenden Animals';
+$lang['conf_notfound_redirect'] = 'Leite auf untenstehende URL um';
+$lang['conf_notfound_url'] = 'URL auf die umgeleitet werden soll wenn oben ausgewählt';
+$lang['save'] = 'Speichern';
+
+// new
+$lang['animal template'] = 'Bestehendes Animal kopieren';
+$lang['animal creation success'] = 'Das Animal "%s" wurde erfolgreich angelegt.';
+$lang['animal creation error'] = 'Es gab einen Fehler beim Anlegen des Animals.';
+$lang['animal configuration'] = 'Animal Grundkonfiguration';
+$lang['animal administrator'] = 'Animal Administrator';
+$lang['noUsers'] = 'Keine Benutzer erzeugen';
+$lang['importUsers'] = 'Alle Benutzeraccounts des Farmers in das neue Animal kopieren';
+$lang['currentAdmin'] = 'Den aktuellen Benutzer als Admin setzen';
+$lang['newAdmin'] = 'Neuen Benutzer "admin" anlegen';
+$lang['admin password'] = 'Passwort für den neuen Administrator';
+$lang['animalname_missing'] = 'Bitte geben Sie einen Namen für das neue Animal an.';
+$lang['animalname_invalid'] = ' Der Name des Animals darf nur aus Buchstaben und Ziffern sowie aus Bindestrichen und Punkten (nicht am Anfang oder Ende) bestehen.';
+$lang['animalname_preexisting'] = 'Ein Animal mit diesem Namen existiert bereits.';
+$lang['adminPassword_empty'] = 'Das Passwort für den neuen Administrator darf nicht leer sein.';
+$lang['animal template copy error'] = 'Es gab ein Problem beim Kopieren von %s aus dem existierenden Animal in das neue.';
+
+// plugins
+$lang['bulkSingleSwitcher'] = 'Ein einzelnes Animal bearbeiten oder alle aufeinmal?';
+$lang['bulkEdit'] = 'Alle Animals bearbeiten';
+$lang['singleEdit'] = 'Ein einzelnes Animal bearbeiten';
+$lang['bulkEditForm'] = 'Plugins in allen Animals ein- oder ausschalten';
+$lang['activate'] = 'Aktivieren';
+$lang['deactivate'] = 'Deaktivieren';
+$lang['singleEditForm'] = 'Plugins eines spezifischen Animals bearbeiten';
+$lang['plugindone'] = 'Plugin states updated';
+$lang['plugin'] = 'Plugin';
+$lang['plugin_on'] = 'an';
+$lang['plugin_off'] = 'aus';
+$lang['plugin_default'] = 'Voreinstellung';
+$lang['plugin_enabled'] = 'Aktiviert';
+$lang['plugin_disabled'] = 'Deaktiviert';
+$lang['js']['animalSelect'] = 'Wählen Sie ein Animal';
+$lang['js']['pluginSelect'] = 'Wählen Sie ein Plugin';
+
+// delete
+$lang['delete_animal'] = 'Animal zum Löschen auswählen';
+$lang['delete_confirm'] = 'Name des Animals erneut eingeben, um Löschen zu bestätigen';
+$lang['delete'] = 'Animal und alle darin gespeicherten Daten löschen';
+$lang['delete_noanimal'] = 'Bitte wählen sie ein Animal zum Löschen aus';
+$lang['delete_mismatch'] = 'Bestätigung stimmt nicht mit Animalnamen überein. Nicht gelöscht.';
+$lang['delete_invalid'] = 'Invalider Animalname. Nicht gelöscht.';
+$lang['delete_success'] = 'Animal erfolgreich gelöscht.';
+$lang['delete_fail'] = 'Einige Dateien konnten nicht gelöscht werden. Sie sollten diese manuell entfernen.';
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/farmer/lang/de/notfound_404.txt b/platform/www/lib/plugins/farmer/lang/de/notfound_404.txt
new file mode 100644
index 0000000..bac18d9
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/de/notfound_404.txt
@@ -0,0 +1,3 @@
+====== 404 Nicht gefunden ======
+
+Die angeforderte Ressource konnte nicht gefunden werden
diff --git a/platform/www/lib/plugins/farmer/lang/de/notfound_list.txt b/platform/www/lib/plugins/farmer/lang/de/notfound_list.txt
new file mode 100644
index 0000000..300ba7f
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/de/notfound_list.txt
@@ -0,0 +1,3 @@
+====== Wiki nicht gefunden ======
+
+Das angeforderte Wiki konnte nicht gefunden werden. Bitte wählen Sie aus der folgenden Liste der vorhandenen Wikis.
diff --git a/platform/www/lib/plugins/farmer/lang/de/settings.php b/platform/www/lib/plugins/farmer/lang/de/settings.php
new file mode 100644
index 0000000..661a3ec
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/de/settings.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * German language file for farmer plugin
+ *
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+// keys need to match the config setting name
+$lang['deactivated plugins'] = 'Kommaseparierte Liste an Plugins die in neuen Animals automatisch deaktiviert werden sollen.';
+
+
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/farmer/lang/de/tab_config.txt b/platform/www/lib/plugins/farmer/lang/de/tab_config.txt
new file mode 100644
index 0000000..299c979
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/de/tab_config.txt
@@ -0,0 +1 @@
+Hier können die grundlegenden Eigenschaften der Farm konfiguriert werden. Beachten Sie, dass alle Einstellungen hier alle Animals beeinflussen werden.
diff --git a/platform/www/lib/plugins/farmer/lang/de/tab_delete.txt b/platform/www/lib/plugins/farmer/lang/de/tab_delete.txt
new file mode 100644
index 0000000..795487a
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/de/tab_delete.txt
@@ -0,0 +1 @@
+Sie können bestehende Animals hier löschen. Dies löscht **alle Daten, inklusive Seiten und Medieninhalte** des gewählten Animals. **Dies kann nicht rückgängig gemacht werden!**
diff --git a/platform/www/lib/plugins/farmer/lang/de/tab_info.txt b/platform/www/lib/plugins/farmer/lang/de/tab_info.txt
new file mode 100644
index 0000000..11faab9
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/de/tab_info.txt
@@ -0,0 +1 @@
+Dieses Wiki ist Teil eines Farm-Setups. Nähere Details finden Sie unten.
diff --git a/platform/www/lib/plugins/farmer/lang/de/tab_new.txt b/platform/www/lib/plugins/farmer/lang/de/tab_new.txt
new file mode 100644
index 0000000..a62ef02
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/de/tab_new.txt
@@ -0,0 +1 @@
+Hier können Sie ein neues Animal anlegen.
diff --git a/platform/www/lib/plugins/farmer/lang/de/tab_plugins.txt b/platform/www/lib/plugins/farmer/lang/de/tab_plugins.txt
new file mode 100644
index 0000000..8277c98
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/de/tab_plugins.txt
@@ -0,0 +1 @@
+Hier können Sie Plugins für einzelne oder alle Animals ein- oder ausschalten.
diff --git a/platform/www/lib/plugins/farmer/lang/de/tab_setup.txt b/platform/www/lib/plugins/farmer/lang/de/tab_setup.txt
new file mode 100644
index 0000000..e0dc63b
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/de/tab_setup.txt
@@ -0,0 +1 @@
+Ihr Wiki ist noch nicht für Farming mit dem Farmer-Plugin eingerichtet. Bitte nutzen Sie den folgenden Dialog zum Aufsetzen der Farm.
diff --git a/platform/www/lib/plugins/farmer/lang/en/lang.php b/platform/www/lib/plugins/farmer/lang/en/lang.php
new file mode 100644
index 0000000..da49836
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/lang.php
@@ -0,0 +1,128 @@
+<?php
+/**
+ * English language file for farmer plugin
+ *
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+
+// menu entry for admin plugins
+$lang['menu'] = 'Farming';
+
+// tabs
+$lang['tab_setup'] = 'Farm Setup';
+$lang['tab_info'] = 'Info';
+$lang['tab_config'] = 'Configuration';
+$lang['tab_plugins'] = 'Manage Plugins';
+$lang['tab_new'] = 'Add new Animal';
+$lang['tab_delete'] = 'Delete Animal';
+
+// setup
+$lang['preloadPHPForm'] = 'Initialize Farming';
+$lang['farm dir'] = 'Animal directory';
+$lang['htaccess setup'] = 'Add farm code to .htaccess?';
+$lang['submit'] = 'Submit';
+$lang['farmdir_missing'] = 'Please enter a directory where the Animals should be stored.';
+$lang['farmdir_in_dokuwiki'] = 'The Animal directory (%s) must be outside of the Farm dokuwiki (%s).';
+$lang['farmdir_uncreatable'] = 'The Animal directory (%s) could not be created. Are the permissions correct?';
+$lang['farmdir_unwritable'] = 'Please make sure that the webserver has write access in the Animal directory (%s)!';
+$lang['farmdir_notEmpty'] = 'The Animal directory (%s) must be empty.';
+$lang['preload creation success'] = 'Farming has been succesfully initialized.';
+$lang['preload creation error'] = 'There was an error during Farming initialization.';
+$lang['overwrite_preload'] = 'Warning: Your existing inc/preload.php will be overwritten when continuing here!';
+
+// info
+$lang['animal'] = 'Animal Name / Domain';
+$lang['thisis'] = 'Instance is';
+$lang['thisis.farmer'] = 'The farmer!';
+$lang['thisis.animal'] = 'An Animal!';
+$lang['baseinstall'] = 'Farmer Install';
+$lang['animals'] = 'Animals';
+$lang['confdir'] = 'Instance Configuration Directory';
+$lang['savedir'] = 'Instance Data Directory';
+$lang['plugins'] = 'Plugins active in this instance';
+
+// config
+$lang['base'] = 'Base Configuration';
+$lang['farm host'] = 'Farmer Host Name';
+$lang['base domain'] = 'Base Domain for subdomain Animals';
+$lang['conf_inherit'] = 'Farmer Settings Animals should inherit';
+$lang['conf_inherit_main'] = 'Configuration Settings';
+$lang['conf_inherit_acronyms'] = 'Acronym Definitions';
+$lang['conf_inherit_entities'] = 'Entity Definitions';
+$lang['conf_inherit_interwiki'] = 'Interwiki Definitions';
+$lang['conf_inherit_license'] = 'License Definitions';
+$lang['conf_inherit_mime'] = 'MIME Type Definitions';
+$lang['conf_inherit_scheme'] = 'URL Scheme Definitions';
+$lang['conf_inherit_smileys'] = 'Smiley Definitions';
+$lang['conf_inherit_wordblock'] = 'Spam Blacklist Entries';
+$lang['conf_inherit_userstyle'] = 'User Styles';
+$lang['conf_inherit_userscript'] = 'User Scripts';
+$lang['conf_inherit_styleini'] = 'Template style customizations';
+$lang['conf_inherit_users'] = 'Users (Plain Auth only)';
+$lang['conf_inherit_plugins'] = 'Plugin State';
+$lang['conf_inherit_yes'] = 'inherited from farmer';
+$lang['conf_inherit_no'] = 'independent from farmer';
+$lang['conf_notfound'] = 'Behavior on accessing nonexistent Animals';
+$lang['conf_notfound_farmer'] = 'Show the farmer wiki';
+$lang['conf_notfound_404'] = 'Show a 404 error page';
+$lang['conf_notfound_list'] = 'Show a list of available animals';
+$lang['conf_notfound_redirect'] = 'Redirect to the URL below';
+$lang['conf_notfound_url'] = 'URL to redirect to if selected above';
+$lang['save'] = 'Save';
+
+// new
+$lang['animal template'] = 'Copy existing Animal';
+$lang['animal creation success'] = 'The Animal "%s" has been successfully created.';
+$lang['animal creation error'] = 'There was an error while creating the Animal.';
+$lang['animal configuration'] = 'Basic Animal configuration';
+$lang['inherit user registration'] = 'Inherit user registration setting from farmer';
+$lang['enable user registration'] = 'Allow users to register themselves';
+$lang['disable user registration'] = 'Disable user register';
+$lang['animal administrator'] = 'Animal administrator';
+$lang['noUsers'] = 'Do not create any users';
+$lang['importUsers'] = 'Import all users of the Farmer to the new Animal';
+$lang['currentAdmin'] = 'Set the current user as admin';
+$lang['newAdmin'] = 'Create new admin user "admin"';
+$lang['admin password'] = 'Password for the new admin';
+$lang['animalname_missing'] = 'Please enter a name for the new Animal.';
+$lang['animalname_invalid'] = 'The Animal name may only contain alphanumeric characters and dots/hyphens (but not as first or last character).';
+$lang['animalname_preexisting'] = 'An Animal with that name already exists.';
+$lang['adminPassword_empty'] = 'The password for the new admin account must not be empty.';
+$lang['animal template copy error'] = 'There was a problem copying %s from the existing Animal to the new one.';
+$lang['aclpolicy missing/bad'] = 'Please choose an initial ACL policy from the dropdown.';
+
+// plugins
+$lang['bulkSingleSwitcher'] = 'Edit a single Animal or all at once?';
+$lang['bulkEdit'] = 'Bulk edit all Animals';
+$lang['singleEdit'] = 'Edit a single Animal';
+$lang['bulkEditForm'] = 'Activate or deactivate a plugin in all Animals';
+$lang['matrixEdit'] = 'Edit Animal/Plugin matrix';
+$lang['default'] = 'Set to default';
+$lang['activate'] = 'Activate';
+$lang['deactivate'] = 'Deactivate';
+$lang['singleEditForm'] = 'Edit the plugins of a specific Animal';
+$lang['plugindone'] = 'Plugin states updated';
+$lang['plugin'] = 'Plugin';
+$lang['plugin_on'] = 'on';
+$lang['plugin_off'] = 'off';
+$lang['plugin_default'] = 'Default';
+$lang['plugin_enabled'] = 'Enabled';
+$lang['plugin_disabled'] = 'Disabled';
+$lang['js']['animalSelect'] = 'Select an animal';
+$lang['js']['pluginSelect'] = 'Select a plugin';
+$lang['disable_new_plugins'] = 'The plugin has been disabled by default. You can enable it here or in specific animals only.';
+
+
+// delete
+$lang['delete_animal'] = 'Select Animal to delete';
+$lang['delete_confirm'] = 'Please type the Animal name to confirm';
+$lang['delete'] = 'Delete the Animal and all its data';
+
+$lang['delete_noanimal'] = 'Please select an Animal to delete';
+$lang['delete_mismatch'] = 'Confirmation does not match Animal name. Not deleted.';
+$lang['delete_invalid'] = 'Invalid Animal name. Not deleted.';
+$lang['delete_success'] = 'Animal successfully deleted.';
+$lang['delete_fail'] = 'Some files could not be deleted, you should clean up manuallly.';
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/farmer/lang/en/notfound_404.txt b/platform/www/lib/plugins/farmer/lang/en/notfound_404.txt
new file mode 100644
index 0000000..b023e79
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/notfound_404.txt
@@ -0,0 +1,3 @@
+====== 404 Not Found ======
+
+The requested resource could not be found.
diff --git a/platform/www/lib/plugins/farmer/lang/en/notfound_list.txt b/platform/www/lib/plugins/farmer/lang/en/notfound_list.txt
new file mode 100644
index 0000000..9e4633a
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/notfound_list.txt
@@ -0,0 +1,3 @@
+====== Wiki Not Found ======
+
+The requested Wiki could not be found. Please refer to the list of available Wikis below.
diff --git a/platform/www/lib/plugins/farmer/lang/en/settings.php b/platform/www/lib/plugins/farmer/lang/en/settings.php
new file mode 100644
index 0000000..0e854ac
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/settings.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * english language file for farmer plugin
+ *
+ * @author Michael Große <grosse@cosmocode.de>
+ */
+
+// keys need to match the config setting name
+$lang['deactivated plugins'] = 'Comma-separated list of plugins which are deactivated by default in new animals.';
+$lang['disable_new_plugins'] = 'Automatically disable plugins after they have been newly installed in the farmer? (only when installed via extension manager)';
+
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/farmer/lang/en/tab_config.txt b/platform/www/lib/plugins/farmer/lang/en/tab_config.txt
new file mode 100644
index 0000000..cedf536
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/tab_config.txt
@@ -0,0 +1 @@
+Here the basic behavior of the farm is configured. Please be aware that changing options here will affect all animals.
diff --git a/platform/www/lib/plugins/farmer/lang/en/tab_config_help.txt b/platform/www/lib/plugins/farmer/lang/en/tab_config_help.txt
new file mode 100644
index 0000000..0ebf7c2
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/tab_config_help.txt
@@ -0,0 +1,26 @@
+===== Farm Configuration =====
+
+All settings made here are saved to the Farmer's ''conf/farm.ini''.
+
+==== Base Configuration ====
+
+The **Farmer's host name** was set automatically during the setup, but you can change it here. It
+will be used to detect if a request was made directly to the Farmer when using host based farms. This
+should be a full qualified hostname (''foo.example.com'' instead of just ''foo'').
+
+When using a subdomain wildcard setup you should specify the main domain in the **Base Domain** setting.
+Eg. if you specify ''example.com'' it is assumed an animal named ''foo'' is reachable via ''foo.example.com''.
+The base domain is only appended to animal names not containing any dots.
+
+==== Inheritence ====
+
+Here you can specify what configuration settings made in the farmer should be used as defaults in the animals.
+Animals can still override the Farmer settings in their own configuration files. When inheritance is disabled,
+DokuWiki's default settings are the defaults for all animals as well.
+
+==== Non Existing Animals ====
+
+By default, when accessing a non-existing animal no error message is shown. You can pick between different
+behavior here. Be sure that your Farmer's host name is set up correctly above, before switching away from the
+default.
+
diff --git a/platform/www/lib/plugins/farmer/lang/en/tab_delete.txt b/platform/www/lib/plugins/farmer/lang/en/tab_delete.txt
new file mode 100644
index 0000000..cd7f67c
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/tab_delete.txt
@@ -0,0 +1 @@
+You can delete existing animals here. This deletes **all data, including pages and media files** of the selected animal. **This is irreversible!**
diff --git a/platform/www/lib/plugins/farmer/lang/en/tab_info.txt b/platform/www/lib/plugins/farmer/lang/en/tab_info.txt
new file mode 100644
index 0000000..f3d7a83
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/tab_info.txt
@@ -0,0 +1 @@
+This wiki is part of a farm setup. Check the details below.
diff --git a/platform/www/lib/plugins/farmer/lang/en/tab_new.txt b/platform/www/lib/plugins/farmer/lang/en/tab_new.txt
new file mode 100644
index 0000000..ffe899c
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/tab_new.txt
@@ -0,0 +1 @@
+You can create a new animal here.
diff --git a/platform/www/lib/plugins/farmer/lang/en/tab_new_help.txt b/platform/www/lib/plugins/farmer/lang/en/tab_new_help.txt
new file mode 100644
index 0000000..0445e82
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/tab_new_help.txt
@@ -0,0 +1,31 @@
+===== Animal Creation =====
+
+Animals are the independent subwikis of a Dokuwiki farm. To create a new animal a name has to be assigned.
+
+==== Animal Names ====
+
+For .htaccess based setups this should be a single word. For domain based setups you should provide either a full qualified domain name.
+If you use a wildcard subdomain setup you can also just provide the hostname part if you set a base domain in the configuration.
+
+Examples:
+
+ * .htaccess based: ''foo'' for an animal accessible at ''%%http://example.org/dokuwiki/!foo/%%''
+ * domain based: ''%%www.foo.com%%'' for an animal accessible at ''%%http://www.foo.com/%%''
+ * sub domain based: ''foo'' for an animal accessible at ''%%http://foo.example.com/%%''
+
+The latter two require the appropriate DNS setup!
+
+==== Copy Animal ====
+
+You can select an existing animal to base the new one on. All configuration, page, media and meta data will be copied
+to the new animal. Page and media revisions will not be copied.
+
+The title and logo image will be overwritten to make sure you can distinguish the new from the old wiki.
+
+==== Animal Administrator ====
+
+The Animal will be a fully functional wiki with it's own user base. You will need at least one administrative user
+to configure it. You can copy your current user or all users from the farmer or create a completely new user for the Animal.
+
+You can choose to not create any users. This should only be chosen when inheriting users from the Farmer was enabled in the
+configuration tab or you copied a different animal with existing users.
diff --git a/platform/www/lib/plugins/farmer/lang/en/tab_plugins.txt b/platform/www/lib/plugins/farmer/lang/en/tab_plugins.txt
new file mode 100644
index 0000000..78e8021
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/tab_plugins.txt
@@ -0,0 +1 @@
+You can activate or deactivate either a plugin for all animals in a single bulk operation or you can edit the plugins of a specific animal.
diff --git a/platform/www/lib/plugins/farmer/lang/en/tab_plugins_help.txt b/platform/www/lib/plugins/farmer/lang/en/tab_plugins_help.txt
new file mode 100644
index 0000000..44af5fc
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/tab_plugins_help.txt
@@ -0,0 +1,16 @@
+===== Plugin Management =====
+
+The extension manager is disabled in all Animals. Plugins can only be installed in the Farmer and only the Farmer
+may control what plugins are enabled or disabled in all Animals. This is done through this interface.
+
+A plugin can have three states in an Animal. It can be enabled (on) or disabled (off), or it can have the default state. The
+default means that the state of a Plugin was not explicitly set for an Animal. Normally the default state is on.
+
+When Animals are configured to inherit the Plugin state of the Farmer, a plugin in default state will have the same
+state as in the Farmer. Eg. when you disable a plugin in the Farmer it will disable the plugin for all Animals which
+did not have the state of this plugin explicitly set.
+
+There are three ways to manage plugins: You can either pick a single plugin and set its state to the same value in all animals
+or you can pick a specific animal and configure all the plugin states within that animal only. The third option allows you to
+see and edit the state of all plugins in all animals at once. This option may not be feasible of you have a large amount
+of animals.
diff --git a/platform/www/lib/plugins/farmer/lang/en/tab_setup.txt b/platform/www/lib/plugins/farmer/lang/en/tab_setup.txt
new file mode 100644
index 0000000..19ae730
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/tab_setup.txt
@@ -0,0 +1 @@
+Your Wiki is not set up as a farm managed by the Farmer Plugin, yet. Please use the wizard below to enable farming.
diff --git a/platform/www/lib/plugins/farmer/lang/en/tab_setup_help.txt b/platform/www/lib/plugins/farmer/lang/en/tab_setup_help.txt
new file mode 100644
index 0000000..f81532f
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/en/tab_setup_help.txt
@@ -0,0 +1,37 @@
+===== About Farms =====
+
+Farms allow you to have a single DokuWiki installation (The "Farmer") that powers an arbitrary number of
+other wikis (The "Animals"). You install plugins and templates in the Farmer only and then make them available
+through to the animals. You only need to keep one wiki uptodate and all other wikis just use the same code base.
+
+After completing this setup step your current DokuWiki (the one you're looking at) will be known as the "Farmer".
+
+===== What does this Setup do? =====
+
+This setup wizard will do three things:
+
+ - create a ''inc/preload.php'' file
+ - create a ''conf/farm.ini'' file
+ - optionally append to the ''.htaccess'' file
+
+The ''preload.php'' is a file that is loaded at the very beginning of loading DokuWiki. Here the farm mechanism is
+inititialized. The Farmer plugin will detect if the current request should access an Animal or the Farmer and
+reconfigure everything accordingly.
+
+The ''conf/farm.ini'' contains the basic configuration of the farm setup. Most importantly it will contain the
+location where all the animal's data will be stored.
+
+The ''.htaccess'' modification makes animals accessible through the //bang!// mechanism. (See below)
+
+===== What to fill in? =====
+
+The **Animal Directory** is where a new directory is created for each Animal you create. This directory has to be
+outside your current DokuWiki. You can specify a relative directory like ''../animals''.
+
+Enabling the **.htaccess** support is recommended. This feature requires Apache with mod_rewrite and .htaccess support.
+
+When enabled, your animals will be accessible under the farmer's URL using the //bang!// mechanism. Eg. if your farmer is
+running at ''%%http://www.example.com/dokuwiki/%%'', an animal will be accessible at
+''%%http://www.example.com/dokuwiki/!animal%%''.
+
+If you do not enable this, you will have to configure your Webserver and DNS to access the animals.
diff --git a/platform/www/lib/plugins/farmer/lang/fr/lang.php b/platform/www/lib/plugins/farmer/lang/fr/lang.php
new file mode 100644
index 0000000..8171952
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/lang.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Schplurtz le Déboulonné <schplurtz@laposte.net>
+ * @author ubibene <services.m@benard.info>
+ */
+$lang['menu'] = 'Élevage';
+$lang['tab_setup'] = 'Configuration de la ferme';
+$lang['tab_info'] = 'Info';
+$lang['tab_config'] = 'Configuration';
+$lang['tab_plugins'] = 'Gestion des greffons';
+$lang['tab_new'] = 'Ajouter un animal';
+$lang['tab_delete'] = 'Supprimer un animal';
+$lang['preloadPHPForm'] = 'Initialiser la ferme';
+$lang['farm dir'] = 'Dossier des animaux';
+$lang['htaccess setup'] = 'Ajouter le code d\'élevage au .htaccess ?';
+$lang['submit'] = 'Envoyer';
+$lang['farmdir_missing'] = 'Veuillez entrer un dossier où stocker les animaux.';
+$lang['farmdir_in_dokuwiki'] = 'Le dossier des animaux (%s) doit se trouver hors de la ferme DokuWiki (%s).';
+$lang['farmdir_uncreatable'] = 'Impossible de créer le dossier (%s). Les permissions sont-elles correctes ?';
+$lang['farmdir_unwritable'] = 'Veuillez vous assurer que le serveur web dispose d\'un accès en écriture au dossier des animaux (%s) !';
+$lang['farmdir_notEmpty'] = 'Le dossier des animaux (%s) doit être vide.';
+$lang['preload creation success'] = 'Configuration de la ferme réalisée.';
+$lang['preload creation error'] = 'Il y a eu une erreur lors de l\'initialisation de l\'élevage.';
+$lang['overwrite_preload'] = 'Attention, votre fichier existant inc/preload.php va être écrasé si vous continuez !';
+$lang['animal'] = 'Nom d\'animal / Domaine';
+$lang['thisis'] = 'L\'instance est';
+$lang['thisis.farmer'] = 'Le fermier!';
+$lang['thisis.animal'] = 'Un animal!';
+$lang['baseinstall'] = 'Installation de la ferme';
+$lang['animals'] = 'Animaux';
+$lang['confdir'] = 'Dossier de configuration de l\'instance';
+$lang['savedir'] = 'Dossier des données de l\'instance';
+$lang['plugins'] = 'Greffons actifs dans cette instance';
+$lang['base'] = 'Configuration de base';
+$lang['farm host'] = 'Nom d\'hôte du fermier';
+$lang['base domain'] = 'Domaine de base pour les animaux par sous-domaine';
+$lang['conf_inherit'] = 'Réglage dont les animaux vont hériter';
+$lang['conf_inherit_main'] = 'Options de configuraton';
+$lang['conf_inherit_acronyms'] = 'Définitions des acronymes';
+$lang['conf_inherit_entities'] = 'Définitions des entités';
+$lang['conf_inherit_interwiki'] = 'Liens interwiki';
+$lang['conf_inherit_license'] = 'Licence du contenu';
+$lang['conf_inherit_mime'] = 'Types MIME';
+$lang['conf_inherit_scheme'] = 'Schémas d\'URL';
+$lang['conf_inherit_smileys'] = 'Frimousses';
+$lang['conf_inherit_wordblock'] = 'Liste noire des spammeurs';
+$lang['conf_inherit_userstyle'] = 'Styles utilisateur';
+$lang['conf_inherit_userscript'] = 'Scripts utilisateur';
+$lang['conf_inherit_styleini'] = 'Personnalisations du style du thème ';
+$lang['conf_inherit_users'] = 'utilisateurs (Auth texte seulement)';
+$lang['conf_inherit_plugins'] = 'État des greffons';
+$lang['conf_inherit_yes'] = 'hérité du fermier';
+$lang['conf_inherit_no'] = 'indépendant du fermier';
+$lang['conf_notfound'] = 'Comportement lors d\'un accès à un animal inexistant';
+$lang['conf_notfound_farmer'] = 'Montrer le wiki fermier';
+$lang['conf_notfound_404'] = 'Montrer une page 404';
+$lang['conf_notfound_list'] = 'Montrer la liste des animaux disponibles';
+$lang['conf_notfound_redirect'] = 'Rediriger vers l\'URL ci-dessous';
+$lang['conf_notfound_url'] = 'URL de redirection si sélectionné ci-dessus';
+$lang['save'] = 'Enregistrer';
+$lang['animal template'] = 'Copier un animal existant';
+$lang['animal creation success'] = 'L\'animal "%s" a été créé avec succès.';
+$lang['animal creation error'] = 'Il y a eu une erreur lors de la création de l\'animal.';
+$lang['animal configuration'] = 'Configuration de base de l\'animal';
+$lang['inherit user registration'] = 'Hériter le réglage d\'enregistrement des utilisateurs.';
+$lang['enable user registration'] = 'Autoriser les utilisateurs à s\'enregistrer';
+$lang['disable user registration'] = 'Désactiver l\'enregistrement des utilisateurs';
+$lang['animal administrator'] = 'Administrateur d\'animal';
+$lang['noUsers'] = 'Ne pas créer d\'utilisateur';
+$lang['importUsers'] = 'Exporter les utilisateurs du fermier vers l\'animal';
+$lang['currentAdmin'] = 'Définir l\'utilisateur courant comme admin';
+$lang['newAdmin'] = 'Créer un nouvel administrateur "admin"';
+$lang['admin password'] = 'Mot de passe du nouvel admin';
+$lang['animalname_missing'] = 'Veuillez saisir le nom du nouvel animal.';
+$lang['animalname_invalid'] = 'Le nom de l\'animal ne doit contenir que des caractères alphanumériques et les points et tirets (mais pas en premier ou en dernier).';
+$lang['animalname_preexisting'] = 'Un animal avec ce nom existe déjà.';
+$lang['adminPassword_empty'] = 'Le mot de passe du nouvel administrateur ne peux pas être vide.';
+$lang['animal template copy error'] = 'Il y a eu un problème en copiant %s de l\'animal existant vers le nouveau.';
+$lang['aclpolicy missing/bad'] = 'Veuillez choisir une politique d\'ACL initiale.';
+$lang['bulkSingleSwitcher'] = 'Modifier un seul animal ou tout le troupeau ?';
+$lang['bulkEdit'] = 'Modifier tout le troupeau';
+$lang['singleEdit'] = 'Modifier un seul animal';
+$lang['bulkEditForm'] = 'Activer ou désactiver un greffon sur tout le troupeau.';
+$lang['matrixEdit'] = 'Modifier la matrice Animal/Greffon';
+$lang['default'] = 'Hériter du fermier';
+$lang['activate'] = 'Activer';
+$lang['deactivate'] = 'Désactiver';
+$lang['singleEditForm'] = 'Éditer les greffons d\'un animal particulier';
+$lang['plugindone'] = 'État du greffon mis à jour';
+$lang['plugin'] = 'Greffon';
+$lang['plugin_on'] = 'marche';
+$lang['plugin_off'] = 'arrêt';
+$lang['plugin_default'] = 'Herité';
+$lang['plugin_enabled'] = 'Activé';
+$lang['plugin_disabled'] = 'Désactivé';
+$lang['js']['animalSelect'] = 'Sélectionnez un animal';
+$lang['js']['pluginSelect'] = 'Sélectionnez un greffon';
+$lang['disable_new_plugins'] = 'Le greffon est par défaut désactivé. Vous pouvez l\'activer ici ou pour certains animaux spécifiques seulement.';
+$lang['delete_animal'] = 'Sélectionnez l\'animal à détruire';
+$lang['delete_confirm'] = 'Veuillez taper le nom de l\'animal pour confirmer';
+$lang['delete'] = 'Détruire l\'animal et toutes ses données';
+$lang['delete_noanimal'] = 'Veuillez sélectionner l\'animal à détruire';
+$lang['delete_mismatch'] = 'La confirmation ne correspond pas à l\'animal. Destruction annulée.';
+$lang['delete_invalid'] = 'Nom d\'animal invalide. Destruction annulée';
+$lang['delete_success'] = 'Animal supprimé avec succès.';
+$lang['delete_fail'] = 'Impossible de supprimer certains fichiers. Vous devriez faire le ménage à la main.';
diff --git a/platform/www/lib/plugins/farmer/lang/fr/notfound_404.txt b/platform/www/lib/plugins/farmer/lang/fr/notfound_404.txt
new file mode 100644
index 0000000..5aaa62d
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/notfound_404.txt
@@ -0,0 +1,3 @@
+====== 404 Non trouvé ======
+
+La ressource demandée ne peut être trouvée.
diff --git a/platform/www/lib/plugins/farmer/lang/fr/notfound_list.txt b/platform/www/lib/plugins/farmer/lang/fr/notfound_list.txt
new file mode 100644
index 0000000..3a6acc7
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/notfound_list.txt
@@ -0,0 +1,4 @@
+====== Wiki non trouvé ======
+
+Impossible de trouver le wiki que vous avez demandé. Veuillez
+vous référer à la liste ci-dessous.
diff --git a/platform/www/lib/plugins/farmer/lang/fr/settings.php b/platform/www/lib/plugins/farmer/lang/fr/settings.php
new file mode 100644
index 0000000..ed4465c
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/settings.php
@@ -0,0 +1,9 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
+ */
+$lang['deactivated plugins'] = 'Liste à virgule des greffons qui sont désactivés par défaut dans les nouveaux animaux.';
+$lang['disable_new_plugins'] = 'Désactiver automatiquement les greffons après qu\'il sont installés dans le fermier. Valable seulement pour les greffons installés par le gestionnaire d\'extensions.';
diff --git a/platform/www/lib/plugins/farmer/lang/fr/tab_config.txt b/platform/www/lib/plugins/farmer/lang/fr/tab_config.txt
new file mode 100644
index 0000000..edee83f
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/tab_config.txt
@@ -0,0 +1 @@
+Ici, le comportement de base de la ferme est configurée. Veuillez garder à l'esprit que changer des options ici affecte tous les animaux.
diff --git a/platform/www/lib/plugins/farmer/lang/fr/tab_config_help.txt b/platform/www/lib/plugins/farmer/lang/fr/tab_config_help.txt
new file mode 100644
index 0000000..28790e4
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/tab_config_help.txt
@@ -0,0 +1,37 @@
+===== Configuration de la ferme =====
+
+Tous les réglages faits ici sont enregistrés dans le fichier ''conf/farm.ini'' du fermier.
+
+==== Configuration de base ====
+
+Le réglage de **Nom d'hôte du fermier** a été effectué automatiquement
+pendant l'initialisation, mais on peut le changer ici. Il sera utilisé
+pour déterminer si une requête est dirigée directement vers le fermier,
+lors de l'utilisation d'une ferme par nom. Ce devrait être un FQDN,
+''toto.example.com'' au lieu de simplement ''toto''.
+
+Lors de l'utilisation d'une configuration avec sous domaine
+générique((//wildcard subdomain//)), vous devez spécifier le
+nom principal dans le réglage **Domaine de base pour les animaux
+par sous domaine**. Par exemple, si vous indiquez ''example.com'',
+on part du principe qu'un animal nommé ''toto'' est joignable via
+''toto.example.com''. Le **Domaine de base** est simplement ajouté aux
+noms des animaux ne contenant aucun point.
+
+==== Héritage ====
+
+Vous pouvez indiquer ici quels réglages de configuration réalisés dans
+le fermier devraient être utilisés comme défaut dans les animaux.
+les animaux peuvent toujours écraser ces réglages dans leurs propres
+fichiers de configuration. Lorsque l'héritage est désactivé, les
+valeurs par défaut de DokuWiki sont également les valeurs par défaut
+pour les nouveaux animaux.
+
+==== Animaux non existant ====
+
+Par défaut, lors d'une tentative d'accès à un animal inexistant,
+aucun message d'erreur n'est affiché. Ici, vous pouvez choisir entre
+différents comportements. Assurez vous que le nom d'hôte du fermier
+soit correctement réglé ci-dessus avant de vous écarter des valeurs
+par défaut.
+
diff --git a/platform/www/lib/plugins/farmer/lang/fr/tab_delete.txt b/platform/www/lib/plugins/farmer/lang/fr/tab_delete.txt
new file mode 100644
index 0000000..73fb7ff
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/tab_delete.txt
@@ -0,0 +1 @@
+Vous pouvez détruire les animaux existant ici. Cela détruit **toutes les données, y compris les pages et médias** des animaux sélectionnés. **Ceci est irréversible** !
diff --git a/platform/www/lib/plugins/farmer/lang/fr/tab_info.txt b/platform/www/lib/plugins/farmer/lang/fr/tab_info.txt
new file mode 100644
index 0000000..e1a4ee0
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/tab_info.txt
@@ -0,0 +1 @@
+Ce wiki fait partie d'une ferme. Consultez les détails ci-dessous.
diff --git a/platform/www/lib/plugins/farmer/lang/fr/tab_new.txt b/platform/www/lib/plugins/farmer/lang/fr/tab_new.txt
new file mode 100644
index 0000000..269260d
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/tab_new.txt
@@ -0,0 +1 @@
+Vous pouvez créer un nouvel animal ici.
diff --git a/platform/www/lib/plugins/farmer/lang/fr/tab_new_help.txt b/platform/www/lib/plugins/farmer/lang/fr/tab_new_help.txt
new file mode 100644
index 0000000..7405a3b
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/tab_new_help.txt
@@ -0,0 +1,43 @@
+===== Création d'animaux =====
+
+Les animaux sont des sous-wikis indépendants d'une ferme de DokuWiki. Pour créer un nouvel animal, il faut lui assigner un nom.
+
+==== Les noms d'animaux ====
+
+Pour les fermes basées sur la réécriture par fichier .htaccess
+cela devrait être un seul mot.
+Pour les configurations basées sur les noms de domaine vous devriez indiquer
+un nom de domaine totalement qualifié (FQDN).
+Pour les fermes à base de sous domaine générique((//wildcard subdomain//
+en bon anglais)), un nom d'hôte suffit, à condition
+d'indiquer également un domaine de base.
+
+Exemples:
+
+ * basé sur .htaccess : ''toto'' pour un animal accessible à ''%%http://example.org/dokuwiki/!toto/%%''
+ * basé sur le domaine : ''%%www.toto.com%%'' pour un animal accessible à ''%%http://www.toto.com/%%''
+ * basé sur le sous domaine : ''toto'' pour un animal accessible à ''%%http://toto.example.com/%%''
+
+Les deux derniers nécessitent un réglage du DNS approprié !
+
+==== Copie d'animal ====
+
+Vous pouvez sélectionner un animal existant comme base d'un
+nouvel animal. Toute la configuration, toutes les pages et fichiers
+médias, ainsi que les méta données seront copiés vers le nouvel
+animal. L'historique des pages et médias ne sera pas copié.
+
+Le titre et l'image de logo seront modifiés de manière à être certain
+que vous pourrez distinguer la nouvelle copie de l'ancien.
+
+==== Administrateur de l'animal ====
+
+L'animal sera un wiki complètement fonctionnel, avec sa propre base
+d'utilisateurs. Vous aurez besoin d'au moins un administrateur pour le
+gérer. Vous pouvez copier l'utilisateur courant, tous les utilisateurs
+du fermier, ou créer un tout nouvel utilisateur pour l'animal.
+
+Vous pouvez choisir de ne pas créer d'utilisateur. Vous ne devriez
+choisir cette possibilité que si l'héritage d'utilisateurs du fermier
+est activé dans l'onglet de configuration, ou si vous avez copié un
+animal déjà muni d'utilisateurs.
diff --git a/platform/www/lib/plugins/farmer/lang/fr/tab_plugins.txt b/platform/www/lib/plugins/farmer/lang/fr/tab_plugins.txt
new file mode 100644
index 0000000..f7c49e5
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/tab_plugins.txt
@@ -0,0 +1 @@
+Vous pouvez activer ou désactiver soit un même greffon pour tous les animaux en une seule opération de masse, soit les greffons d'un animal particulier.
diff --git a/platform/www/lib/plugins/farmer/lang/fr/tab_plugins_help.txt b/platform/www/lib/plugins/farmer/lang/fr/tab_plugins_help.txt
new file mode 100644
index 0000000..179ab63
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/tab_plugins_help.txt
@@ -0,0 +1,22 @@
+===== Gestion des greffons =====
+
+Le gestionnaire d'extensions est désactivé dans tous les animaux. On peut
+uniquement installer les thèmes et greffons dans le fermier.
+Seul le fermier peut contrôler quels greffons
+sont activés dans les animaux. La présente interface permet d'effectuer
+ces réglages.
+
+Un greffon peut avoir trois états dans un animal. Il peut être activé, ou désactivé. Il peut également avoir l'état par défaut. Cette dernière option signifie que l'état du greffon n'est pas explicitement choisi pour un animal. Habituellement, l'état par défaut est <<activé>>.
+
+Lorsque les animaux sont configurés pour hériter l'état du fermier, un
+greffon dans l'état par défaut aura le même état que dans le fermier.
+C'est à dire que désactiver un greffon dans le fermier le désactivera
+aussi dans tous les animaux pour lesquels l'état du greffon n'a pas été
+explicitement spécifié. Si vous l'activez à nouveau dans le fermier,
+le greffon s'activera de la même manière dans tous les animaux
+concernés.
+
+Il existe trois manières de gérer les greffons :
+ - Choisir un greffon et fixer son état à la même valeur dans tous les animaux,
+ - Choisir un animal particulier et gérer l'état de chacun de ses greffons.
+ - Voir et éditer l'état de tous les greffons dans tous les animaux d'un seul coup. Cette option pourrait ne pas être praticable si vous avez un grand nombre d'animaux.
diff --git a/platform/www/lib/plugins/farmer/lang/fr/tab_setup.txt b/platform/www/lib/plugins/farmer/lang/fr/tab_setup.txt
new file mode 100644
index 0000000..f6dbdb3
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/tab_setup.txt
@@ -0,0 +1 @@
+Votre Wiki est pas configuré pour l'élevage avec le greffon //farmer//. Veuillez utiliser la boîte de dialogue suivante pour la mise en place de la ferme.
diff --git a/platform/www/lib/plugins/farmer/lang/fr/tab_setup_help.txt b/platform/www/lib/plugins/farmer/lang/fr/tab_setup_help.txt
new file mode 100644
index 0000000..66a3ccc
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/fr/tab_setup_help.txt
@@ -0,0 +1,52 @@
+===== À propos de l'élevage =====
+
+L'élevage est une technique qui permet, à partir d'une unique
+installation de DokuWiki, connue comme le <<fermier>>, de propulser
+un nombre quelconque d'autres wikis appelés les animaux. Vous installez les
+greffons et thèmes uniquement dans le fermier, et les rendez disponibles
+à votre guise dans des animaux. Il suffit de mettre à jour le wiki principal et
+tous les autres wikis, qui partagent la même base de code, sont également mis
+à jour.
+
+Après avoir complété cette étape de configuration, votre wiki actuel,
+celui que vous lisez en ce moment même, sera connu comme le <<fermier>>.
+
+===== Que fait cette initialisation ? =====
+
+Cet assistant de configuration fera trois choses :
+
+ - Créer un fichier ''inc/preload.php'',
+ - créer un fichier ''conf/farm.ini''
+ - facultativement, ajouter un fichier ''.htaccess''.
+
+''preload.php'' est un fichier qui est chargé au tout début du chargement de DokuWiki.
+Là, le mécanisme de ferme est initialisé. Le greffon //farmer// détectera si la requête
+doit être adressée à un animal ou au fermier, et reconfigurera l'ensemble de manière
+appropriée.
+
+''conf/farm.ini'' contient les configurations de base du système de ferme. En particulier,
+ce fichier contient l'emplacement où les données des animaux seront
+enregistrées.
+
+La modification du fichier ''.htaccess'' permet de rendre accessible les animaux via
+le mécanisme ''!bang'' (voir ci-dessous).
+
+===== Que remplir ? =====
+
+Le **Dossier des animaux** est le dossier où un nouveau dossier est créé pour
+chaque nouvel animal. Ce dossier **doit** se trouver en dehors de l'arborescence
+de votre DokuWiki actuel. Vous pouvez utiliser un dossier relatif, tel que
+''../animaux'' ou un chemin absolu.
+
+Il est recommandé d'activer l'utilisation du fichier ''.htaccess''. Cette fonctionnalité
+nécessite un serveur [[https://httpd.apache.org/|Apache]] avec le module
+mod_rewrite et la prise en charge des fichiers .htaccess.
+
+Lorsque vous activez le .htaccess, les animaux sont accessibles sous l'URL du
+wiki fermier en utilisant le suffixe //!bang//. Par exemple, si votre fermier
+est accessible à l'URL
+''%%http://www.example.com/dokuwiki/%%'', l'animal toto sera accessible à
+l'URL ''%%http://www.example.com/dokuwiki/!toto%%''.
+
+Si vous n'activez pas ce mécanisme, vous devrez configurer votre serveur web
+et votre DNS pour pouvoir accéder aux animaux.
diff --git a/platform/www/lib/plugins/farmer/lang/ja/lang.php b/platform/www/lib/plugins/farmer/lang/ja/lang.php
new file mode 100644
index 0000000..f837a3a
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/lang.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Hideaki SAWADA <chuno@live.jp>
+ */
+$lang['menu'] = '牧場設定';
+$lang['tab_setup'] = '牧場設定';
+$lang['tab_info'] = '情報';
+$lang['tab_config'] = '設定';
+$lang['tab_plugins'] = 'プラグイン管理';
+$lang['tab_new'] = '新しい動物の追加';
+$lang['tab_delete'] = '動物の削除';
+$lang['preloadPHPForm'] = '牧場の初期化';
+$lang['farm dir'] = '動物用ディレクトリ';
+$lang['htaccess setup'] = '.htaccess に牧場用のコードを追加しますか?';
+$lang['submit'] = '実行';
+$lang['farmdir_missing'] = '動物を保存するディレクトリを入力してください。';
+$lang['farmdir_in_dokuwiki'] = '動物用ディレクトリは牧場 dokuwiki の外にある必要があります。';
+$lang['farmdir_uncreatable'] = '動物用ディレクトリを作成できませんでした。権限は適切ですか?';
+$lang['farmdir_unwritable'] = 'Web サーバが動物用ディレクトリに書き込み権を持っていることを確認してください!';
+$lang['farmdir_notEmpty'] = '動物用ディレクトリは空でなければなりません。';
+$lang['preload creation success'] = '牧場は正常に初期化されました。';
+$lang['preload creation error'] = '牧場の初期化中にエラーが発生しました。';
+$lang['overwrite_preload'] = '警告:続行すると、既存の inc/preload.php が上書きされます!';
+$lang['animal'] = '動物名・ドメイン';
+$lang['thisis'] = 'インスタンスは';
+$lang['thisis.farmer'] = '牧場主です!';
+$lang['thisis.animal'] = '動物です!';
+$lang['baseinstall'] = '牧場主のインストール先';
+$lang['animals'] = '動物';
+$lang['confdir'] = 'インスタンスの設定ディレクトリ';
+$lang['savedir'] = 'インスタンスのデータディレクトリ';
+$lang['plugins'] = 'このインスタンスで有効なプラグイン';
+$lang['base'] = '基本設定';
+$lang['farm host'] = '牧場主ホスト名';
+$lang['base domain'] = '動物サブドメイン用のベースドメイン';
+$lang['conf_inherit'] = '動物が継承する牧場主設定';
+$lang['conf_inherit_main'] = 'DokuWiki 設定';
+$lang['conf_inherit_acronyms'] = '略字と頭字語';
+$lang['conf_inherit_entities'] = '記号への変換';
+$lang['conf_inherit_interwiki'] = 'InterWiki リンク';
+$lang['conf_inherit_license'] = 'ライセンス';
+$lang['conf_inherit_mime'] = 'MIME の設定';
+$lang['conf_inherit_scheme'] = 'URL スキーム';
+$lang['conf_inherit_smileys'] = 'スマイリー';
+$lang['conf_inherit_wordblock'] = 'ブラックリスト';
+$lang['conf_inherit_userstyle'] = 'ユーザースタイル';
+$lang['conf_inherit_users'] = 'ユーザー(テキスト認証の場合のみ)';
+$lang['conf_inherit_plugins'] = 'プラグインの状態';
+$lang['conf_inherit_yes'] = '牧場主から継承';
+$lang['conf_inherit_no'] = '牧場主から独立';
+$lang['conf_notfound'] = '実在しない動物へのアクセス時の動作';
+$lang['conf_notfound_farmer'] = '牧場主を表示';
+$lang['conf_notfound_404'] = '404 エラーページを表示';
+$lang['conf_notfound_list'] = '利用可能な動物一覧を表示';
+$lang['conf_notfound_redirect'] = '以下の URL へ転送';
+$lang['conf_notfound_url'] = '上記選択した場合、転送先の URL';
+$lang['save'] = '保存';
+$lang['animal template'] = '既存の動物をコピーする';
+$lang['animal creation success'] = '動物 "%s" は正常に作成されました。';
+$lang['animal creation error'] = '動物の作成中にエラーが発生しました。';
+$lang['animal configuration'] = '基本的な動物設定';
+$lang['inherit user registration'] = '牧場主からユーザー登録設定を継承する';
+$lang['enable user registration'] = '自分でユーザー登録することを許可する';
+$lang['disable user registration'] = 'ユーザー登録を無効化する';
+$lang['animal administrator'] = '動物の管理者';
+$lang['noUsers'] = 'ユーザーを作成しない';
+$lang['importUsers'] = '牧場主の全てのユーザーを新しい動物に入力する';
+$lang['currentAdmin'] = '現在のユーザーを admin として設定する';
+$lang['newAdmin'] = '新しい管理者ユーザー "admin" を作成する';
+$lang['admin password'] = '新しい管理者のパスワード';
+$lang['animalname_missing'] = '新しい動物の名前を入力してください。';
+$lang['animalname_invalid'] = '動物の名前には、英数字とドット・ハイフン(最初または最後の文字以外)のみを使用できます。';
+$lang['animalname_preexisting'] = 'その名前の動物はすでに存在しています。';
+$lang['adminPassword_empty'] = '新しい管理者アカウントのパスワードは空ではいけません。';
+$lang['animal template copy error'] = '既存の動物から %s をコピーする際に問題が発生しました。';
+$lang['aclpolicy missing/bad'] = 'ドロップダウンから最初の ACL ポリシーを選択してください。';
+$lang['bulkSingleSwitcher'] = '特定の動物を設定しますか?全動物を一括設定しますか?';
+$lang['bulkEdit'] = '全動物を一括設定する';
+$lang['singleEdit'] = '特定の動物を設定する';
+$lang['bulkEditForm'] = '特定のプラグインを全動物一括で設定する';
+$lang['matrixEdit'] = '一覧(動物×プラグイン)で設定する';
+$lang['default'] = 'デフォルト値を設定';
+$lang['activate'] = '有効化';
+$lang['deactivate'] = '無効化';
+$lang['singleEditForm'] = '特定の動物のプラグインを設定する';
+$lang['plugindone'] = 'プラグインの状態は更新されました';
+$lang['plugin'] = 'プラグイン';
+$lang['plugin_on'] = '有効';
+$lang['plugin_off'] = '無効';
+$lang['plugin_default'] = 'デフォルト値';
+$lang['plugin_enabled'] = '個別に有効化';
+$lang['plugin_disabled'] = '個別に無効化';
+$lang['js']['animalSelect'] = '動物を選択する';
+$lang['js']['pluginSelect'] = 'プラグインを選択する';
+$lang['disable_new_plugins'] = 'プラグインはデフォルトで無効になっています。デフォルト値を有効にするか特定の動物に対して有効にすることができます。';
+$lang['delete_animal'] = '選択した動物の削除';
+$lang['delete_confirm'] = '確認のために動物の名前を入力してください';
+$lang['delete'] = '動物とそのすべてのデータを削除します';
+$lang['delete_noanimal'] = '削除する動物を選択してください';
+$lang['delete_mismatch'] = '確認入力は動物の名前と一致しません。 削除されません。';
+$lang['delete_invalid'] = '無効な動物の名前です。 削除されません。';
+$lang['delete_success'] = '動物は正常に削除されました。';
+$lang['delete_fail'] = '一部のファイルを削除できなかったため、手動できれいにする必要があります。';
diff --git a/platform/www/lib/plugins/farmer/lang/ja/notfound_404.txt b/platform/www/lib/plugins/farmer/lang/ja/notfound_404.txt
new file mode 100644
index 0000000..3f575d6
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/notfound_404.txt
@@ -0,0 +1,3 @@
+====== 404 ありません ======
+
+要求されたリソースがありません。
diff --git a/platform/www/lib/plugins/farmer/lang/ja/notfound_list.txt b/platform/www/lib/plugins/farmer/lang/ja/notfound_list.txt
new file mode 100644
index 0000000..f3e633c
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/notfound_list.txt
@@ -0,0 +1,3 @@
+====== Wiki がありません ======
+
+要求された Wiki がありません。以下の利用可能な Wiki の一覧を参照してください。
diff --git a/platform/www/lib/plugins/farmer/lang/ja/settings.php b/platform/www/lib/plugins/farmer/lang/ja/settings.php
new file mode 100644
index 0000000..dd9b6a6
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/settings.php
@@ -0,0 +1,9 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Hideaki SAWADA <chuno@live.jp>
+ */
+$lang['deactivated plugins'] = '新しい動物を作成した場合、デフォルトで無効とするプラグインのカンマ区切り一覧。';
+$lang['disable_new_plugins'] = '牧場にプラグインを新規にインストールした後、プラグインを自動的に無効化しますか?(拡張機能管理でインストールした場合のみ)';
diff --git a/platform/www/lib/plugins/farmer/lang/ja/tab_config.txt b/platform/www/lib/plugins/farmer/lang/ja/tab_config.txt
new file mode 100644
index 0000000..4d3619b
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/tab_config.txt
@@ -0,0 +1 @@
+牧場の基本的な動作を設定します。 ここのオプションを変更すると、全ての動物に影響が及ぶのでご注意ください。 \ No newline at end of file
diff --git a/platform/www/lib/plugins/farmer/lang/ja/tab_config_help.txt b/platform/www/lib/plugins/farmer/lang/ja/tab_config_help.txt
new file mode 100644
index 0000000..5e9e4b8
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/tab_config_help.txt
@@ -0,0 +1,25 @@
+===== 牧場設定 =====
+
+ここで入力した設定内容はすべて牧場主の ''conf/farm.ini'' に保存されます。
+
+==== 基本設定 ====
+
+**牧場主ホスト名**は、設定時に自動的に設定されますが、ここで変更できます。
+ホスト方式牧場を使用する場合、牧場主に直接要求されたかどうかの検出に使用します。
+これは完全修飾ホスト名でなければなりません。(''foo'' のみではなく ''foo.example.com'')
+
+サブドメインのワイルドカード設定を使用する場合、**ベースドメイン**設定でメインドメインを指定する必要があります。
+例えば ''example.com'' を指定した場合、''foo'' という名の動物は ''foo.example.com'' 経由で届く想定します。
+ベースドメインにはドットを含まない動物名だけが付加されます。
+
+==== 継承 ====
+
+ここでは、牧場主上で作成したどの設定を動物内でデフォルトとして使用するかを指定できます。
+動物は、独自の設定ファイルで牧場主の設定を上書きすることもできます。
+継承が無効になっている場合、DokuWiki のデフォルト設定が、すべての動物のデフォルトです。
+
+==== 実在しない動物 ====
+
+デフォルトでは、実在しない動物へのアクセス時にエラーメッセージを表示しません。
+ここで異なる動作を選ぶことができます。
+デフォルトから切替える前に、牧場主ホスト名が上記に正しく設定されていることを確認して下さい。
diff --git a/platform/www/lib/plugins/farmer/lang/ja/tab_delete.txt b/platform/www/lib/plugins/farmer/lang/ja/tab_delete.txt
new file mode 100644
index 0000000..bde0596
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/tab_delete.txt
@@ -0,0 +1 @@
+既存の動物を削除できます。 選択した動物の**ページやメディアファイルを含む全てのデータ**を削除します。 **元に戻せないので注意して下さい!** \ No newline at end of file
diff --git a/platform/www/lib/plugins/farmer/lang/ja/tab_info.txt b/platform/www/lib/plugins/farmer/lang/ja/tab_info.txt
new file mode 100644
index 0000000..78ea6c7
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/tab_info.txt
@@ -0,0 +1 @@
+この Wiki は、牧場設定の一部分です。 以下の詳細を確認してください。 \ No newline at end of file
diff --git a/platform/www/lib/plugins/farmer/lang/ja/tab_new.txt b/platform/www/lib/plugins/farmer/lang/ja/tab_new.txt
new file mode 100644
index 0000000..78d5975
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/tab_new.txt
@@ -0,0 +1 @@
+新しい動物を作成できます。 \ No newline at end of file
diff --git a/platform/www/lib/plugins/farmer/lang/ja/tab_new_help.txt b/platform/www/lib/plugins/farmer/lang/ja/tab_new_help.txt
new file mode 100644
index 0000000..9287e1f
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/tab_new_help.txt
@@ -0,0 +1,38 @@
+===== 動物の作成 =====
+
+動物は、Dokuwiki 牧場の独立したサブ wiki です。
+新しい動物を作成するには名前を割り当てなければなりません。
+
+==== 動物名 ====
+
+.htaccess 方式の設定の場合、動物名は一つの単語でなければなりません。
+ドメイン方式の設定の場合、完全修飾ドメイン名を提供する必要があります。
+ワイルドカードサブドメインの設定を使用している場合、ベースドメインが設定済みであれば、単にホスト名部分を提供するだけです。
+
+例:
+
+ * .htaccess 方式:''%%http://example.org/dokuwiki/!foo/%%'' にアクセス可能な動物用の ''foo''
+ * ドメイン方式:''%%http://www.foo.com/%%'' にアクセス可能な動物用の ''%%www.foo.com%%''
+ * サブドメイン方式: ''%%http://foo.example.com/%%'' にアクセス可能な動物用の ''foo''
+
+後の二つは、適切な DNS 設定が必要です!
+
+==== 動物のコピー ====
+
+新しい動物の基礎にするために既存の動物を選択することができます。
+すべての設定、ページ、メディア、メタデータが新しい動物にコピーされます。
+ページやメディアの履歴はコピーされません。
+
+タイトルとロゴイメージは上書きして、コピー元と区別できることを確認してください。
+
+==== 動物の管理者 ====
+
+動物は独自のユーザー基準で完全な機能の wiki になります。
+ユーザーを設定するために少なくとも1つの管理ユーザーが必要になります。
+牧場主から現在のユーザーまたはすべてのユーザーをコピーしたり、動物のために完全に新しいユーザーを作成できます。
+
+ユーザーを作成しないことも選択できます。
+設定タブで牧場主からユーザーの継承を有効にした場合、既存のユーザーを別の動物からコピーした場合のみこれを選択すべきです。
+
+/lang/en/tab_plugins.txt
+一括操作で全ての動物に対するプラグインの有効化・無効化や、特定の動物に対するプラグインの編集ができます。
diff --git a/platform/www/lib/plugins/farmer/lang/ja/tab_plugins.txt b/platform/www/lib/plugins/farmer/lang/ja/tab_plugins.txt
new file mode 100644
index 0000000..ee4acd5
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/tab_plugins.txt
@@ -0,0 +1 @@
+一度の操作で全ての動物のプラグインを有効または無効にすることや、個々の動物のプラグインを編集することができます。 \ No newline at end of file
diff --git a/platform/www/lib/plugins/farmer/lang/ja/tab_plugins_help.txt b/platform/www/lib/plugins/farmer/lang/ja/tab_plugins_help.txt
new file mode 100644
index 0000000..fe4aa72
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/tab_plugins_help.txt
@@ -0,0 +1,17 @@
+===== プラグイン管理 =====
+
+拡張機能管理は、すべての動物の中で無効になっています。
+プラグインは牧場主にのみインストールすることができ、牧場主だけがすべての動物の中でどのプラグインを有効にするか無効にするかを制御できます。
+この画面を使ってこの制御を行います。
+
+各プラグインは動物毎に三つの状態を指定できます。
+有効、無効、デフォルト状態です。
+デフォルトは動物に対してプラグインの状態を明示的に設定していないことを意味します。
+通常はデフォルトの状態は有効になっています。
+
+動物が牧場主のプラグインの状態を継承するように設定した場合、デフォルト状態のプラグインは牧場主と同じ状態になります。
+例えば、牧場主のプラグインを無効にした場合、このプラグインの状態を明示的に設定していなかったすべての動物内でプラグインを無効にします。
+
+プラグイン管理には二種類の方法があります:
+ * 一つのプラグインを選択し、すべての動物に対して同じ状態を設定する。
+ * 特定の動物を選び、その動物内のみに対するすべてのプラグインの状態を設定する。
diff --git a/platform/www/lib/plugins/farmer/lang/ja/tab_setup.txt b/platform/www/lib/plugins/farmer/lang/ja/tab_setup.txt
new file mode 100644
index 0000000..a5cf09c
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/tab_setup.txt
@@ -0,0 +1 @@
+この Wiki は牧場主プラグインの牧場設定をされていません。以下の入力画面で牧場設定をして下さい。 \ No newline at end of file
diff --git a/platform/www/lib/plugins/farmer/lang/ja/tab_setup_help.txt b/platform/www/lib/plugins/farmer/lang/ja/tab_setup_help.txt
new file mode 100644
index 0000000..d2cd61f
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/ja/tab_setup_help.txt
@@ -0,0 +1,38 @@
+===== 牧場について =====
+
+牧場は複数の他の wiki(「動物」 "Animals")を動かす単一の DokuWiki インストール(「牧場主」 "Farmer")を使えるようにします。
+プラグインとテンプレートは牧場主にだけインストールすれば、動物もそれらを利用できるようになります。
+一つの wiki だけを最新状態に保つ必要があり、他のすべての wiki は同じコードベースを使用するだけになります。
+
+この設定手順を完了した後、現在の DokuWiki(あなたが見ている DokuWiki)が、「牧場主」として認識されるようになります。
+
+===== この設定は何をしますか? =====
+
+この設定ウィザードでは、以下の三つを行います:
+
+ - ''inc/preload.php'' ファイルの作成
+ - ''conf/farm.ini'' ファイルの作成
+ - 必要に応じて ''.htaccess'' ファイルに追加
+
+''preload.php'' は DokuWiki 読込み時の最初に読み込まれるファイルです。
+ここで牧場機能が初期化されます。
+現在の要求が動物か牧場主をアクセスし、それに応じて全てを再構成する必要がある場合、牧場主プラグインを検出します。
+
+''conf/farm.ini'' には牧場の基本的な設定が含まれています。
+最も重要なことは、すべての動物データが格納される場所が含まれていることです。
+
+''.htaccess'' を変更することで //バン !// 機能を使って動物にアクセスできるようになります。(下記参照)
+
+===== 入力方法? =====
+
+**動物用のディレクトリ**は、各動物用に新しいディレクトリを作成する場所です。
+このディレクトリは、現在の DokuWiki の外でなければなりません。
+''../animals'' のような相対的なディレクトリを指定できます。
+
+**.htaccess** 対応を有効にすることを推奨します。
+この機能は、Apache の mod_rewrite と .htaccess 対応が必要です。
+
+有効にすると、動物は //バン !// 機能を使って牧場主の URL の下でアクセスできるようになります。
+例えば、''%%http://www.example.com/dokuwiki/%%'' で牧場主が実行されている場合、''%%http://www.example.com/dokuwiki/!animal%%'' で動物にアクセスできるようになります。
+
+このオプションを有効にしない場合、動物にアクセスするには、Web サーバーや DNS を設定するが必要があります。
diff --git a/platform/www/lib/plugins/farmer/lang/nl/lang.php b/platform/www/lib/plugins/farmer/lang/nl/lang.php
new file mode 100644
index 0000000..cef0c84
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/nl/lang.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Harriet Neitz <harrietneitz@gmail.com>
+ */
+$lang['tab_info'] = 'Info';
+$lang['tab_config'] = 'Configuratie';
+$lang['tab_plugins'] = 'Beheer plugins';
+$lang['tab_new'] = 'Voeg nieuw dier toe';
+$lang['tab_delete'] = 'Verwijder dier';
+$lang['farmdir_in_dokuwiki'] = 'De dier directory (%s) moet buiten de Boerderij dokuwiki (%s) staan.';
+$lang['farmdir_unwritable'] = 'Controleer dat de webserver schrijf-toegang heeft tot de dier directory (%s)!';
+$lang['overwrite_preload'] = 'Waarschuwing: Je bestaande inc/preload.php wordt overschreven wanneer je hier doorgaat!';
+$lang['conf_notfound_404'] = 'Toon een 404 fout pagina';
+$lang['conf_notfound_list'] = 'Toon een lijst van bestaande dieren';
+$lang['conf_notfound_redirect'] = 'Redirect naar onderstaande URL';
+$lang['save'] = 'Opslaan';
+$lang['animal template'] = 'Kopieer bestaand dier';
diff --git a/platform/www/lib/plugins/farmer/lang/pl/lang.php b/platform/www/lib/plugins/farmer/lang/pl/lang.php
new file mode 100644
index 0000000..375d919
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/lang/pl/lang.php
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Bartek S <sadupl@gmail.com>
+ */
+$lang['menu'] = 'Farma';
+$lang['tab_setup'] = 'Ustawienia Farmy';
+$lang['tab_info'] = 'Informacje';
+$lang['tab_config'] = 'Konfiguracja';
+$lang['tab_plugins'] = 'Zarządzaj pluginami';
+$lang['tab_new'] = 'Dodaj nowe zwierzę';
+$lang['tab_delete'] = 'Usuń zwierzę';
+$lang['preloadPHPForm'] = 'Zainicjuj Farmę';
+$lang['farm dir'] = 'Katalog zwierząt';
+$lang['htaccess setup'] = 'Dodać kod farmy do .htaccess?';
+$lang['submit'] = 'Zatwierdź';
+$lang['farmdir_missing'] = 'Proszę podać katalog gdzie Zwierzęta powinny być przechowywane.';
+$lang['farmdir_in_dokuwiki'] = 'Katalog Zwierząt (%s) musi być poza Farmą dokuwiki (%s).';
+$lang['farmdir_uncreatable'] = 'Katalog Zwierząt (%s) nie został stworzony. Czy uprawnienia są prawidłowe?';
+$lang['farmdir_unwritable'] = 'Proszę się upenić, że webserver posiada prawa zapisu do katalogu Zwierząt.';
+$lang['farmdir_notEmpty'] = 'Katalog Zwierząt (%s) musi być pusty.';
+$lang['preload creation success'] = 'Farma została zainicjowana pomyślnie.';
+$lang['preload creation error'] = 'Wystąpił błąd podczas inicjowania Farmy.';
+$lang['overwrite_preload'] = 'Uwaga: Dotychczasowy plik inc/preload.php będzie zastąpiony podczas kontynuowania!';
+$lang['animal'] = 'Imię Zwierzęta / Domeny';
+$lang['thisis'] = 'Instancja to';
+$lang['thisis.farmer'] = 'Farmer!';
+$lang['thisis.animal'] = 'Zwierzę!';
+$lang['baseinstall'] = 'Instalacja Farmera';
+$lang['animals'] = 'Zwierzęta';
+$lang['confdir'] = 'Katalog konfiguracji instancji';
+$lang['savedir'] = 'Katalog danych instancji';
+$lang['plugins'] = 'Aktywne pluginy tej instancji';
+$lang['base'] = 'Konfiguracja Bazy';
+$lang['farm host'] = 'Host Name Farmera';
+$lang['base domain'] = 'Domena podstawowa dla subdomeny Zwierzęta';
+$lang['conf_inherit'] = 'Zwierzęta powinny dziedziczyć ustawienia farmera';
+$lang['conf_inherit_main'] = 'Ustawienia konfiguracji';
+$lang['conf_inherit_acronyms'] = 'Definicje akronimów';
+$lang['conf_inherit_entities'] = 'Definicje jednostek';
+$lang['conf_inherit_interwiki'] = 'Definicje Interwiki';
+$lang['conf_inherit_license'] = 'Definicje licencji';
+$lang['conf_inherit_smileys'] = 'Definicje uśmieszków';
+$lang['conf_inherit_wordblock'] = 'Wpisy SPAM na czarnej liście';
+$lang['conf_inherit_userstyle'] = 'Style użytkownika';
+$lang['conf_inherit_userscript'] = 'Skrypty użytkownika';
+$lang['conf_inherit_styleini'] = 'Dostosowanie stylu szablonu';
+$lang['conf_inherit_users'] = 'Użytkownicy (tylko zwykłe uwierzytelnianie)';
+$lang['conf_inherit_plugins'] = 'Stan wtyczki';
+$lang['conf_inherit_yes'] = 'odziedziczony po farmerze';
+$lang['conf_inherit_no'] = 'niezależny od farmera';
+$lang['conf_notfound'] = 'Zachowanie podczas uzyskiwania dostępu do nieistniejących zwierząt';
+$lang['conf_notfound_farmer'] = 'Pokaż wiki farmera';
+$lang['conf_notfound_404'] = 'Pokaż stronę błędu 404';
+$lang['conf_notfound_list'] = 'Pokaż listę dostępnych zwierząt';
+$lang['conf_notfound_redirect'] = 'Przekieruj na poniższy adres URL';
+$lang['conf_notfound_url'] = 'Adres URL do przekierowania, jeśli wybrano powyżej';
+$lang['save'] = 'Zapisz';
+$lang['animal template'] = 'Kopiuj istniejące Zwierzę';
+$lang['animal creation success'] = 'Zwierzę "%s" zostało pomyślnie utworzone.';
+$lang['animal creation error'] = 'Wystąpił błąd podczas tworzenia zwierzęcia.';
+$lang['animal configuration'] = 'Podstawowa konfiguracja Zwierząt';
+$lang['inherit user registration'] = 'Dziedzicz ustawienia rejestracji użytkownika od farmera';
+$lang['enable user registration'] = 'Pozwól użytkownikom się zarejestrować';
+$lang['disable user registration'] = 'Wyłącz rejestrację użytkowników';
+$lang['animal administrator'] = 'Administrator zwięrząt';
+$lang['noUsers'] = 'Nie twórz żadnych użytkowników';
+$lang['importUsers'] = 'Zaimportuj wszystkich użytkowników Farmera do nowego zwierzęcia';
+$lang['currentAdmin'] = 'Ustaw bieżącego użytkownika jako admin';
+$lang['newAdmin'] = 'Utwórz nowego administratora „admin”';
+$lang['admin password'] = 'Hasło dla nowego administratora';
+$lang['animalname_missing'] = 'Wprowadź nazwę nowego zwierzęcia.';
+$lang['animalname_preexisting'] = 'Zwierzę o tej nazwie już istnieje.';
+$lang['adminPassword_empty'] = 'Hasło do nowego konta administratora nie może być puste.';
+$lang['bulkSingleSwitcher'] = 'Edytować jedno Zwierzę czy wszystkie naraz?';
+$lang['bulkEdit'] = 'Zbiorcza edycja wszystkich Zwierząt';
+$lang['singleEdit'] = 'Edytuj jedno Zwierzę';
+$lang['bulkEditForm'] = 'Aktywuj lub dezaktywuj wtyczkę we wszystkich Zwierzętach';
+$lang['default'] = 'Ustaw na domyślny';
+$lang['activate'] = 'Aktywuj';
+$lang['deactivate'] = 'Dezaktywuj';
+$lang['singleEditForm'] = 'Edytuj wtyczki określonego Zwierzęcia';
+$lang['plugindone'] = 'Stany wtyczek zaktualizowane';
+$lang['plugin'] = 'Plugin';
+$lang['plugin_on'] = 'on
+(it means \'włączone\', but in PL we use also ON/OFF)';
+$lang['plugin_off'] = 'off
+(it means \'wyłączone\', but in PL we use also ON/OFF)';
+$lang['plugin_default'] = 'Domyślne';
+$lang['plugin_enabled'] = 'Włączone';
+$lang['plugin_disabled'] = 'Wyłączone';
+$lang['js']['animalSelect'] = 'Wybierz zwierzę';
+$lang['js']['pluginSelect'] = 'Wybierz plugin';
+$lang['delete_animal'] = 'Wybierz Zwierzę do usunięcia';
+$lang['delete_confirm'] = 'Wpisz nazwę Zwierzęcia, aby potwierdzić';
+$lang['delete'] = 'Usuń Zwierzę i wszystkie jego dane';
+$lang['delete_noanimal'] = 'Proszę wybrać Zwierzę do usunięcia';
+$lang['delete_mismatch'] = 'Potwierdzenie nie pasuje do nazwy Zwierzęcia. Nie usunięte.';
+$lang['delete_invalid'] = 'Nieprawidłowa nazwa Zwierzęcia. Nie usunięte';
+$lang['delete_success'] = 'Zwierzę zostało pomyślnie usunięte.';
diff --git a/platform/www/lib/plugins/farmer/plugin.info.txt b/platform/www/lib/plugins/farmer/plugin.info.txt
new file mode 100644
index 0000000..2a26566
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/plugin.info.txt
@@ -0,0 +1,7 @@
+base farmer
+author Michael Große, Andreas Gohr
+email dokuwiki@cosmocode.de
+date 2021-01-08
+name farmer plugin
+desc A plugin to help with creating and administring wiki farm animals
+url https://dokuwiki.org/plugin:farmer
diff --git a/platform/www/lib/plugins/farmer/script.js b/platform/www/lib/plugins/farmer/script.js
new file mode 100644
index 0000000..d5cea10
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/script.js
@@ -0,0 +1,2 @@
+/* DOKUWIKI:include_once script/jquery.chosen.js */
+/* DOKUWIKI:include script/plugins.js */
diff --git a/platform/www/lib/plugins/farmer/script/jquery.chosen.js b/platform/www/lib/plugins/farmer/script/jquery.chosen.js
new file mode 100644
index 0000000..929a9ca
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/script/jquery.chosen.js
@@ -0,0 +1,1257 @@
+/*!
+Chosen, a Select Box Enhancer for jQuery and Prototype
+by Patrick Filler for Harvest, http://getharvest.com
+
+Version 1.4.2
+Full source at https://github.com/harvesthq/chosen
+Copyright (c) 2011-2015 Harvest http://getharvest.com
+
+MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
+This file is generated by `grunt build`, do not edit it by hand.
+*/
+
+(function() {
+ var $, AbstractChosen, Chosen, SelectParser, _ref,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ SelectParser = (function() {
+ function SelectParser() {
+ this.options_index = 0;
+ this.parsed = [];
+ }
+
+ SelectParser.prototype.add_node = function(child) {
+ if (child.nodeName.toUpperCase() === "OPTGROUP") {
+ return this.add_group(child);
+ } else {
+ return this.add_option(child);
+ }
+ };
+
+ SelectParser.prototype.add_group = function(group) {
+ var group_position, option, _i, _len, _ref, _results;
+ group_position = this.parsed.length;
+ this.parsed.push({
+ array_index: group_position,
+ group: true,
+ label: this.escapeExpression(group.label),
+ title: group.title ? group.title : void 0,
+ children: 0,
+ disabled: group.disabled,
+ classes: group.className
+ });
+ _ref = group.childNodes;
+ _results = [];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ option = _ref[_i];
+ _results.push(this.add_option(option, group_position, group.disabled));
+ }
+ return _results;
+ };
+
+ SelectParser.prototype.add_option = function(option, group_position, group_disabled) {
+ if (option.nodeName.toUpperCase() === "OPTION") {
+ if (option.text !== "") {
+ if (group_position != null) {
+ this.parsed[group_position].children += 1;
+ }
+ this.parsed.push({
+ array_index: this.parsed.length,
+ options_index: this.options_index,
+ value: option.value,
+ text: option.text,
+ html: option.innerHTML,
+ title: option.title ? option.title : void 0,
+ selected: option.selected,
+ disabled: group_disabled === true ? group_disabled : option.disabled,
+ group_array_index: group_position,
+ group_label: group_position != null ? this.parsed[group_position].label : null,
+ classes: option.className,
+ style: option.style.cssText
+ });
+ } else {
+ this.parsed.push({
+ array_index: this.parsed.length,
+ options_index: this.options_index,
+ empty: true
+ });
+ }
+ return this.options_index += 1;
+ }
+ };
+
+ SelectParser.prototype.escapeExpression = function(text) {
+ var map, unsafe_chars;
+ if ((text == null) || text === false) {
+ return "";
+ }
+ if (!/[\&\<\>\"\'\`]/.test(text)) {
+ return text;
+ }
+ map = {
+ "<": "&lt;",
+ ">": "&gt;",
+ '"': "&quot;",
+ "'": "&#x27;",
+ "`": "&#x60;"
+ };
+ unsafe_chars = /&(?!\w+;)|[\<\>\"\'\`]/g;
+ return text.replace(unsafe_chars, function(chr) {
+ return map[chr] || "&amp;";
+ });
+ };
+
+ return SelectParser;
+
+ })();
+
+ SelectParser.select_to_array = function(select) {
+ var child, parser, _i, _len, _ref;
+ parser = new SelectParser();
+ _ref = select.childNodes;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ child = _ref[_i];
+ parser.add_node(child);
+ }
+ return parser.parsed;
+ };
+
+ AbstractChosen = (function() {
+ function AbstractChosen(form_field, options) {
+ this.form_field = form_field;
+ this.options = options != null ? options : {};
+ if (!AbstractChosen.browser_is_supported()) {
+ return;
+ }
+ this.is_multiple = this.form_field.multiple;
+ this.set_default_text();
+ this.set_default_values();
+ this.setup();
+ this.set_up_html();
+ this.register_observers();
+ this.on_ready();
+ }
+
+ AbstractChosen.prototype.set_default_values = function() {
+ var _this = this;
+ this.click_test_action = function(evt) {
+ return _this.test_active_click(evt);
+ };
+ this.activate_action = function(evt) {
+ return _this.activate_field(evt);
+ };
+ this.active_field = false;
+ this.mouse_on_container = false;
+ this.results_showing = false;
+ this.result_highlighted = null;
+ this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false;
+ this.disable_search_threshold = this.options.disable_search_threshold || 0;
+ this.disable_search = this.options.disable_search || false;
+ this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true;
+ this.group_search = this.options.group_search != null ? this.options.group_search : true;
+ this.search_contains = this.options.search_contains || false;
+ this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true;
+ this.max_selected_options = this.options.max_selected_options || Infinity;
+ this.inherit_select_classes = this.options.inherit_select_classes || false;
+ this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true;
+ this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true;
+ return this.include_group_label_in_selected = this.options.include_group_label_in_selected || false;
+ };
+
+ AbstractChosen.prototype.set_default_text = function() {
+ if (this.form_field.getAttribute("data-placeholder")) {
+ this.default_text = this.form_field.getAttribute("data-placeholder");
+ } else if (this.is_multiple) {
+ this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text;
+ } else {
+ this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text;
+ }
+ return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text;
+ };
+
+ AbstractChosen.prototype.choice_label = function(item) {
+ if (this.include_group_label_in_selected && (item.group_label != null)) {
+ return "<b class='group-name'>" + item.group_label + "</b>" + item.html;
+ } else {
+ return item.html;
+ }
+ };
+
+ AbstractChosen.prototype.mouse_enter = function() {
+ return this.mouse_on_container = true;
+ };
+
+ AbstractChosen.prototype.mouse_leave = function() {
+ return this.mouse_on_container = false;
+ };
+
+ AbstractChosen.prototype.input_focus = function(evt) {
+ var _this = this;
+ if (this.is_multiple) {
+ if (!this.active_field) {
+ return setTimeout((function() {
+ return _this.container_mousedown();
+ }), 50);
+ }
+ } else {
+ if (!this.active_field) {
+ return this.activate_field();
+ }
+ }
+ };
+
+ AbstractChosen.prototype.input_blur = function(evt) {
+ var _this = this;
+ if (!this.mouse_on_container) {
+ this.active_field = false;
+ return setTimeout((function() {
+ return _this.blur_test();
+ }), 100);
+ }
+ };
+
+ AbstractChosen.prototype.results_option_build = function(options) {
+ var content, data, _i, _len, _ref;
+ content = '';
+ _ref = this.results_data;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ data = _ref[_i];
+ if (data.group) {
+ content += this.result_add_group(data);
+ } else {
+ content += this.result_add_option(data);
+ }
+ if (options != null ? options.first : void 0) {
+ if (data.selected && this.is_multiple) {
+ this.choice_build(data);
+ } else if (data.selected && !this.is_multiple) {
+ this.single_set_selected_text(this.choice_label(data));
+ }
+ }
+ }
+ return content;
+ };
+
+ AbstractChosen.prototype.result_add_option = function(option) {
+ var classes, option_el;
+ if (!option.search_match) {
+ return '';
+ }
+ if (!this.include_option_in_results(option)) {
+ return '';
+ }
+ classes = [];
+ if (!option.disabled && !(option.selected && this.is_multiple)) {
+ classes.push("active-result");
+ }
+ if (option.disabled && !(option.selected && this.is_multiple)) {
+ classes.push("disabled-result");
+ }
+ if (option.selected) {
+ classes.push("result-selected");
+ }
+ if (option.group_array_index != null) {
+ classes.push("group-option");
+ }
+ if (option.classes !== "") {
+ classes.push(option.classes);
+ }
+ option_el = document.createElement("li");
+ option_el.className = classes.join(" ");
+ option_el.style.cssText = option.style;
+ option_el.setAttribute("data-option-array-index", option.array_index);
+ option_el.innerHTML = option.search_text;
+ if (option.title) {
+ option_el.title = option.title;
+ }
+ return this.outerHTML(option_el);
+ };
+
+ AbstractChosen.prototype.result_add_group = function(group) {
+ var classes, group_el;
+ if (!(group.search_match || group.group_match)) {
+ return '';
+ }
+ if (!(group.active_options > 0)) {
+ return '';
+ }
+ classes = [];
+ classes.push("group-result");
+ if (group.classes) {
+ classes.push(group.classes);
+ }
+ group_el = document.createElement("li");
+ group_el.className = classes.join(" ");
+ group_el.innerHTML = group.search_text;
+ if (group.title) {
+ group_el.title = group.title;
+ }
+ return this.outerHTML(group_el);
+ };
+
+ AbstractChosen.prototype.results_update_field = function() {
+ this.set_default_text();
+ if (!this.is_multiple) {
+ this.results_reset_cleanup();
+ }
+ this.result_clear_highlight();
+ this.results_build();
+ if (this.results_showing) {
+ return this.winnow_results();
+ }
+ };
+
+ AbstractChosen.prototype.reset_single_select_options = function() {
+ var result, _i, _len, _ref, _results;
+ _ref = this.results_data;
+ _results = [];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ result = _ref[_i];
+ if (result.selected) {
+ _results.push(result.selected = false);
+ } else {
+ _results.push(void 0);
+ }
+ }
+ return _results;
+ };
+
+ AbstractChosen.prototype.results_toggle = function() {
+ if (this.results_showing) {
+ return this.results_hide();
+ } else {
+ return this.results_show();
+ }
+ };
+
+ AbstractChosen.prototype.results_search = function(evt) {
+ if (this.results_showing) {
+ return this.winnow_results();
+ } else {
+ return this.results_show();
+ }
+ };
+
+ AbstractChosen.prototype.winnow_results = function() {
+ var escapedSearchText, option, regex, results, results_group, searchText, startpos, text, zregex, _i, _len, _ref;
+ this.no_results_clear();
+ results = 0;
+ searchText = this.get_search_text();
+ escapedSearchText = searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+ zregex = new RegExp(escapedSearchText, 'i');
+ regex = this.get_search_regex(escapedSearchText);
+ _ref = this.results_data;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ option = _ref[_i];
+ option.search_match = false;
+ results_group = null;
+ if (this.include_option_in_results(option)) {
+ if (option.group) {
+ option.group_match = false;
+ option.active_options = 0;
+ }
+ if ((option.group_array_index != null) && this.results_data[option.group_array_index]) {
+ results_group = this.results_data[option.group_array_index];
+ if (results_group.active_options === 0 && results_group.search_match) {
+ results += 1;
+ }
+ results_group.active_options += 1;
+ }
+ option.search_text = option.group ? option.label : option.html;
+ if (!(option.group && !this.group_search)) {
+ option.search_match = this.search_string_match(option.search_text, regex);
+ if (option.search_match && !option.group) {
+ results += 1;
+ }
+ if (option.search_match) {
+ if (searchText.length) {
+ startpos = option.search_text.search(zregex);
+ text = option.search_text.substr(0, startpos + searchText.length) + '</em>' + option.search_text.substr(startpos + searchText.length);
+ option.search_text = text.substr(0, startpos) + '<em>' + text.substr(startpos);
+ }
+ if (results_group != null) {
+ results_group.group_match = true;
+ }
+ } else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match) {
+ option.search_match = true;
+ }
+ }
+ }
+ }
+ this.result_clear_highlight();
+ if (results < 1 && searchText.length) {
+ this.update_results_content("");
+ return this.no_results(searchText);
+ } else {
+ this.update_results_content(this.results_option_build());
+ return this.winnow_results_set_highlight();
+ }
+ };
+
+ AbstractChosen.prototype.get_search_regex = function(escaped_search_string) {
+ var regex_anchor;
+ regex_anchor = this.search_contains ? "" : "^";
+ return new RegExp(regex_anchor + escaped_search_string, 'i');
+ };
+
+ AbstractChosen.prototype.search_string_match = function(search_string, regex) {
+ var part, parts, _i, _len;
+ if (regex.test(search_string)) {
+ return true;
+ } else if (this.enable_split_word_search && (search_string.indexOf(" ") >= 0 || search_string.indexOf("[") === 0)) {
+ parts = search_string.replace(/\[|\]/g, "").split(" ");
+ if (parts.length) {
+ for (_i = 0, _len = parts.length; _i < _len; _i++) {
+ part = parts[_i];
+ if (regex.test(part)) {
+ return true;
+ }
+ }
+ }
+ }
+ };
+
+ AbstractChosen.prototype.choices_count = function() {
+ var option, _i, _len, _ref;
+ if (this.selected_option_count != null) {
+ return this.selected_option_count;
+ }
+ this.selected_option_count = 0;
+ _ref = this.form_field.options;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ option = _ref[_i];
+ if (option.selected) {
+ this.selected_option_count += 1;
+ }
+ }
+ return this.selected_option_count;
+ };
+
+ AbstractChosen.prototype.choices_click = function(evt) {
+ evt.preventDefault();
+ if (!(this.results_showing || this.is_disabled)) {
+ return this.results_show();
+ }
+ };
+
+ AbstractChosen.prototype.keyup_checker = function(evt) {
+ var stroke, _ref;
+ stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
+ this.search_field_scale();
+ switch (stroke) {
+ case 8:
+ if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0) {
+ return this.keydown_backstroke();
+ } else if (!this.pending_backstroke) {
+ this.result_clear_highlight();
+ return this.results_search();
+ }
+ break;
+ case 13:
+ evt.preventDefault();
+ if (this.results_showing) {
+ return this.result_select(evt);
+ }
+ break;
+ case 27:
+ if (this.results_showing) {
+ this.results_hide();
+ }
+ return true;
+ case 9:
+ case 38:
+ case 40:
+ case 16:
+ case 91:
+ case 17:
+ break;
+ default:
+ return this.results_search();
+ }
+ };
+
+ AbstractChosen.prototype.clipboard_event_checker = function(evt) {
+ var _this = this;
+ return setTimeout((function() {
+ return _this.results_search();
+ }), 50);
+ };
+
+ AbstractChosen.prototype.container_width = function() {
+ if (this.options.width != null) {
+ return this.options.width;
+ } else {
+ return "" + this.form_field.offsetWidth + "px";
+ }
+ };
+
+ AbstractChosen.prototype.include_option_in_results = function(option) {
+ if (this.is_multiple && (!this.display_selected_options && option.selected)) {
+ return false;
+ }
+ if (!this.display_disabled_options && option.disabled) {
+ return false;
+ }
+ if (option.empty) {
+ return false;
+ }
+ return true;
+ };
+
+ AbstractChosen.prototype.search_results_touchstart = function(evt) {
+ this.touch_started = true;
+ return this.search_results_mouseover(evt);
+ };
+
+ AbstractChosen.prototype.search_results_touchmove = function(evt) {
+ this.touch_started = false;
+ return this.search_results_mouseout(evt);
+ };
+
+ AbstractChosen.prototype.search_results_touchend = function(evt) {
+ if (this.touch_started) {
+ return this.search_results_mouseup(evt);
+ }
+ };
+
+ AbstractChosen.prototype.outerHTML = function(element) {
+ var tmp;
+ if (element.outerHTML) {
+ return element.outerHTML;
+ }
+ tmp = document.createElement("div");
+ tmp.appendChild(element);
+ return tmp.innerHTML;
+ };
+
+ AbstractChosen.browser_is_supported = function() {
+ if (window.navigator.appName === "Microsoft Internet Explorer") {
+ return document.documentMode >= 8;
+ }
+ if (/iP(od|hone)/i.test(window.navigator.userAgent)) {
+ return false;
+ }
+ if (/Android/i.test(window.navigator.userAgent)) {
+ if (/Mobile/i.test(window.navigator.userAgent)) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ AbstractChosen.default_multiple_text = "Select Some Options";
+
+ AbstractChosen.default_single_text = "Select an Option";
+
+ AbstractChosen.default_no_result_text = "No results match";
+
+ return AbstractChosen;
+
+ })();
+
+ $ = jQuery;
+
+ $.fn.extend({
+ chosen: function(options) {
+ if (!AbstractChosen.browser_is_supported()) {
+ return this;
+ }
+ return this.each(function(input_field) {
+ var $this, chosen;
+ $this = $(this);
+ chosen = $this.data('chosen');
+ if (options === 'destroy' && chosen instanceof Chosen) {
+ chosen.destroy();
+ } else if (!(chosen instanceof Chosen)) {
+ $this.data('chosen', new Chosen(this, options));
+ }
+ });
+ }
+ });
+
+ Chosen = (function(_super) {
+ __extends(Chosen, _super);
+
+ function Chosen() {
+ _ref = Chosen.__super__.constructor.apply(this, arguments);
+ return _ref;
+ }
+
+ Chosen.prototype.setup = function() {
+ this.form_field_jq = $(this.form_field);
+ this.current_selectedIndex = this.form_field.selectedIndex;
+ return this.is_rtl = this.form_field_jq.hasClass("chosen-rtl");
+ };
+
+ Chosen.prototype.set_up_html = function() {
+ var container_classes, container_props;
+ container_classes = ["chosen-container"];
+ container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single"));
+ if (this.inherit_select_classes && this.form_field.className) {
+ container_classes.push(this.form_field.className);
+ }
+ if (this.is_rtl) {
+ container_classes.push("chosen-rtl");
+ }
+ container_props = {
+ 'class': container_classes.join(' '),
+ 'style': "width: " + (this.container_width()) + ";",
+ 'title': this.form_field.title
+ };
+ if (this.form_field.id.length) {
+ container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen";
+ }
+ this.container = $("<div />", container_props);
+ if (this.is_multiple) {
+ this.container.html('<ul class="chosen-choices"><li class="search-field"><input type="text" value="' + this.default_text + '" class="default" autocomplete="off" style="width:25px;" /></li></ul><div class="chosen-drop"><ul class="chosen-results"></ul></div>');
+ } else {
+ this.container.html('<a class="chosen-single chosen-default" tabindex="-1"><span>' + this.default_text + '</span><div><b></b></div></a><div class="chosen-drop"><div class="chosen-search"><input type="text" autocomplete="off" /></div><ul class="chosen-results"></ul></div>');
+ }
+ this.form_field_jq.hide().after(this.container);
+ this.dropdown = this.container.find('div.chosen-drop').first();
+ this.search_field = this.container.find('input').first();
+ this.search_results = this.container.find('ul.chosen-results').first();
+ this.search_field_scale();
+ this.search_no_results = this.container.find('li.no-results').first();
+ if (this.is_multiple) {
+ this.search_choices = this.container.find('ul.chosen-choices').first();
+ this.search_container = this.container.find('li.search-field').first();
+ } else {
+ this.search_container = this.container.find('div.chosen-search').first();
+ this.selected_item = this.container.find('.chosen-single').first();
+ }
+ this.results_build();
+ this.set_tab_index();
+ return this.set_label_behavior();
+ };
+
+ Chosen.prototype.on_ready = function() {
+ return this.form_field_jq.trigger("chosen:ready", {
+ chosen: this
+ });
+ };
+
+ Chosen.prototype.register_observers = function() {
+ var _this = this;
+ this.container.bind('touchstart.chosen', function(evt) {
+ _this.container_mousedown(evt);
+ return evt.preventDefault();
+ });
+ this.container.bind('touchend.chosen', function(evt) {
+ _this.container_mouseup(evt);
+ return evt.preventDefault();
+ });
+ this.container.bind('mousedown.chosen', function(evt) {
+ _this.container_mousedown(evt);
+ });
+ this.container.bind('mouseup.chosen', function(evt) {
+ _this.container_mouseup(evt);
+ });
+ this.container.bind('mouseenter.chosen', function(evt) {
+ _this.mouse_enter(evt);
+ });
+ this.container.bind('mouseleave.chosen', function(evt) {
+ _this.mouse_leave(evt);
+ });
+ this.search_results.bind('mouseup.chosen', function(evt) {
+ _this.search_results_mouseup(evt);
+ });
+ this.search_results.bind('mouseover.chosen', function(evt) {
+ _this.search_results_mouseover(evt);
+ });
+ this.search_results.bind('mouseout.chosen', function(evt) {
+ _this.search_results_mouseout(evt);
+ });
+ this.search_results.bind('mousewheel.chosen DOMMouseScroll.chosen', function(evt) {
+ _this.search_results_mousewheel(evt);
+ });
+ this.search_results.bind('touchstart.chosen', function(evt) {
+ _this.search_results_touchstart(evt);
+ });
+ this.search_results.bind('touchmove.chosen', function(evt) {
+ _this.search_results_touchmove(evt);
+ });
+ this.search_results.bind('touchend.chosen', function(evt) {
+ _this.search_results_touchend(evt);
+ });
+ this.form_field_jq.bind("chosen:updated.chosen", function(evt) {
+ _this.results_update_field(evt);
+ });
+ this.form_field_jq.bind("chosen:activate.chosen", function(evt) {
+ _this.activate_field(evt);
+ });
+ this.form_field_jq.bind("chosen:open.chosen", function(evt) {
+ _this.container_mousedown(evt);
+ });
+ this.form_field_jq.bind("chosen:close.chosen", function(evt) {
+ _this.input_blur(evt);
+ });
+ this.search_field.bind('blur.chosen', function(evt) {
+ _this.input_blur(evt);
+ });
+ this.search_field.bind('keyup.chosen', function(evt) {
+ _this.keyup_checker(evt);
+ });
+ this.search_field.bind('keydown.chosen', function(evt) {
+ _this.keydown_checker(evt);
+ });
+ this.search_field.bind('focus.chosen', function(evt) {
+ _this.input_focus(evt);
+ });
+ this.search_field.bind('cut.chosen', function(evt) {
+ _this.clipboard_event_checker(evt);
+ });
+ this.search_field.bind('paste.chosen', function(evt) {
+ _this.clipboard_event_checker(evt);
+ });
+ if (this.is_multiple) {
+ return this.search_choices.bind('click.chosen', function(evt) {
+ _this.choices_click(evt);
+ });
+ } else {
+ return this.container.bind('click.chosen', function(evt) {
+ evt.preventDefault();
+ });
+ }
+ };
+
+ Chosen.prototype.destroy = function() {
+ $(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
+ if (this.search_field[0].tabIndex) {
+ this.form_field_jq[0].tabIndex = this.search_field[0].tabIndex;
+ }
+ this.container.remove();
+ this.form_field_jq.removeData('chosen');
+ return this.form_field_jq.show();
+ };
+
+ Chosen.prototype.search_field_disabled = function() {
+ this.is_disabled = this.form_field_jq[0].disabled;
+ if (this.is_disabled) {
+ this.container.addClass('chosen-disabled');
+ this.search_field[0].disabled = true;
+ if (!this.is_multiple) {
+ this.selected_item.unbind("focus.chosen", this.activate_action);
+ }
+ return this.close_field();
+ } else {
+ this.container.removeClass('chosen-disabled');
+ this.search_field[0].disabled = false;
+ if (!this.is_multiple) {
+ return this.selected_item.bind("focus.chosen", this.activate_action);
+ }
+ }
+ };
+
+ Chosen.prototype.container_mousedown = function(evt) {
+ if (!this.is_disabled) {
+ if (evt && evt.type === "mousedown" && !this.results_showing) {
+ evt.preventDefault();
+ }
+ if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close"))) {
+ if (!this.active_field) {
+ if (this.is_multiple) {
+ this.search_field.val("");
+ }
+ $(this.container[0].ownerDocument).bind('click.chosen', this.click_test_action);
+ this.results_show();
+ } else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chosen-single").length)) {
+ evt.preventDefault();
+ this.results_toggle();
+ }
+ return this.activate_field();
+ }
+ }
+ };
+
+ Chosen.prototype.container_mouseup = function(evt) {
+ if (evt.target.nodeName === "ABBR" && !this.is_disabled) {
+ return this.results_reset(evt);
+ }
+ };
+
+ Chosen.prototype.search_results_mousewheel = function(evt) {
+ var delta;
+ if (evt.originalEvent) {
+ delta = evt.originalEvent.deltaY || -evt.originalEvent.wheelDelta || evt.originalEvent.detail;
+ }
+ if (delta != null) {
+ evt.preventDefault();
+ if (evt.type === 'DOMMouseScroll') {
+ delta = delta * 40;
+ }
+ return this.search_results.scrollTop(delta + this.search_results.scrollTop());
+ }
+ };
+
+ Chosen.prototype.blur_test = function(evt) {
+ if (!this.active_field && this.container.hasClass("chosen-container-active")) {
+ return this.close_field();
+ }
+ };
+
+ Chosen.prototype.close_field = function() {
+ $(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
+ this.active_field = false;
+ this.results_hide();
+ this.container.removeClass("chosen-container-active");
+ this.clear_backstroke();
+ this.show_search_field_default();
+ return this.search_field_scale();
+ };
+
+ Chosen.prototype.activate_field = function() {
+ this.container.addClass("chosen-container-active");
+ this.active_field = true;
+ this.search_field.val(this.search_field.val());
+ return this.search_field.focus();
+ };
+
+ Chosen.prototype.test_active_click = function(evt) {
+ var active_container;
+ active_container = $(evt.target).closest('.chosen-container');
+ if (active_container.length && this.container[0] === active_container[0]) {
+ return this.active_field = true;
+ } else {
+ return this.close_field();
+ }
+ };
+
+ Chosen.prototype.results_build = function() {
+ this.parsing = true;
+ this.selected_option_count = null;
+ this.results_data = SelectParser.select_to_array(this.form_field);
+ if (this.is_multiple) {
+ this.search_choices.find("li.search-choice").remove();
+ } else if (!this.is_multiple) {
+ this.single_set_selected_text();
+ if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) {
+ this.search_field[0].readOnly = true;
+ this.container.addClass("chosen-container-single-nosearch");
+ } else {
+ this.search_field[0].readOnly = false;
+ this.container.removeClass("chosen-container-single-nosearch");
+ }
+ }
+ this.update_results_content(this.results_option_build({
+ first: true
+ }));
+ this.search_field_disabled();
+ this.show_search_field_default();
+ this.search_field_scale();
+ return this.parsing = false;
+ };
+
+ Chosen.prototype.result_do_highlight = function(el) {
+ var high_bottom, high_top, maxHeight, visible_bottom, visible_top;
+ if (el.length) {
+ this.result_clear_highlight();
+ this.result_highlight = el;
+ this.result_highlight.addClass("highlighted");
+ maxHeight = parseInt(this.search_results.css("maxHeight"), 10);
+ visible_top = this.search_results.scrollTop();
+ visible_bottom = maxHeight + visible_top;
+ high_top = this.result_highlight.position().top + this.search_results.scrollTop();
+ high_bottom = high_top + this.result_highlight.outerHeight();
+ if (high_bottom >= visible_bottom) {
+ return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0);
+ } else if (high_top < visible_top) {
+ return this.search_results.scrollTop(high_top);
+ }
+ }
+ };
+
+ Chosen.prototype.result_clear_highlight = function() {
+ if (this.result_highlight) {
+ this.result_highlight.removeClass("highlighted");
+ }
+ return this.result_highlight = null;
+ };
+
+ Chosen.prototype.results_show = function() {
+ if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
+ this.form_field_jq.trigger("chosen:maxselected", {
+ chosen: this
+ });
+ return false;
+ }
+ this.container.addClass("chosen-with-drop");
+ this.results_showing = true;
+ this.search_field.focus();
+ this.search_field.val(this.search_field.val());
+ this.winnow_results();
+ return this.form_field_jq.trigger("chosen:showing_dropdown", {
+ chosen: this
+ });
+ };
+
+ Chosen.prototype.update_results_content = function(content) {
+ return this.search_results.html(content);
+ };
+
+ Chosen.prototype.results_hide = function() {
+ if (this.results_showing) {
+ this.result_clear_highlight();
+ this.container.removeClass("chosen-with-drop");
+ this.form_field_jq.trigger("chosen:hiding_dropdown", {
+ chosen: this
+ });
+ }
+ return this.results_showing = false;
+ };
+
+ Chosen.prototype.set_tab_index = function(el) {
+ var ti;
+ if (this.form_field.tabIndex) {
+ ti = this.form_field.tabIndex;
+ this.form_field.tabIndex = -1;
+ return this.search_field[0].tabIndex = ti;
+ }
+ };
+
+ Chosen.prototype.set_label_behavior = function() {
+ var _this = this;
+ this.form_field_label = this.form_field_jq.parents("label");
+ if (!this.form_field_label.length && this.form_field.id.length) {
+ this.form_field_label = $("label[for='" + this.form_field.id + "']");
+ }
+ if (this.form_field_label.length > 0) {
+ return this.form_field_label.bind('click.chosen', function(evt) {
+ if (_this.is_multiple) {
+ return _this.container_mousedown(evt);
+ } else {
+ return _this.activate_field();
+ }
+ });
+ }
+ };
+
+ Chosen.prototype.show_search_field_default = function() {
+ if (this.is_multiple && this.choices_count() < 1 && !this.active_field) {
+ this.search_field.val(this.default_text);
+ return this.search_field.addClass("default");
+ } else {
+ this.search_field.val("");
+ return this.search_field.removeClass("default");
+ }
+ };
+
+ Chosen.prototype.search_results_mouseup = function(evt) {
+ var target;
+ target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
+ if (target.length) {
+ this.result_highlight = target;
+ this.result_select(evt);
+ return this.search_field.focus();
+ }
+ };
+
+ Chosen.prototype.search_results_mouseover = function(evt) {
+ var target;
+ target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
+ if (target) {
+ return this.result_do_highlight(target);
+ }
+ };
+
+ Chosen.prototype.search_results_mouseout = function(evt) {
+ if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first())) {
+ return this.result_clear_highlight();
+ }
+ };
+
+ Chosen.prototype.choice_build = function(item) {
+ var choice, close_link,
+ _this = this;
+ choice = $('<li />', {
+ "class": "search-choice"
+ }).html("<span>" + (this.choice_label(item)) + "</span>");
+ if (item.disabled) {
+ choice.addClass('search-choice-disabled');
+ } else {
+ close_link = $('<a />', {
+ "class": 'search-choice-close',
+ 'data-option-array-index': item.array_index
+ });
+ close_link.bind('click.chosen', function(evt) {
+ return _this.choice_destroy_link_click(evt);
+ });
+ choice.append(close_link);
+ }
+ return this.search_container.before(choice);
+ };
+
+ Chosen.prototype.choice_destroy_link_click = function(evt) {
+ evt.preventDefault();
+ evt.stopPropagation();
+ if (!this.is_disabled) {
+ return this.choice_destroy($(evt.target));
+ }
+ };
+
+ Chosen.prototype.choice_destroy = function(link) {
+ if (this.result_deselect(link[0].getAttribute("data-option-array-index"))) {
+ this.show_search_field_default();
+ if (this.is_multiple && this.choices_count() > 0 && this.search_field.val().length < 1) {
+ this.results_hide();
+ }
+ link.parents('li').first().remove();
+ return this.search_field_scale();
+ }
+ };
+
+ Chosen.prototype.results_reset = function() {
+ this.reset_single_select_options();
+ this.form_field.options[0].selected = true;
+ this.single_set_selected_text();
+ this.show_search_field_default();
+ this.results_reset_cleanup();
+ this.form_field_jq.trigger("change");
+ if (this.active_field) {
+ return this.results_hide();
+ }
+ };
+
+ Chosen.prototype.results_reset_cleanup = function() {
+ this.current_selectedIndex = this.form_field.selectedIndex;
+ return this.selected_item.find("abbr").remove();
+ };
+
+ Chosen.prototype.result_select = function(evt) {
+ var high, item;
+ if (this.result_highlight) {
+ high = this.result_highlight;
+ this.result_clear_highlight();
+ if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
+ this.form_field_jq.trigger("chosen:maxselected", {
+ chosen: this
+ });
+ return false;
+ }
+ if (this.is_multiple) {
+ high.removeClass("active-result");
+ } else {
+ this.reset_single_select_options();
+ }
+ high.addClass("result-selected");
+ item = this.results_data[high[0].getAttribute("data-option-array-index")];
+ item.selected = true;
+ this.form_field.options[item.options_index].selected = true;
+ this.selected_option_count = null;
+ if (this.is_multiple) {
+ this.choice_build(item);
+ } else {
+ this.single_set_selected_text(this.choice_label(item));
+ }
+ if (!((evt.metaKey || evt.ctrlKey) && this.is_multiple)) {
+ this.results_hide();
+ }
+ this.search_field.val("");
+ if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) {
+ this.form_field_jq.trigger("change", {
+ 'selected': this.form_field.options[item.options_index].value
+ });
+ }
+ this.current_selectedIndex = this.form_field.selectedIndex;
+ evt.preventDefault();
+ return this.search_field_scale();
+ }
+ };
+
+ Chosen.prototype.single_set_selected_text = function(text) {
+ if (text == null) {
+ text = this.default_text;
+ }
+ if (text === this.default_text) {
+ this.selected_item.addClass("chosen-default");
+ } else {
+ this.single_deselect_control_build();
+ this.selected_item.removeClass("chosen-default");
+ }
+ return this.selected_item.find("span").html(text);
+ };
+
+ Chosen.prototype.result_deselect = function(pos) {
+ var result_data;
+ result_data = this.results_data[pos];
+ if (!this.form_field.options[result_data.options_index].disabled) {
+ result_data.selected = false;
+ this.form_field.options[result_data.options_index].selected = false;
+ this.selected_option_count = null;
+ this.result_clear_highlight();
+ if (this.results_showing) {
+ this.winnow_results();
+ }
+ this.form_field_jq.trigger("change", {
+ deselected: this.form_field.options[result_data.options_index].value
+ });
+ this.search_field_scale();
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ Chosen.prototype.single_deselect_control_build = function() {
+ if (!this.allow_single_deselect) {
+ return;
+ }
+ if (!this.selected_item.find("abbr").length) {
+ this.selected_item.find("span").first().after("<abbr class=\"search-choice-close\"></abbr>");
+ }
+ return this.selected_item.addClass("chosen-single-with-deselect");
+ };
+
+ Chosen.prototype.get_search_text = function() {
+ return $('<div/>').text($.trim(this.search_field.val())).html();
+ };
+
+ Chosen.prototype.winnow_results_set_highlight = function() {
+ var do_high, selected_results;
+ selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : [];
+ do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first();
+ if (do_high != null) {
+ return this.result_do_highlight(do_high);
+ }
+ };
+
+ Chosen.prototype.no_results = function(terms) {
+ var no_results_html;
+ no_results_html = $('<li class="no-results">' + this.results_none_found + ' "<span></span>"</li>');
+ no_results_html.find("span").first().html(terms);
+ this.search_results.append(no_results_html);
+ return this.form_field_jq.trigger("chosen:no_results", {
+ chosen: this
+ });
+ };
+
+ Chosen.prototype.no_results_clear = function() {
+ return this.search_results.find(".no-results").remove();
+ };
+
+ Chosen.prototype.keydown_arrow = function() {
+ var next_sib;
+ if (this.results_showing && this.result_highlight) {
+ next_sib = this.result_highlight.nextAll("li.active-result").first();
+ if (next_sib) {
+ return this.result_do_highlight(next_sib);
+ }
+ } else {
+ return this.results_show();
+ }
+ };
+
+ Chosen.prototype.keyup_arrow = function() {
+ var prev_sibs;
+ if (!this.results_showing && !this.is_multiple) {
+ return this.results_show();
+ } else if (this.result_highlight) {
+ prev_sibs = this.result_highlight.prevAll("li.active-result");
+ if (prev_sibs.length) {
+ return this.result_do_highlight(prev_sibs.first());
+ } else {
+ if (this.choices_count() > 0) {
+ this.results_hide();
+ }
+ return this.result_clear_highlight();
+ }
+ }
+ };
+
+ Chosen.prototype.keydown_backstroke = function() {
+ var next_available_destroy;
+ if (this.pending_backstroke) {
+ this.choice_destroy(this.pending_backstroke.find("a").first());
+ return this.clear_backstroke();
+ } else {
+ next_available_destroy = this.search_container.siblings("li.search-choice").last();
+ if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled")) {
+ this.pending_backstroke = next_available_destroy;
+ if (this.single_backstroke_delete) {
+ return this.keydown_backstroke();
+ } else {
+ return this.pending_backstroke.addClass("search-choice-focus");
+ }
+ }
+ }
+ };
+
+ Chosen.prototype.clear_backstroke = function() {
+ if (this.pending_backstroke) {
+ this.pending_backstroke.removeClass("search-choice-focus");
+ }
+ return this.pending_backstroke = null;
+ };
+
+ Chosen.prototype.keydown_checker = function(evt) {
+ var stroke, _ref1;
+ stroke = (_ref1 = evt.which) != null ? _ref1 : evt.keyCode;
+ this.search_field_scale();
+ if (stroke !== 8 && this.pending_backstroke) {
+ this.clear_backstroke();
+ }
+ switch (stroke) {
+ case 8:
+ this.backstroke_length = this.search_field.val().length;
+ break;
+ case 9:
+ if (this.results_showing && !this.is_multiple) {
+ this.result_select(evt);
+ }
+ this.mouse_on_container = false;
+ break;
+ case 13:
+ if (this.results_showing) {
+ evt.preventDefault();
+ }
+ break;
+ case 32:
+ if (this.disable_search) {
+ evt.preventDefault();
+ }
+ break;
+ case 38:
+ evt.preventDefault();
+ this.keyup_arrow();
+ break;
+ case 40:
+ evt.preventDefault();
+ this.keydown_arrow();
+ break;
+ }
+ };
+
+ Chosen.prototype.search_field_scale = function() {
+ var div, f_width, h, style, style_block, styles, w, _i, _len;
+ if (this.is_multiple) {
+ h = 0;
+ w = 0;
+ style_block = "position:absolute; left: -1000px; top: -1000px; display:none;";
+ styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing'];
+ for (_i = 0, _len = styles.length; _i < _len; _i++) {
+ style = styles[_i];
+ style_block += style + ":" + this.search_field.css(style) + ";";
+ }
+ div = $('<div />', {
+ 'style': style_block
+ });
+ div.text(this.search_field.val());
+ $('body').append(div);
+ w = div.width() + 25;
+ div.remove();
+ f_width = this.container.outerWidth();
+ if (w > f_width - 10) {
+ w = f_width - 10;
+ }
+ return this.search_field.css({
+ 'width': w + 'px'
+ });
+ }
+ };
+
+ return Chosen;
+
+ })(AbstractChosen);
+
+}).call(this);
diff --git a/platform/www/lib/plugins/farmer/script/plugins.js b/platform/www/lib/plugins/farmer/script/plugins.js
new file mode 100644
index 0000000..f092b39
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/script/plugins.js
@@ -0,0 +1,149 @@
+/**
+ * DokuWiki Plugin farmer (JS for plugin management)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Michael Große <grosse@cosmocode.de>
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+(function () {
+ 'use strict';
+
+ jQuery(function () {
+ // general animal select
+ var $animalSelect = jQuery('select.farmer_chosen_animals');
+ $animalSelect.chosen({
+ width: '100%',
+ search_contains: true,
+ allow_single_deselect: true,
+ "placeholder_text_single": LANG.plugins.farmer.animalSelect
+ });
+
+ jQuery('select.acl_chosen').chosen({
+ disable_search: true,
+ width: '100%'
+ });
+
+
+ // Plugin Management for all Animals
+ var $formAllAnimals = jQuery('#farmer__pluginsforall');
+ $formAllAnimals.find('select')
+ .change(function () {
+ $formAllAnimals.find('button').prop('disabled', false);
+ })
+ .chosen({
+ width: '100%',
+ search_contains: true,
+ "placeholder_text_single": LANG.plugins.farmer.pluginSelect
+ })
+ ;
+
+ // Plugin Management for single Animals
+ var $formSingleAnimal = jQuery('#farmer__pluginsforone');
+ $formSingleAnimal.find('select')
+ .change(function () {
+ var animal = jQuery(this).val();
+ $formSingleAnimal.find('button').prop('disabled', true);
+ jQuery.post(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ {
+ call: 'plugin_farmer_getPlugins_' + animal
+ },
+ function (data) {
+ $formSingleAnimal.find('div.output').html(data);
+ $formSingleAnimal.find('button').prop('disabled', false);
+ },
+ 'html'
+ )}
+ )
+ .chosen({
+ width: '100%',
+ search_contains: true,
+ "placeholder_text_single": LANG.plugins.farmer.animalSelect
+ })
+ ;
+
+ /**
+ * Handle clicks on the matrix
+ */
+ var $formPluginMatrix = jQuery('#farmer__pluginmatrix').hide();
+ $formPluginMatrix.on('click', 'td', function () {
+ var $td = jQuery(this);
+ $td.html('⌛').css('background-color','transparent');
+ jQuery.post(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ {
+ call: 'plugin_farmer_modPlugin',
+ plugin: $td.data('plugin'),
+ ani: $td.data('animal')
+ },
+ function (data) {
+ $td.replaceWith(data);
+ },
+ 'html'
+ );
+ });
+
+ /**
+ * show the matrix interface
+ */
+ function showMatrix() {
+ jQuery.post(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ {
+ call: 'plugin_farmer_getPluginMatrix'
+ },
+ function (data) {
+ $formPluginMatrix.html(data);
+ $formPluginMatrix.show();
+ },
+ 'html'
+ )
+ }
+
+ // make sure there's enough space for the dropdown
+ $animalSelect.on('chosen:showing_dropdown', function (evt, params) {
+ jQuery(evt.target).parent('fieldset').animate({
+ "padding-bottom": '20em'
+ }, 400);
+ }).on('chosen:hiding_dropdown', function (evt, params) {
+ jQuery(evt.target).parent('fieldset').animate({
+ "padding-bottom": '7px'
+ }, 400);
+ });
+
+ var $aclPolicyFieldset = jQuery('#aclPolicyFieldset');
+ if ($aclPolicyFieldset.length) {
+ $animalSelect.on('change', function (evt, params) {
+ var $this = jQuery(this);
+ if ($this.val() === '') {
+ $aclPolicyFieldset.slideDown();
+ } else {
+ $aclPolicyFieldset.slideUp();
+ }
+ });
+ }
+
+
+
+
+ jQuery("input[name=bulkSingleSwitch]:radio").change(function () {
+ if (jQuery('#farmer__bulk').prop("checked")) {
+ $formAllAnimals.show();
+ $formSingleAnimal.hide();
+ $formPluginMatrix.hide();
+ } else if (jQuery('#farmer__single').prop("checked")) {
+ $formAllAnimals.hide();
+ $formSingleAnimal.show();
+ $formPluginMatrix.hide();
+ } else {
+ $formAllAnimals.hide();
+ $formSingleAnimal.hide();
+ showMatrix();
+ }
+ });
+ jQuery('#farmer__bulk').click();
+
+
+ });
+
+})();
diff --git a/platform/www/lib/plugins/farmer/style.less b/platform/www/lib/plugins/farmer/style.less
new file mode 100644
index 0000000..905078c
--- /dev/null
+++ b/platform/www/lib/plugins/farmer/style.less
@@ -0,0 +1,104 @@
+#plugin__farmer_admin {
+
+ .panelHeader {
+ background-color: #eee;
+ margin: 0;
+ padding: 10px 10px 8px;
+ overflow: hidden;
+ border-right: 1px solid @ini_border;
+ border-left: 1px solid @ini_border;
+ }
+
+ .panelMain,
+ .panelFooter {
+ padding: 1em;
+ border-right: 1px solid @ini_border;
+ border-left: 1px solid @ini_border;
+ }
+
+ .panelFooter {
+ border-bottom: 1px solid @ini_border;
+ }
+
+ form {
+ display: block;
+ text-align: center;
+
+ fieldset {
+ width: 80%;
+ padding: 1em;
+ }
+
+ label {
+ text-align: left;
+ display: block;
+ margin-bottom: 0.5em;
+
+ &:hover {
+ background-color: @ini_background_alt;
+ }
+
+ span {
+ display: inline-block;
+ width: 40%;
+ }
+ }
+
+ .chosen-container {
+ margin-bottom: 0.5em;
+ }
+
+ button {
+ margin: 1em;
+ }
+ }
+
+ .pluginmatrix {
+ width: 80%;
+ margin: 1em auto;
+
+ table {
+ thead th {
+ position: relative;
+ height: 8em;
+ vertical-align: bottom;
+ overflow: visible;
+ background-color: transparent;
+ border: none;
+
+ div {
+ transform-origin: bottom left;
+ transform: rotate(-60deg);
+ position: absolute;
+ left: 1em;
+ bottom: 0;
+ z-index: 5;
+ }
+ }
+
+ tbody {
+ position: relative;
+
+ td {
+ cursor: pointer;
+ }
+
+ th.off,
+ td.off {
+ background-color: #c33;
+ }
+
+ th.on,
+ td.on {
+ background-color: #3c3;
+ }
+
+ th.default,
+ td.default {
+ opacity: 0.5;
+ }
+ }
+ }
+ }
+
+}
diff --git a/platform/www/lib/plugins/fastwiki b/platform/www/lib/plugins/fastwiki
new file mode 160000
+Subproject 82b64eac47dbf50c2668b11eeaf901be63994a8
diff --git a/platform/www/lib/plugins/index.html b/platform/www/lib/plugins/index.html
new file mode 100644
index 0000000..977f90e
--- /dev/null
+++ b/platform/www/lib/plugins/index.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="refresh" content="0; URL=../../" />
+<meta name="robots" content="noindex" />
+<title>nothing here...</title>
+</head>
+<body>
+<!-- this is just here to prevent directory browsing -->
+</body>
+</html>
diff --git a/platform/www/lib/plugins/info/plugin.info.txt b/platform/www/lib/plugins/info/plugin.info.txt
new file mode 100644
index 0000000..f4c6201
--- /dev/null
+++ b/platform/www/lib/plugins/info/plugin.info.txt
@@ -0,0 +1,7 @@
+base info
+author Andreas Gohr
+email andi@splitbrain.org
+date 2020-06-04
+name Info Plugin
+desc Displays information about various DokuWiki internals
+url http://dokuwiki.org/plugin:info
diff --git a/platform/www/lib/plugins/info/syntax.php b/platform/www/lib/plugins/info/syntax.php
new file mode 100644
index 0000000..0d1e389
--- /dev/null
+++ b/platform/www/lib/plugins/info/syntax.php
@@ -0,0 +1,302 @@
+<?php
+/**
+ * Info Plugin: Displays information about various DokuWiki internals
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Esther Brunner <wikidesign@gmail.com>
+ */
+class syntax_plugin_info extends DokuWiki_Syntax_Plugin
+{
+
+ /**
+ * What kind of syntax are we?
+ */
+ public function getType()
+ {
+ return 'substition';
+ }
+
+ /**
+ * What about paragraphs?
+ */
+ public function getPType()
+ {
+ return 'block';
+ }
+
+ /**
+ * Where to sort in?
+ */
+ public function getSort()
+ {
+ return 155;
+ }
+
+
+ /**
+ * Connect pattern to lexer
+ */
+ public function connectTo($mode)
+ {
+ $this->Lexer->addSpecialPattern('~~INFO:\w+~~', $mode, 'plugin_info');
+ }
+
+ /**
+ * Handle the match
+ *
+ * @param string $match The text matched by the patterns
+ * @param int $state The lexer state for the match
+ * @param int $pos The character position of the matched text
+ * @param Doku_Handler $handler The Doku_Handler object
+ * @return array Return an array with all data you want to use in render
+ */
+ public function handle($match, $state, $pos, Doku_Handler $handler)
+ {
+ $match = substr($match, 7, -2); //strip ~~INFO: from start and ~~ from end
+ return array(strtolower($match));
+ }
+
+ /**
+ * Create output
+ *
+ * @param string $format string output format being rendered
+ * @param Doku_Renderer $renderer the current renderer object
+ * @param array $data data created by handler()
+ * @return boolean rendered correctly?
+ */
+ public function render($format, Doku_Renderer $renderer, $data)
+ {
+ if ($format == 'xhtml') {
+ /** @var Doku_Renderer_xhtml $renderer */
+ //handle various info stuff
+ switch ($data[0]) {
+ case 'syntaxmodes':
+ $renderer->doc .= $this->renderSyntaxModes();
+ break;
+ case 'syntaxtypes':
+ $renderer->doc .= $this->renderSyntaxTypes();
+ break;
+ case 'syntaxplugins':
+ $this->renderPlugins('syntax', $renderer);
+ break;
+ case 'adminplugins':
+ $this->renderPlugins('admin', $renderer);
+ break;
+ case 'actionplugins':
+ $this->renderPlugins('action', $renderer);
+ break;
+ case 'rendererplugins':
+ $this->renderPlugins('renderer', $renderer);
+ break;
+ case 'helperplugins':
+ $this->renderPlugins('helper', $renderer);
+ break;
+ case 'authplugins':
+ $this->renderPlugins('auth', $renderer);
+ break;
+ case 'remoteplugins':
+ $this->renderPlugins('remote', $renderer);
+ break;
+ case 'helpermethods':
+ $this->renderHelperMethods($renderer);
+ break;
+ case 'datetime':
+ $renderer->doc .= date('r');
+ break;
+ default:
+ $renderer->doc .= "no info about ".htmlspecialchars($data[0]);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * list all installed plugins
+ *
+ * uses some of the original renderer methods
+ *
+ * @param string $type
+ * @param Doku_Renderer_xhtml $renderer
+ */
+ protected function renderPlugins($type, Doku_Renderer_xhtml $renderer)
+ {
+ global $lang;
+ $renderer->doc .= '<ul>';
+
+ $plugins = plugin_list($type);
+ $plginfo = array();
+
+ // remove subparts
+ foreach ($plugins as $p) {
+ if (!$po = plugin_load($type, $p)) continue;
+ list($name,/* $part */) = explode('_', $p, 2);
+ $plginfo[$name] = $po->getInfo();
+ }
+
+ // list them
+ foreach ($plginfo as $info) {
+ $renderer->doc .= '<li><div class="li">';
+ $renderer->externallink($info['url'], $info['name']);
+ $renderer->doc .= ' ';
+ $renderer->doc .= '<em>'.$info['date'].'</em>';
+ $renderer->doc .= ' ';
+ $renderer->doc .= $lang['by'];
+ $renderer->doc .= ' ';
+ $renderer->emaillink($info['email'], $info['author']);
+ $renderer->doc .= '<br />';
+ $renderer->doc .= strtr(hsc($info['desc']), array("\n"=>"<br />"));
+ $renderer->doc .= '</div></li>';
+ unset($po);
+ }
+
+ $renderer->doc .= '</ul>';
+ }
+
+ /**
+ * list all installed plugins
+ *
+ * uses some of the original renderer methods
+ *
+ * @param Doku_Renderer_xhtml $renderer
+ */
+ protected function renderHelperMethods(Doku_Renderer_xhtml $renderer)
+ {
+ $plugins = plugin_list('helper');
+ foreach ($plugins as $p) {
+ if (!$po = plugin_load('helper', $p)) continue;
+
+ if (!method_exists($po, 'getMethods')) continue;
+ $methods = $po->getMethods();
+ $info = $po->getInfo();
+
+ $hid = $this->addToToc($info['name'], 2, $renderer);
+ $doc = '<h2><a name="'.$hid.'" id="'.$hid.'">'.hsc($info['name']).'</a></h2>';
+ $doc .= '<div class="level2">';
+ $doc .= '<p>'.strtr(hsc($info['desc']), array("\n"=>"<br />")).'</p>';
+ $doc .= '<pre class="code">$'.$p." = plugin_load('helper', '".$p."');</pre>";
+ $doc .= '</div>';
+ foreach ($methods as $method) {
+ $title = '$'.$p.'->'.$method['name'].'()';
+ $hid = $this->addToToc($title, 3, $renderer);
+ $doc .= '<h3><a name="'.$hid.'" id="'.$hid.'">'.hsc($title).'</a></h3>';
+ $doc .= '<div class="level3">';
+ $doc .= '<div class="table"><table class="inline"><tbody>';
+ $doc .= '<tr><th>Description</th><td colspan="2">'.$method['desc'].
+ '</td></tr>';
+ if ($method['params']) {
+ $c = count($method['params']);
+ $doc .= '<tr><th rowspan="'.$c.'">Parameters</th><td>';
+ $params = array();
+ foreach ($method['params'] as $desc => $type) {
+ $params[] = hsc($desc).'</td><td>'.hsc($type);
+ }
+ $doc .= join('</td></tr><tr><td>', $params).'</td></tr>';
+ }
+ if ($method['return']) {
+ $doc .= '<tr><th>Return value</th><td>'.hsc(key($method['return'])).
+ '</td><td>'.hsc(current($method['return'])).'</td></tr>';
+ }
+ $doc .= '</tbody></table></div>';
+ $doc .= '</div>';
+ }
+ unset($po);
+
+ $renderer->doc .= $doc;
+ }
+ }
+
+ /**
+ * lists all known syntax types and their registered modes
+ *
+ * @return string
+ */
+ protected function renderSyntaxTypes()
+ {
+ global $PARSER_MODES;
+ $doc = '';
+
+ $doc .= '<div class="table"><table class="inline"><tbody>';
+ foreach ($PARSER_MODES as $mode => $modes) {
+ $doc .= '<tr>';
+ $doc .= '<td class="leftalign">';
+ $doc .= $mode;
+ $doc .= '</td>';
+ $doc .= '<td class="leftalign">';
+ $doc .= join(', ', $modes);
+ $doc .= '</td>';
+ $doc .= '</tr>';
+ }
+ $doc .= '</tbody></table></div>';
+ return $doc;
+ }
+
+ /**
+ * lists all known syntax modes and their sorting value
+ *
+ * @return string
+ */
+ protected function renderSyntaxModes()
+ {
+ $modes = p_get_parsermodes();
+
+ $compactmodes = array();
+ foreach ($modes as $mode) {
+ $compactmodes[$mode['sort']][] = $mode['mode'];
+ }
+ $doc = '';
+ $doc .= '<div class="table"><table class="inline"><tbody>';
+
+ foreach ($compactmodes as $sort => $modes) {
+ $rowspan = '';
+ if (count($modes) > 1) {
+ $rowspan = ' rowspan="'.count($modes).'"';
+ }
+
+ foreach ($modes as $index => $mode) {
+ $doc .= '<tr>';
+ $doc .= '<td class="leftalign">';
+ $doc .= $mode;
+ $doc .= '</td>';
+
+ if ($index === 0) {
+ $doc .= '<td class="rightalign" '.$rowspan.'>';
+ $doc .= $sort;
+ $doc .= '</td>';
+ }
+ $doc .= '</tr>';
+ }
+ }
+
+ $doc .= '</tbody></table></div>';
+ return $doc;
+ }
+
+ /**
+ * Adds a TOC item
+ *
+ * @param string $text
+ * @param int $level
+ * @param Doku_Renderer_xhtml $renderer
+ * @return string
+ */
+ protected function addToToc($text, $level, Doku_Renderer_xhtml $renderer)
+ {
+ global $conf;
+
+ $hid = '';
+ if (($level >= $conf['toptoclevel']) && ($level <= $conf['maxtoclevel'])) {
+ $hid = $renderer->_headerToLink($text, true);
+ $renderer->toc[] = array(
+ 'hid' => $hid,
+ 'title' => $text,
+ 'type' => 'ul',
+ 'level' => $level - $conf['toptoclevel'] + 1
+ );
+ }
+ return $hid;
+ }
+}
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/markdowku/LICENSE b/platform/www/lib/plugins/markdowku/LICENSE
new file mode 100644
index 0000000..40ff04a
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/LICENSE
@@ -0,0 +1,27 @@
+BSD 2-Clause License
+
+Copyright (c) 2017, Julian Fagir <gnrp@komkon2.de>
+Copyright (c) 2020, Raphael Wimmer <raphael.wimmer@ur.de>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/platform/www/lib/plugins/markdowku/README.md b/platform/www/lib/plugins/markdowku/README.md
new file mode 100644
index 0000000..d5b199a
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/README.md
@@ -0,0 +1,2 @@
+# dokuwiki-plugin-markdowku
+Markdowku - markdown syntax plugin for DokuWiki
diff --git a/platform/www/lib/plugins/markdowku/manager.dat b/platform/www/lib/plugins/markdowku/manager.dat
new file mode 100644
index 0000000..664b6c7
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/manager.dat
@@ -0,0 +1,2 @@
+downloadurl=https://github.com/Medieninformatik-Regensburg/dokuwiki-plugin-markdowku/archive/refs/heads/master.zip
+installed=Sun, 06 Mar 2022 22:47:06 +0000
diff --git a/platform/www/lib/plugins/markdowku/plugin.info.txt b/platform/www/lib/plugins/markdowku/plugin.info.txt
new file mode 100644
index 0000000..7682646
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/plugin.info.txt
@@ -0,0 +1,7 @@
+base markdowku
+author Julian Fagir, Raphael Wimmer (maintainer)
+email raphael.wimmer@ur.de
+date 2021-12-04
+name Markdowku
+desc Integrates Markdown into Dokuwiki syntax
+url https://www.dokuwiki.org/plugin:markdowku
diff --git a/platform/www/lib/plugins/markdowku/syntax/anchorsinline.php b/platform/www/lib/plugins/markdowku/syntax/anchorsinline.php
new file mode 100644
index 0000000..b16f2b4
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/anchorsinline.php
@@ -0,0 +1,46 @@
+<?php
+/*
+ * Inline links [name](target "title")
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_anchorsinline extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'substition'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 102; }
+
+ function connectTo($mode) {
+ $this->nested_brackets_re =
+ str_repeat('(?>[^\[\]]+|\[', 6).
+ str_repeat('\])*', 6);
+ $this->Lexer->addSpecialPattern(
+ '\['.$this->nested_brackets_re.'\]\([ \t]*<?.+?>?[ \t]*(?:[\'"].*?[\'"])?\)',
+ $mode,
+ 'plugin_markdowku_anchorsinline'
+ );
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ if ($state == DOKU_LEXER_SPECIAL) {
+ $text = preg_match(
+ '/^\[('.$this->nested_brackets_re.')\]\([ \t]*<?(.+?)>?[ \t]*(?:[\'"](.*?)[\'"])?[ \t]*?\)$/',
+ $match,
+ $matches);
+ $target = $matches[2] == '' ? $matches[3] : $matches[2];
+ $title = $matches[1];
+
+ $target = preg_replace('/^mailto:/', '', $target);
+ $handler->internallink($target.'|'.$title, $state, $pos);
+ }
+ return true;
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/anchorsreference.php b/platform/www/lib/plugins/markdowku/syntax/anchorsreference.php
new file mode 100644
index 0000000..be8cdd6
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/anchorsreference.php
@@ -0,0 +1,62 @@
+<?php
+/*
+ * Reference links, i.e.
+ * ... [name][id] ...
+ * ... [id][] ...
+ * ...
+ * [id]: http://example.com (handled by markdowku_references)
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_anchorsreference extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'substition'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 102; }
+
+ function connectTo($mode) {
+ $this->nested_brackets_re =
+ str_repeat('(?>[^\[\]]+|\[', 3).
+ str_repeat('\])*', 3);
+ $this->Lexer->addSpecialPattern(
+ '\['.$this->nested_brackets_re.'\][ ]?(?:\n[ ]*)?\[[^\[\]\n]*?\]',
+ $mode,
+ 'plugin_markdowku_anchorsreference');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($state, $match);
+ }
+
+ function render($mode, Doku_Renderer$renderer, $data) {
+ global $ID;
+ preg_match(
+ '/^\[('.$this->nested_brackets_re.')\][ ]?(?:\n[ ]*)?\[(.*?)\]$/',
+ $data[1],
+ $matches);
+
+ $title = $matches[1];
+
+ if ($matches[2] == '')
+ $rid = $matches[1];
+ else
+ $rid = $matches[2];
+
+ $rid = preg_replace("/ /", ".", $rid);
+ $target = p_get_metadata($ID, 'markdowku_references_'.$rid, METADATA_RENDER_USING_CACHE);
+ if ($target == '') {
+ $renderer->cdata($data[1]);
+ } else if (preg_match('/^mailto:/', $target) or
+ preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>', $target)) {
+ $target = preg_replace('/^mailto:/', '', $target);
+ $renderer->emaillink($target, $title);
+ } else {
+ $renderer->externallink($target, $title);
+ }
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/autolinks.php b/platform/www/lib/plugins/markdowku/syntax/autolinks.php
new file mode 100644
index 0000000..b6ed523
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/autolinks.php
@@ -0,0 +1,40 @@
+<?php
+/*
+ * Autolinks enclosed in <...>
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_autolinks extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'substition'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 102; }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '<(?:https?|ftp|mailto):[^\'">\s]+?>',
+ $mode,
+ 'plugin_markdowku_autolinks'
+ );
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ if (preg_match('/^<mailto:/', $match)) {
+ $match = substr($match, 8, -1);
+ $handler->_addCall('emaillink', array($match, NULL), $pos);
+ } else {
+ $match = substr($match, 1, -1);
+ $handler->_addCall('externallink', array($match, NULL), $pos);
+ }
+
+ return true;
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/blockquotes.php b/platform/www/lib/plugins/markdowku/syntax/blockquotes.php
new file mode 100644
index 0000000..463c049
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/blockquotes.php
@@ -0,0 +1,115 @@
+<?php
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+include_once('headeratx.php');
+
+use dokuwiki\Parsing\Handler\Quote;
+
+class syntax_plugin_markdowku_blockquotes extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'container'; }
+ function getPType() { return 'block'; }
+ function getSort() { return 219; }
+ function getAllowedTypes() {
+ return array('formatting', 'substition', 'disabled', 'protected',
+ 'container');
+ }
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern(
+ // (?<=\n)[ \t]*>[ \t]?(?=(?:\n[ ]*[^>]|\Z))
+// '\n[ \t]*>[ \t]?.+?\n(?:.+\n)*',
+// '(?:\n|\A)[ \t]*>(?:[ >\t]*)?', //[ \t]?(?=[^\n]+?\n)',
+ '(?:\n|\A)[ \t]*>(?:[ >\t]*)?.*?(?=\n)', //[ \t]?(?=[^\n]+?\n)',
+ $mode,
+ 'plugin_markdowku_blockquotes');
+
+ /* Setext headers need two lines */
+ $this->Lexer->addPattern(
+ '\n[ \t]*>(?:[ \t>]*>)?[ \t]?[^\n]+?[ \t]*\n[ \t]*>(?:[ \t>]*>)?[ \t]?=+[ \t]*',
+ 'plugin_markdowku_blockquotes');
+
+ $this->Lexer->addPattern(
+ '\n[ \t]*>(?:[ \t>]*>)?[ \t]?[^\n]+?[ \t]*\n[ \t]*>(?:[ \t>]*>)?[ \t]?-+[ \t]*',
+ 'plugin_markdowku_blockquotes');
+
+ $this->Lexer->addPattern(
+// '\n[ \t]*>(?:[ \t>]*>)?[ \t]?', //[ \t]?(?=[^\n]+?\n)',
+ '\n[ \t]*>(?:[ \t>]*>)?[ \t]?.*?(?=\n)', //[ \t]?(?=[^\n]+?\n)',
+ 'plugin_markdowku_blockquotes');
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern(
+ '(?:\n[^>]|\Z)',
+ 'plugin_markdowku_blockquotes');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ global $DOKU_PLUGINS;
+
+ preg_match('/^\n[ \t]*>(?:[ \t>]*>)?[ \t]?/', $match, $quotearg);
+ $quoteinarg = preg_replace('/^\n[ \t]*>(?:[ \t>]*>)?[ \t]?/', '', $match);
+
+ if ($state == DOKU_LEXER_ENTER) {
+ $ReWriter = new Doku_Handler_Markdown_Quote($handler->getCallWriter());
+ $handler->setCallWriter($ReWriter);
+ $handler->_addCall('quote_start', $quotearg, $pos);
+ } elseif ($state == DOKU_LEXER_EXIT) {
+ $handler->_addCall('quote_end', array(), $pos);
+ $handler->getCallWriter()->process();
+ $ReWriter = & $handler->getCallWriter();
+ $handler->setCallWriter($ReWriter->getCallWriter());
+ }
+
+ if ($quoteinarg == '') {
+ $handler->_addCall('quote_newline', $quotearg, $pos);
+ /* ATX headers (headeratx) */
+ } elseif (preg_match('/^\#{1,6}[ \t]*.+?[ \t]*\#*/', $quoteinarg)) {
+ $plugin =& plugin_load('syntax', 'markdowku_headeratx');
+ $plugin->handle($quoteinarg, $state, $pos, $handler);
+ /* Horizontal rulers (hr) */
+ } elseif (preg_match('/[ ]{0,2}(?:[ ]?_[ ]?){3,}[ \t]*/', $quoteinarg)
+ or preg_match('/[ ]{0,2}(?:[ ]?-[ ]?){3,}[ \t]*/', $quoteinarg)
+ or preg_match('/[ ]{0,2}(?:[ ]?\*[ ]?){3,}[ \t]*/', $quoteinarg)) {
+ $plugin =& plugin_load('syntax', 'markdowku_hr');
+ $plugin->handle($quoteinarg, $state, $pos, $handler);
+ /* Setext headers (headersetext) */
+ } elseif (preg_match('/^[^\n]+?[ \t]*\n[ \t]*>(?:[ \t>]*>)?[ \t]?=+[ \t]*/', $quoteinarg)
+ or preg_match('/^[^\n]+?[ \t]*\n[ \t]*>(?:[ \t>]*>)?[ \t]?-+[ \t]*/', $quoteinarg)) {
+ $quoteinarg = preg_replace('/(?<=\n)[ \t]*>(?:[ \t>]*>)?[ \t]?/', '', $quoteinarg);
+ $plugin =& plugin_load('syntax', 'markdowku_headersetext');
+ $plugin->handle($quoteinarg, $state, $pos, $handler);
+ } else {
+ $handler->_addCall('cdata', array($quoteinarg), $pos);
+ }
+
+ return true;
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ return true;
+ }
+}
+
+class Doku_Handler_Markdown_Quote extends Quote {
+ function getDepth($marker) {
+ $quoteLength = 0;
+ $position = 0;
+ $text = preg_replace('/^\n*/', '', $marker);
+ while (TRUE) {
+ if (preg_match('/^[ \t]/', substr($text, $position)) > 0) {
+ $position++;
+ } elseif (preg_match('/^>/', substr($text, $position)) > 0) {
+ $position++;
+ $quoteLength++;
+ } else {
+ break;
+ }
+ }
+ return $quoteLength;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/boldasterisk.php b/platform/www/lib/plugins/markdowku/syntax/boldasterisk.php
new file mode 100644
index 0000000..0dcad52
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/boldasterisk.php
@@ -0,0 +1,45 @@
+<?php
+/*
+ * Bold text enclosed in asterisks: **...**
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_boldasterisk extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'formatting'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 69; }
+ function getAllowedTypes() { return array('formatting', 'substition'); }
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern(
+ '(?<![\\\\*])\*\*(?![ ])(?=(?:(?!\n\n).)+?[^\\\\ ]\*\*)',
+ $mode,
+ 'plugin_markdowku_boldasterisk');
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern(
+ '(?<![\\\\ ])\*\*',
+ 'plugin_markdowku_boldasterisk');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($state, $match);
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ if ($data[0] == DOKU_LEXER_ENTER)
+ $renderer->strong_open();
+ elseif ($data[0] == DOKU_LEXER_EXIT)
+ $renderer->strong_close();
+ else
+ $renderer->cdata($data[1]);
+
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/codeblocks.php b/platform/www/lib/plugins/markdowku/syntax/codeblocks.php
new file mode 100644
index 0000000..bbda567
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/codeblocks.php
@@ -0,0 +1,63 @@
+<?php
+/*
+ * Codeblocks, indented by four spaces
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+use dokuwiki\Parsing\Handler\Preformatted;
+
+class syntax_plugin_markdowku_codeblocks extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'protected'; }
+ function getPType() { return 'block'; }
+ function getSort() { return 199; }
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern(
+ '(?:\n\n|\A\n?) ',
+ $mode,
+ 'plugin_markdowku_codeblocks');
+
+ $this->Lexer->addPattern(
+ '\n ',
+ 'plugin_markdowku_codeblocks');
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern(
+ '\n(?:(?=\n*[ ]{0,3}\S)|\Z)',
+ 'plugin_markdowku_codeblocks');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ switch ($state) {
+ case DOKU_LEXER_ENTER:
+ $ReWriter = new Preformatted($handler->getCallWriter());
+ $handler->setCallWriter($ReWriter);
+ $handler->_addCall('preformatted_start', array($match), $pos);
+ break;
+ case DOKU_LEXER_MATCHED:
+ $handler->_addCall('preformatted_newline', array($match), $pos);
+ break;
+ case DOKU_LEXER_UNMATCHED:
+ $handler->_addCall('preformatted_content', array($match), $pos);
+ break;
+ case DOKU_LEXER_EXIT:
+ $handler->_addCall('preformatted_end', array(), $pos);
+ $handler->_addCall('preformatted_content', array($match), $pos);
+ $handler->getCallWriter()->process();
+ $ReWriter = & $handler->getCallWriter();
+ $handler->setCallWriter($ReWriter->getCallWriter());
+ break;
+ }
+ return true;
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ return false;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/codespans1.php b/platform/www/lib/plugins/markdowku/syntax/codespans1.php
new file mode 100644
index 0000000..6465288
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/codespans1.php
@@ -0,0 +1,35 @@
+<?php
+/*
+ * Codespans enclosed with one backtick: `...`
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_codespans1 extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'formatting'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 99; }
+ function getAllowedTypes() { return array(); }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '(?<!`)`(?!`).+?(?<!`)`(?!`)',
+ $mode,
+ 'plugin_markdowku_codespans1');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($match);
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ $renderer->monospace_open();
+ $renderer->cdata(substr($data[0], 1, -1));
+ $renderer->monospace_close();
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/codespans2.php b/platform/www/lib/plugins/markdowku/syntax/codespans2.php
new file mode 100644
index 0000000..596fc28
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/codespans2.php
@@ -0,0 +1,34 @@
+<?php
+/*
+ * Codespans enclosed with two backticks: ``...``
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_codespans2 extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'formatting'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 98; }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '(?<!`)``(?!`).+?(?<!`)``(?!`)',
+ $mode,
+ 'plugin_markdowku_codespans2');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($match);
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ $renderer->monospace_open();
+ $renderer->cdata(substr($data[0], 2, -2));
+ $renderer->monospace_close();
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/codespans3.php b/platform/www/lib/plugins/markdowku/syntax/codespans3.php
new file mode 100644
index 0000000..b6b342b
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/codespans3.php
@@ -0,0 +1,34 @@
+<?php
+/*
+ * Codespans enclosed within three backticks: ```...```
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_codespans3 extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'formatting'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 97; }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '(?<!`)```(?!`).+?(?<!`)```(?!`)',
+ $mode,
+ 'plugin_markdowku_codespans3');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($match);
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ $renderer->monospace_open();
+ $renderer->cdata(substr($data[0], 3, -3));
+ $renderer->monospace_close();
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/codespans4.php b/platform/www/lib/plugins/markdowku/syntax/codespans4.php
new file mode 100644
index 0000000..4166d7f
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/codespans4.php
@@ -0,0 +1,34 @@
+<?php
+/*
+ * Codespans enclosed within four backticks: ````...````
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_codespans4 extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'formatting'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 96; }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '(?<!`)````(?!`).+?(?<!`)````(?!`)',
+ $mode,
+ 'plugin_markdowku_codespans4');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($match);
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ $renderer->monospace_open();
+ $renderer->cdata(substr($data[0], 4, -4));
+ $renderer->monospace_close();
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/codespans5.php b/platform/www/lib/plugins/markdowku/syntax/codespans5.php
new file mode 100644
index 0000000..00f33d2
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/codespans5.php
@@ -0,0 +1,34 @@
+<?php
+/*
+ * Codespans enclosed within five brackets: `````...`````
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_codespans5 extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'formatting'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 95; }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '(?<!`)`````(?!`).+?(?<!`)`````(?!`)',
+ $mode,
+ 'plugin_markdowku_codespans5');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($match);
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ $renderer->monospace_open();
+ $renderer->cdata(substr($data[0], 5, -5));
+ $renderer->monospace_close();
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/escapespecialchars.php b/platform/www/lib/plugins/markdowku/syntax/escapespecialchars.php
new file mode 100644
index 0000000..b27b7c0
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/escapespecialchars.php
@@ -0,0 +1,94 @@
+<?php
+/*
+ * Unescape escaped backslash. \\\\ -> \
+ * This is in a separate class as it needs a higher priority than the other
+ * escapes.
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_escapespecialchars extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'substition'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 61; }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\`',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\\*',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\_',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\\{',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\\}',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\\[',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\\]',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\\(',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\\)',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\>',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\\#',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\\+',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\\-',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\\-',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\\.',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ $this->Lexer->addSpecialPattern(
+ '(?<!\\\\)\\\\!',
+ $mode,
+ 'plugin_markdowku_escapespecialchars');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($state, $match);
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ $renderer->doc .= substr($data[1], -1);
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/githubcodeblocks.php b/platform/www/lib/plugins/markdowku/syntax/githubcodeblocks.php
new file mode 100644
index 0000000..c715c55
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/githubcodeblocks.php
@@ -0,0 +1,48 @@
+<?php
+/*
+ * Github style codeblocks, starting and ending with three backticks, optionally
+ * providing a language to be used for syntax highlighting.
+ *
+ * ```php
+ * ...
+ * ```
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_githubcodeblocks extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'protected'; }
+ function getPType() { return 'block'; }
+ function getSort() { return 91; }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '\n```[a-z0-9_]*\n.+?\n```(?=\n)',
+ $mode,
+ 'plugin_markdowku_githubcodeblocks');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ if (preg_match('/^\n```([a-z0-9_]+)\n/', $match, $matches) > 0) {
+ $lang = $matches[1];
+ } else {
+ $lang = NULL;
+ }
+
+ $text = preg_replace('/^```[a-z0-9_]+\n/m', '', $match);
+ $text = preg_replace('/^```$/m', '', $text);
+ if ($lang)
+ $handler->_addCall('file', array($text, $lang, 'snippet.'.$lang), $pos);
+ else
+ $handler->_addCall('code', array($text, $lang), $pos);
+ return true;
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/headeratx.php b/platform/www/lib/plugins/markdowku/syntax/headeratx.php
new file mode 100644
index 0000000..b55c38c
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/headeratx.php
@@ -0,0 +1,57 @@
+<?php
+/*
+ * Header in ATX style, i.e. '# Header1', '## Header2', ...
+ */
+
+if (!defined('DOKU_INC')) die();
+if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
+require_once (DOKU_PLUGIN . 'syntax.php');
+
+class syntax_plugin_markdowku_headeratx extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'baseonly'; }
+ function getPType() { return 'block'; }
+ function getSort() { return 49; }
+ function getAllowedTypes() {
+ return array('formatting', 'substition', 'disabled', 'protected');
+ }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '\n\#{1,6}[ \t]*.+?[ \t]*\#*(?=\n+)',
+ 'base',
+ 'plugin_markdowku_headeratx');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ global $conf;
+
+ $title = trim($match);
+ $level = strspn($title, '#');
+ $title = trim($title, '#');
+ $title = trim($title);
+
+ if ($level < 1)
+ $level = 1;
+ elseif ($level > 6)
+ $level = 6;
+
+ if ($handler->getStatus('section'))
+ $handler->_addCall('section_close', array(), $pos);
+ if ($level <= $conf['maxseclevel']) {
+ $handler->setStatus('section_edit_start', $pos);
+ $handler->setStatus('section_edit_level', $level);
+ $handler->setStatus('section_edit_title', $title);
+ }
+ $handler->_addCall('header', array($title, $level, $pos), $pos);
+ $handler->_addCall('section_open', array($level), $pos);
+ $handler->setStatus('section', true);
+
+ return true;
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/headersetext.php b/platform/www/lib/plugins/markdowku/syntax/headersetext.php
new file mode 100644
index 0000000..43e9551
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/headersetext.php
@@ -0,0 +1,56 @@
+<?php
+/*
+ * Setext style headers:
+ * Header
+ * ======
+ */
+
+if (!defined('DOKU_INC')) die();
+if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
+require_once (DOKU_PLUGIN . 'syntax.php');
+
+class syntax_plugin_markdowku_headersetext extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'baseonly'; }
+ function getPType() { return 'block'; }
+ function getSort() { return 49; }
+ function getAllowedTypes() {
+ return array('formatting', 'substition', 'disabled', 'protected');
+ }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '\n[^\n]+[ \t]*\n=+[ \t]*(?=\n)',
+ 'base',
+ 'plugin_markdowku_headersetext');
+
+ $this->Lexer->addSpecialPattern(
+ '\n[^\n]+[ \t]*\n-+[ \t]*(?=\n)',
+ 'base',
+ 'plugin_markdowku_headersetext');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ $title = preg_replace('/^\n(.+?)[ \t]*\n.*/', '\1', $match);
+ $title = trim($title);
+ if (preg_match('/^\n(.+?)[ \t]*\n=/', $match))
+ $level = 1;
+ if (preg_match('/^\n(.+?)[ \t]*\n-/', $match))
+ $level = 2;
+
+ if ($handler->getStatus('section'))
+ $handler->_addCall('section_close', array(), $pos);
+ $handler->setStatus('section_edit_start', $pos);
+ $handler->setStatus('section_edit_level', $level);
+ $handler->setStatus('section_edit_title', $title);
+ $handler->_addCall('header', array($title, $level, $pos), $pos);
+ $handler->_addCall('section_open', array($level), $pos);
+ $handler->setStatus('section', true);
+ return true;
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/hr.php b/platform/www/lib/plugins/markdowku/syntax/hr.php
new file mode 100644
index 0000000..f9da2c0
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/hr.php
@@ -0,0 +1,47 @@
+<?php
+/*
+ * Horizontal rulers:
+ * * * *
+ * ---
+ * ___
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_hr extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'container'; }
+ function getPType() { return 'block'; }
+ function getSort() { return 8; } /* Before list block parsing. */
+
+ function connectTo($mode) {
+ /* We use two newlines, as we don't want to conflict with setext header
+ * parsing, but also have to be before list blocks. */
+ $this->Lexer->addSpecialPattern(
+ '\n[ ]{0,2}(?:[ ]?\*[ ]?){3,}[ \t]*(?=\n)',
+ $mode,
+ 'plugin_markdowku_hr');
+
+ $this->Lexer->addSpecialPattern(
+ '\n[ ]{0,2}(?:[ ]?-[ ]?){3,}[ \t]*(?=\n)',
+ $mode,
+ 'plugin_markdowku_hr');
+
+ $this->Lexer->addSpecialPattern(
+ '\n[ ]{0,2}(?:[ ]?_[ ]?){3,}[ \t]*(?=\n)',
+ $mode,
+ 'plugin_markdowku_hr');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ $handler->_addCall('hr', array(), $pos);
+ return true;
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/imagesinline.php b/platform/www/lib/plugins/markdowku/syntax/imagesinline.php
new file mode 100644
index 0000000..28b7d75
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/imagesinline.php
@@ -0,0 +1,43 @@
+<?php
+/*
+ * Inline images: ![source](description "title")
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_imagesinline extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'substition'; }
+ function getPType() { return 'block'; }
+ function getSort() { return 101; }
+
+ function connectTo($mode) {
+ $this->nested_brackets_re =
+ str_repeat('(?>[^\[\]]+|\[', 6).
+ str_repeat('\])*', 6);
+ $this->Lexer->addSpecialPattern(
+ '\!\['.$this->nested_brackets_re.'\]\([ \t]*<?.+?>?[ \t]*(?:[\'"].*?[\'"])?\)',
+ $mode,
+ 'plugin_markdowku_imagesinline');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ if ($state == DOKU_LEXER_SPECIAL) {
+ $text = preg_match(
+ '/^\!\[('.$this->nested_brackets_re.')\]\([ \t]*<?(.+?)>?[ \t]*(?:[\'"](.*?)[\'"])?[ \t]*?\)$/',
+ $match,
+ $matches);
+ $target = $matches[2] == '' ? $matches[3] : $matches[2];
+ $title = $matches[1];
+ $handler->media($target.'|'.$title, $state, $pos);
+ }
+ return true;
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/imagesreference.php b/platform/www/lib/plugins/markdowku/syntax/imagesreference.php
new file mode 100644
index 0000000..9382d2a
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/imagesreference.php
@@ -0,0 +1,58 @@
+<?php
+/*
+ * Reference links, i.e.
+ * ... [name][id] ...
+ * ... [id][] ...
+ * ...
+ * [id]: http://example.com (handled by markdowku_references)
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_imagesreference extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'substition'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 102; }
+
+ function connectTo($mode) {
+ $this->nested_brackets_re =
+ str_repeat('(?>[^\[\]]+|\[', 3).
+ str_repeat('\])*', 3);
+ $this->Lexer->addSpecialPattern(
+ '\!\['.$this->nested_brackets_re.'\][ ]?(?:\n[ ]*)?\[[^\n]*?\]',
+ $mode,
+ 'plugin_markdowku_imagesreference');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($state, $match);
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ global $ID;
+ preg_match(
+ '/^\!\[('.$this->nested_brackets_re.')\][ ]?(?:\n[ ]*)?\[(.*?)\]$/',
+ $data[1],
+ $matches);
+
+ $title = $matches[1];
+
+ if ($matches[2] == '')
+ $rid = $matches[1];
+ else
+ $rid = $matches[2];
+
+ $rid = preg_replace("/ /", ".", $rid);
+ $target = p_get_metadata($ID, 'markdowku_references_'.$rid, METADATA_RENDER_USING_CACHE);
+ if ($target == '')
+ $renderer->cdata($data[1]);
+ else
+ $renderer->_media($target, $title);
+
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/italicasterisk.php b/platform/www/lib/plugins/markdowku/syntax/italicasterisk.php
new file mode 100644
index 0000000..bebc45f
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/italicasterisk.php
@@ -0,0 +1,49 @@
+<?php
+/*
+ * Italic text enclosed by asterisks, i.e. *...*
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+# Fix for Hogfather RC2 - see https://github.com/dwp-forge/columns/issues/5#issuecomment-638467603
+require_once(DOKU_INC.'inc/Parsing/Lexer/Lexer.php');
+
+class syntax_plugin_markdowku_italicasterisk extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'formatting'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 79; }
+ function getAllowedTypes() {
+ return Array('formatting', 'substition');
+ }
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern(
+ '(?<![\\\\])\*(?![ *])(?=(?:(?!\n\n).)+?(?<![\\\\ *])\*)',
+ $mode,
+ 'plugin_markdowku_italicasterisk');
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern(
+ '(?<![\\\\* ])\*',
+ 'plugin_markdowku_italicasterisk');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($state, $match);
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ if ($data[0] == DOKU_LEXER_ENTER)
+ $renderer->emphasis_open();
+ elseif ($data[0] == DOKU_LEXER_EXIT)
+ $renderer->emphasis_close();
+ else
+ $renderer->cdata($data[1]);
+
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/markdowku/syntax/italicunderline.php b/platform/www/lib/plugins/markdowku/syntax/italicunderline.php
new file mode 100644
index 0000000..13a5a02
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/italicunderline.php
@@ -0,0 +1,56 @@
+<?php
+/*
+ * Italic text enclosed in underlines: _..._
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+# Fix for Hogfather RC2 - see https://github.com/dwp-forge/columns/issues/5#issuecomment-638467603
+require_once(DOKU_INC.'inc/Parsing/Lexer/Lexer.php');
+
+class syntax_plugin_markdowku_italicunderline extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'formatting'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 79; }
+ function getAllowedTypes() {
+ return Array('formatting', 'substition');
+ }
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern(
+ '(?<![\\\\])_(?![ _])(?=(?:(?!\n\n).)+?[^\\\\ _]_)',
+ $mode,
+ 'plugin_markdowku_italicunderline');
+// $this->Lexer->addSpecialPattern(
+// '\w+_\w+_\w[\w_]*',
+// $mode,
+// 'plugin_markdowku_italicunderline');
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern(
+ '(?<![\\\\_ ])_',
+ 'plugin_markdowku_italicunderline');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($state, $match);
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+
+ if ($data[0] == DOKU_LEXER_ENTER)
+ $renderer->emphasis_open();
+ elseif ($data[0] == DOKU_LEXER_EXIT)
+ $renderer->emphasis_close();
+ elseif ($data[0] == DOKU_LEXER_UNMATCHED)
+ $renderer->cdata($data[1]);
+ elseif ($data[0] == DOKU_LEXER_SPECIAL)
+ $renderer->cdata($data[1]);
+
+ return true;
+ }
+}
diff --git a/platform/www/lib/plugins/markdowku/syntax/linebreak.php b/platform/www/lib/plugins/markdowku/syntax/linebreak.php
new file mode 100644
index 0000000..ef2f1df
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/linebreak.php
@@ -0,0 +1,32 @@
+<?php
+/*
+ * Linebreaks, determined by two spaces at the line end.
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_linebreak extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'substition'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 139; }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '[ ]{2,}\n',
+ $mode,
+ 'plugin_markdowku_linebreak');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($match, $state, $pos);
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ $renderer->linebreak();
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/olists.php b/platform/www/lib/plugins/markdowku/syntax/olists.php
new file mode 100644
index 0000000..853232a
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/olists.php
@@ -0,0 +1,98 @@
+<?php
+/*
+ * Ordered lists:
+ * 1. ...
+ * 2. ...
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+use dokuwiki\Parsing\Handler\Lists;
+
+class syntax_plugin_markdowku_olists extends DokuWiki_Syntax_Plugin {
+ function getType() { return 'container'; }
+ function getPType() { return 'block'; }
+ function getSort() { return 9; }
+ function getAllowedTypes() {
+ return array('formatting', 'substition', 'paragraphs', 'baseonly');
+ }
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern(
+ '\n\n[ ]{0,3}\d+\.[ \t]',
+ $mode,
+ 'plugin_markdowku_olists');
+
+ $this->Lexer->addPattern(
+ '\n^[ \t]*\d+\.[ \t]',
+ 'plugin_markdowku_olists');
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern(
+ '(?:\Z|\n{1,}(?=\n\S)(?!\n[ \t]*\d+\.[ \t]))',
+ 'plugin_markdowku_olists');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ switch ($state) {
+ case DOKU_LEXER_ENTER:
+ $ReWriter = new Doku_Handler_Markdown_Ordered_List($handler->getCallWriter());
+ $handler->setCallWriter($ReWriter);
+ $handler->_addCall('list_open', array($match), $pos);
+ break;
+ case DOKU_LEXER_MATCHED:
+ $handler->_addCall('list_item', array($match), $pos);
+ break;
+ case DOKU_LEXER_UNMATCHED:
+ $handler->_addCall('cdata', array($match), $pos);
+ break;
+ case DOKU_LEXER_EXIT:
+ $handler->_addCall('list_close', array(), $pos);
+ $handler->getCallWriter()->process();
+ $ReWriter = & $handler->getCallWriter();
+ $handler->setCallWriter($ReWriter->getCallWriter());
+ break;
+ }
+ return true;
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ return true;
+ }
+}
+
+class Doku_Handler_Markdown_Ordered_List extends Lists {
+ private $depth = array(0, 4);
+
+ function interpretSyntax($match, &$type) {
+ $type="o";
+ $listlevel = 1;
+ $real_position = 0;
+ $logical_position = 0;
+ $text = preg_replace('/^\n*/', '', $match);
+
+ while (TRUE) {
+ if (preg_match('/^[ ]{'.$this->depth[$listlevel].'}/', substr($text, $real_position)) > 0) {
+ $real_position += $this->depth[$listlevel];
+ $logical_position += $this->depth[$listlevel];
+ $listlevel += 1;
+ continue;
+ }
+ if (preg_match('/^\t/', substr($text, $real_position)) > 0) {
+ $real_position += 1;
+ $logical_position += 4;
+ $listlevel += 1;
+ continue;
+ }
+ if (preg_match('/^[ ]{0,3}\d+\.[ \t]/', substr($text, $real_position)) > 0) {
+ $this->depth[$listlevel] = strlen(substr($text, $real_position)) - 1;
+ }
+ break;
+ }
+ return $listlevel;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/references.php b/platform/www/lib/plugins/markdowku/syntax/references.php
new file mode 100644
index 0000000..fe50a09
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/references.php
@@ -0,0 +1,41 @@
+<?php
+/*
+ * References for links or images, i.e.
+ * [id]: http://example.com
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_markdowku_references extends DokuWiki_Syntax_Plugin {
+
+ function getType() { return 'substition'; }
+ function getPType() { return 'normal'; }
+ function getSort() { return 100; }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '\n[ ]{0,3}\[[^\n]+?\]:[ \t]*\n?[ \t]*<?\S+?>?[ \t]*\n?[ \t]*(?:(?<=\s)["(].+?[")][\t]*)?(?=\n)',
+ $mode,
+ 'plugin_markdowku_references');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array($state, $match);
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ if ($mode != 'metadata')
+ return false;
+
+ preg_match(
+ '/\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+)>?[ \t]*\n?[ \t]*(?:(?<=\s)["(](.+?)[")][\t]*)?/',
+ $data[1],
+ $matches);
+ $key = 'markdowku_references_'.preg_replace("/ /", ".", $matches[1]);
+ $renderer->meta[$key] = $matches[2];
+ return true;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/markdowku/syntax/ulists.php b/platform/www/lib/plugins/markdowku/syntax/ulists.php
new file mode 100644
index 0000000..79393c2
--- /dev/null
+++ b/platform/www/lib/plugins/markdowku/syntax/ulists.php
@@ -0,0 +1,99 @@
+<?php
+/*
+ * Unorderd lists:
+ * * ...
+ * * ...
+ */
+
+if(!defined('DOKU_INC')) die();
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+use dokuwiki\Parsing\Handler\Lists;
+
+class syntax_plugin_markdowku_ulists extends DokuWiki_Syntax_Plugin {
+ function getType() { return 'container'; }
+ function getPType() { return 'block'; }
+ function getSort() { return 9; }
+ function getAllowedTypes() {
+ return array('formatting', 'substition', 'paragraphs', 'baseonly', 'container');
+ }
+
+ function connectTo($mode) {
+ /* The negative lookahead is for not conflicting with hrs. */
+ $this->Lexer->addEntryPattern(
+ '\n\n[ ]{0,3}[*+-][ \t](?!(?:[ ]?[*+-][ ]?){2,}[ \t]*\n)',
+ $mode,
+ 'plugin_markdowku_ulists');
+
+ $this->Lexer->addPattern(
+ '\n[ \t]*[*+-][ \t](?!(?:[ ]?[*+-][ ]?){2,}[ \t]*\n)',
+ 'plugin_markdowku_ulists');
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern(
+ '(?:\Z|\n{1,}(?=\n\S)(?!\n[ \t]*[*+-][ \t]))',
+ 'plugin_markdowku_ulists');
+ }
+
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ switch ($state) {
+ case DOKU_LEXER_ENTER:
+ $ReWriter = new Doku_Handler_Markdown_Unordered_List($handler->getCallWriter());
+ $handler->setCallWriter($ReWriter);
+ $handler->_addCall('list_open', array($match), $pos);
+ break;
+ case DOKU_LEXER_MATCHED:
+ $handler->_addCall('list_item', array($match), $pos);
+ break;
+ case DOKU_LEXER_UNMATCHED:
+ $handler->_addCall('cdata', array($match), $pos);
+ break;
+ case DOKU_LEXER_EXIT:
+ $handler->_addCall('list_close', array(), $pos);
+ $handler->getCallWriter()->process();
+ $ReWriter = & $handler->getCallWriter();
+ $handler->setCallWriter($ReWriter->getCallWriter());
+ break;
+ }
+ return true;
+ }
+
+ function render($mode, Doku_Renderer $renderer, $data) {
+ return true;
+ }
+}
+
+class Doku_Handler_Markdown_Unordered_List extends Lists {
+ private $depth = array(0, 4);
+
+ function interpretSyntax($match, &$type) {
+ $type="u";
+ $listlevel = 1;
+ $real_position = 0;
+ $logical_position = 0;
+ $text = preg_replace('/^\n*/', '', $match);
+
+ while (TRUE) {
+ if (preg_match('/^[ ]{'.$this->depth[$listlevel].'}/', substr($text, $real_position)) > 0) {
+ $real_position += $this->depth[$listlevel];
+ $logical_position += $this->depth[$listlevel];
+ $listlevel += 1;
+ continue;
+ }
+ if (preg_match('/^\t/', substr($text, $real_position)) > 0) {
+ $real_position += 1;
+ $logical_position += 4;
+ $listlevel += 1;
+ continue;
+ }
+ if (preg_match('/^[ ]{0,3}[*+-][ \t]/', substr($text, $real_position)) > 0) {
+ $this->depth[$listlevel] = strlen(substr($text, $real_position)) - 1;
+ }
+ break;
+ }
+ return $listlevel + 1;
+ }
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/master.zip b/platform/www/lib/plugins/master.zip
new file mode 100644
index 0000000..0c9a008
--- /dev/null
+++ b/platform/www/lib/plugins/master.zip
Binary files differ
diff --git a/platform/www/lib/plugins/popularity/action.php b/platform/www/lib/plugins/popularity/action.php
new file mode 100644
index 0000000..fac6107
--- /dev/null
+++ b/platform/www/lib/plugins/popularity/action.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Popularity Feedback Plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ */
+
+class action_plugin_popularity extends DokuWiki_Action_Plugin
+{
+
+ /**
+ * @var helper_plugin_popularity
+ */
+ protected $helper;
+
+ public function __construct()
+ {
+ $this->helper = $this->loadHelper('popularity', false);
+ }
+
+ /** @inheritdoc */
+ public function register(Doku_Event_Handler $controller)
+ {
+ $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'autosubmit', array());
+ }
+
+ /**
+ * Event handler
+ *
+ * @param Doku_Event $event
+ * @param $param
+ */
+ public function autosubmit(Doku_Event &$event, $param)
+ {
+ //Do we have to send the data now
+ if (!$this->helper->isAutosubmitEnabled() || $this->isTooEarlyToSubmit()) {
+ return;
+ }
+
+ //Actually send it
+ $status = $this->helper->sendData($this->helper->gatherAsString());
+
+ if ($status !== '') {
+ //If an error occured, log it
+ io_saveFile($this->helper->autosubmitErrorFile, $status);
+ } else {
+ //If the data has been sent successfully, previous log of errors are useless
+ @unlink($this->helper->autosubmitErrorFile);
+ //Update the last time we sent data
+ touch($this->helper->autosubmitFile);
+ }
+
+ $event->stopPropagation();
+ $event->preventDefault();
+ }
+
+ /**
+ * Check if it's time to send autosubmit data
+ * (we should have check if autosubmit is enabled first)
+ */
+ protected function isTooEarlyToSubmit()
+ {
+ $lastSubmit = $this->helper->lastSentTime();
+ return $lastSubmit + 24*60*60*30 > time();
+ }
+}
diff --git a/platform/www/lib/plugins/popularity/admin.php b/platform/www/lib/plugins/popularity/admin.php
new file mode 100644
index 0000000..61d8cc3
--- /dev/null
+++ b/platform/www/lib/plugins/popularity/admin.php
@@ -0,0 +1,157 @@
+<?php
+/**
+ * Popularity Feedback Plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+class admin_plugin_popularity extends DokuWiki_Admin_Plugin
+{
+
+ /** @var helper_plugin_popularity */
+ protected $helper;
+ protected $sentStatus = null;
+
+ /**
+ * admin_plugin_popularity constructor.
+ */
+ public function __construct()
+ {
+ $this->helper = $this->loadHelper('popularity', false);
+ }
+
+ /**
+ * return prompt for admin menu
+ * @param $language
+ * @return string
+ */
+ public function getMenuText($language)
+ {
+ return $this->getLang('name');
+ }
+
+ /**
+ * return sort order for position in admin menu
+ */
+ public function getMenuSort()
+ {
+ return 2000;
+ }
+
+ /**
+ * Accessible for managers
+ */
+ public function forAdminOnly()
+ {
+ return false;
+ }
+
+
+ /**
+ * handle user request
+ */
+ public function handle()
+ {
+ global $INPUT;
+
+ //Send the data
+ if ($INPUT->has('data')) {
+ $this->sentStatus = $this->helper->sendData($INPUT->str('data'));
+ if ($this->sentStatus === '') {
+ //Update the last time we sent the data
+ touch($this->helper->popularityLastSubmitFile);
+ }
+ //Deal with the autosubmit option
+ $this->enableAutosubmit($INPUT->has('autosubmit'));
+ }
+ }
+
+ /**
+ * Enable or disable autosubmit
+ * @param bool $enable If TRUE, it will enable autosubmit. Else, it will disable it.
+ */
+ protected function enableAutosubmit($enable)
+ {
+ if ($enable) {
+ io_saveFile($this->helper->autosubmitFile, ' ');
+ } else {
+ @unlink($this->helper->autosubmitFile);
+ }
+ }
+
+ /**
+ * Output HTML form
+ */
+ public function html()
+ {
+ global $INPUT;
+
+ if (! $INPUT->has('data')) {
+ echo $this->locale_xhtml('intro');
+
+ //If there was an error the last time we tried to autosubmit, warn the user
+ if ($this->helper->isAutoSubmitEnabled()) {
+ if (file_exists($this->helper->autosubmitErrorFile)) {
+ echo $this->getLang('autosubmitError');
+ echo io_readFile($this->helper->autosubmitErrorFile);
+ }
+ }
+
+ flush();
+ echo $this->buildForm('server');
+
+ //Print the last time the data was sent
+ $lastSent = $this->helper->lastSentTime();
+ if ($lastSent !== 0) {
+ echo $this->getLang('lastSent') . ' ' . datetime_h($lastSent);
+ }
+ } else {
+ //If we just submitted the form
+ if ($this->sentStatus === '') {
+ //If we successfully sent the data
+ echo $this->locale_xhtml('submitted');
+ } else {
+ //If we failed to submit the data, try directly with the browser
+ echo $this->getLang('submissionFailed') . $this->sentStatus . '<br />';
+ echo $this->getLang('submitDirectly');
+ echo $this->buildForm('browser', $INPUT->str('data'));
+ }
+ }
+ }
+
+
+ /**
+ * Build the form which presents the data to be sent
+ * @param string $submissionMode How is the data supposed to be sent? (may be: 'browser' or 'server')
+ * @param string $data The popularity data, if it has already been computed. NULL otherwise.
+ * @return string The form, as an html string
+ */
+ protected function buildForm($submissionMode, $data = null)
+ {
+ $url = ($submissionMode === 'browser' ? $this->helper->submitUrl : script());
+ if (is_null($data)) {
+ $data = $this->helper->gatherAsString();
+ }
+
+ $form = '<form method="post" action="'. $url .'" accept-charset="utf-8">'
+ .'<fieldset style="width: 60%;">'
+ .'<textarea class="edit" rows="10" cols="80" readonly="readonly" name="data">'
+ .$data
+ .'</textarea><br />';
+
+ //If we submit via the server, we give the opportunity to suscribe to the autosubmission option
+ if ($submissionMode !== 'browser') {
+ $form .= '<label for="autosubmit">'
+ .'<input type="checkbox" name="autosubmit" id="autosubmit" '
+ .($this->helper->isAutosubmitEnabled() ? 'checked' : '' )
+ .'/> ' . $this->getLang('autosubmit') .'<br />'
+ .'</label>'
+ .'<input type="hidden" name="do" value="admin" />'
+ .'<input type="hidden" name="page" value="popularity" />';
+ }
+ $form .= '<button type="submit">'.$this->getLang('submit').'</button>'
+ .'</fieldset>'
+ .'</form>';
+ return $form;
+ }
+}
diff --git a/platform/www/lib/plugins/popularity/admin.svg b/platform/www/lib/plugins/popularity/admin.svg
new file mode 100644
index 0000000..820fc8c
--- /dev/null
+++ b/platform/www/lib/plugins/popularity/admin.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M2 21l21-9L2 3v7l15 2-15 2v7z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/plugins/popularity/helper.php b/platform/www/lib/plugins/popularity/helper.php
new file mode 100644
index 0000000..4537976
--- /dev/null
+++ b/platform/www/lib/plugins/popularity/helper.php
@@ -0,0 +1,292 @@
+<?php
+
+use dokuwiki\HTTP\DokuHTTPClient;
+use dokuwiki\Extension\Event;
+
+/**
+ * Popularity Feedback Plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ */
+class helper_plugin_popularity extends Dokuwiki_Plugin
+{
+ /**
+ * The url where the data should be sent
+ */
+ public $submitUrl = 'http://update.dokuwiki.org/popularity.php';
+
+ /**
+ * Name of the file which determine if the the autosubmit is enabled,
+ * and when it was submited for the last time
+ */
+ public $autosubmitFile;
+
+ /**
+ * File where the last error which happened when we tried to autosubmit, will be log
+ */
+ public $autosubmitErrorFile;
+
+ /**
+ * Name of the file which determine when the popularity data was manually
+ * submitted for the last time
+ * (If this file doesn't exist, the data has never been sent)
+ */
+ public $popularityLastSubmitFile;
+
+ /**
+ * helper_plugin_popularity constructor.
+ */
+ public function __construct()
+ {
+ global $conf;
+ $this->autosubmitFile = $conf['cachedir'].'/autosubmit.txt';
+ $this->autosubmitErrorFile = $conf['cachedir'].'/autosubmitError.txt';
+ $this->popularityLastSubmitFile = $conf['cachedir'].'/lastSubmitTime.txt';
+ }
+
+ /**
+ * Check if autosubmit is enabled
+ *
+ * @return boolean TRUE if we should send data once a month, FALSE otherwise
+ */
+ public function isAutoSubmitEnabled()
+ {
+ return file_exists($this->autosubmitFile);
+ }
+
+ /**
+ * Send the data, to the submit url
+ *
+ * @param string $data The popularity data
+ * @return string An empty string if everything worked fine, a string describing the error otherwise
+ */
+ public function sendData($data)
+ {
+ $error = '';
+ $httpClient = new DokuHTTPClient();
+ $status = $httpClient->sendRequest($this->submitUrl, array('data' => $data), 'POST');
+ if (! $status) {
+ $error = $httpClient->error;
+ }
+ return $error;
+ }
+
+ /**
+ * Compute the last time the data was sent. If it has never been sent, we return 0.
+ *
+ * @return int
+ */
+ public function lastSentTime()
+ {
+ $manualSubmission = @filemtime($this->popularityLastSubmitFile);
+ $autoSubmission = @filemtime($this->autosubmitFile);
+
+ return max((int) $manualSubmission, (int) $autoSubmission);
+ }
+
+ /**
+ * Gather all information
+ *
+ * @return string The popularity data as a string
+ */
+ public function gatherAsString()
+ {
+ $data = $this->gather();
+ $string = '';
+ foreach ($data as $key => $val) {
+ if (is_array($val)) foreach ($val as $v) {
+ $string .= hsc($key)."\t".hsc($v)."\n";
+ } else {
+ $string .= hsc($key)."\t".hsc($val)."\n";
+ }
+ }
+ return $string;
+ }
+
+ /**
+ * Gather all information
+ *
+ * @return array The popularity data as an array
+ */
+ protected function gather()
+ {
+ global $conf;
+ /** @var $auth DokuWiki_Auth_Plugin */
+ global $auth;
+ $data = array();
+ $phptime = ini_get('max_execution_time');
+ @set_time_limit(0);
+ $pluginInfo = $this->getInfo();
+
+ // version
+ $data['anon_id'] = md5(auth_cookiesalt());
+ $data['version'] = getVersion();
+ $data['popversion'] = $pluginInfo['date'];
+ $data['language'] = $conf['lang'];
+ $data['now'] = time();
+ $data['popauto'] = (int) $this->isAutoSubmitEnabled();
+
+ // some config values
+ $data['conf_useacl'] = $conf['useacl'];
+ $data['conf_authtype'] = $conf['authtype'];
+ $data['conf_template'] = $conf['template'];
+
+ // number and size of pages
+ $list = array();
+ search($list, $conf['datadir'], array($this, 'searchCountCallback'), array('all'=>false), '');
+ $data['page_count'] = $list['file_count'];
+ $data['page_size'] = $list['file_size'];
+ $data['page_biggest'] = $list['file_max'];
+ $data['page_smallest'] = $list['file_min'];
+ $data['page_nscount'] = $list['dir_count'];
+ $data['page_nsnest'] = $list['dir_nest'];
+ if ($list['file_count']) $data['page_avg'] = $list['file_size'] / $list['file_count'];
+ $data['page_oldest'] = $list['file_oldest'];
+ unset($list);
+
+ // number and size of media
+ $list = array();
+ search($list, $conf['mediadir'], array($this, 'searchCountCallback'), array('all'=>true));
+ $data['media_count'] = $list['file_count'];
+ $data['media_size'] = $list['file_size'];
+ $data['media_biggest'] = $list['file_max'];
+ $data['media_smallest'] = $list['file_min'];
+ $data['media_nscount'] = $list['dir_count'];
+ $data['media_nsnest'] = $list['dir_nest'];
+ if ($list['file_count']) $data['media_avg'] = $list['file_size'] / $list['file_count'];
+ unset($list);
+
+ // number and size of cache
+ $list = array();
+ search($list, $conf['cachedir'], array($this, 'searchCountCallback'), array('all'=>true));
+ $data['cache_count'] = $list['file_count'];
+ $data['cache_size'] = $list['file_size'];
+ $data['cache_biggest'] = $list['file_max'];
+ $data['cache_smallest'] = $list['file_min'];
+ if ($list['file_count']) $data['cache_avg'] = $list['file_size'] / $list['file_count'];
+ unset($list);
+
+ // number and size of index
+ $list = array();
+ search($list, $conf['indexdir'], array($this, 'searchCountCallback'), array('all'=>true));
+ $data['index_count'] = $list['file_count'];
+ $data['index_size'] = $list['file_size'];
+ $data['index_biggest'] = $list['file_max'];
+ $data['index_smallest'] = $list['file_min'];
+ if ($list['file_count']) $data['index_avg'] = $list['file_size'] / $list['file_count'];
+ unset($list);
+
+ // number and size of meta
+ $list = array();
+ search($list, $conf['metadir'], array($this, 'searchCountCallback'), array('all'=>true));
+ $data['meta_count'] = $list['file_count'];
+ $data['meta_size'] = $list['file_size'];
+ $data['meta_biggest'] = $list['file_max'];
+ $data['meta_smallest'] = $list['file_min'];
+ if ($list['file_count']) $data['meta_avg'] = $list['file_size'] / $list['file_count'];
+ unset($list);
+
+ // number and size of attic
+ $list = array();
+ search($list, $conf['olddir'], array($this, 'searchCountCallback'), array('all'=>true));
+ $data['attic_count'] = $list['file_count'];
+ $data['attic_size'] = $list['file_size'];
+ $data['attic_biggest'] = $list['file_max'];
+ $data['attic_smallest'] = $list['file_min'];
+ if ($list['file_count']) $data['attic_avg'] = $list['file_size'] / $list['file_count'];
+ $data['attic_oldest'] = $list['file_oldest'];
+ unset($list);
+
+ // user count
+ if ($auth && $auth->canDo('getUserCount')) {
+ $data['user_count'] = $auth->getUserCount();
+ }
+
+ // calculate edits per day
+ $list = @file($conf['metadir'].'/_dokuwiki.changes');
+ $count = count($list);
+ if ($count > 2) {
+ $first = (int) substr(array_shift($list), 0, 10);
+ $last = (int) substr(array_pop($list), 0, 10);
+ $dur = ($last - $first)/(60*60*24); // number of days in the changelog
+ $data['edits_per_day'] = $count/$dur;
+ }
+ unset($list);
+
+ // plugins
+ $data['plugin'] = plugin_list();
+
+ // pcre info
+ if (defined('PCRE_VERSION')) $data['pcre_version'] = PCRE_VERSION;
+ $data['pcre_backtrack'] = ini_get('pcre.backtrack_limit');
+ $data['pcre_recursion'] = ini_get('pcre.recursion_limit');
+
+ // php info
+ $data['os'] = PHP_OS;
+ $data['webserver'] = $_SERVER['SERVER_SOFTWARE'];
+ $data['php_version'] = phpversion();
+ $data['php_sapi'] = php_sapi_name();
+ $data['php_memory'] = php_to_byte(ini_get('memory_limit'));
+ $data['php_exectime'] = $phptime;
+ $data['php_extension'] = get_loaded_extensions();
+
+ // plugin usage data
+ $this->addPluginUsageData($data);
+
+ return $data;
+ }
+
+ /**
+ * Triggers event to let plugins add their own data
+ *
+ * @param $data
+ */
+ protected function addPluginUsageData(&$data)
+ {
+ $pluginsData = array();
+ Event::createAndTrigger('PLUGIN_POPULARITY_DATA_SETUP', $pluginsData);
+ foreach ($pluginsData as $plugin => $d) {
+ if (is_array($d)) {
+ foreach ($d as $key => $value) {
+ $data['plugin_' . $plugin . '_' . $key] = $value;
+ }
+ } else {
+ $data['plugin_' . $plugin] = $d;
+ }
+ }
+ }
+
+ /**
+ * Callback to search and count the content of directories in DokuWiki
+ *
+ * @param array &$data Reference to the result data structure
+ * @param string $base Base usually $conf['datadir']
+ * @param string $file current file or directory relative to $base
+ * @param string $type Type either 'd' for directory or 'f' for file
+ * @param int $lvl Current recursion depht
+ * @param array $opts option array as given to search()
+ * @return bool
+ */
+ public function searchCountCallback(&$data, $base, $file, $type, $lvl, $opts)
+ {
+ // traverse
+ if ($type == 'd') {
+ if ($data['dir_nest'] < $lvl) $data['dir_nest'] = $lvl;
+ $data['dir_count']++;
+ return true;
+ }
+
+ //only search txt files if 'all' option not set
+ if ($opts['all'] || substr($file, -4) == '.txt') {
+ $size = filesize($base.'/'.$file);
+ $date = filemtime($base.'/'.$file);
+ $data['file_count']++;
+ $data['file_size'] += $size;
+ if (!isset($data['file_min']) || $data['file_min'] > $size) $data['file_min'] = $size;
+ if ($data['file_max'] < $size) $data['file_max'] = $size;
+ if (!isset($data['file_oldest']) || $data['file_oldest'] > $date) $data['file_oldest'] = $date;
+ }
+
+ return false;
+ }
+}
diff --git a/platform/www/lib/plugins/popularity/lang/en/intro.txt b/platform/www/lib/plugins/popularity/lang/en/intro.txt
new file mode 100644
index 0000000..e1d6d94
--- /dev/null
+++ b/platform/www/lib/plugins/popularity/lang/en/intro.txt
@@ -0,0 +1,11 @@
+====== Popularity Feedback ======
+
+This [[doku>popularity|tool]] gathers anonymous data about your wiki and allows you to send it back to the DokuWiki developers. This helps them to understand them how DokuWiki is used by its users and makes sure future development decisions are backed up by real world usage statistics.
+
+You are encouraged to repeat this step from time to time to keep developers informed when your wiki grows. Your repeated data sets will be identified by an anonymous ID.
+
+Data collected contains information like your DokuWiki version, the number and size of your pages and files, installed plugins and information about your PHP install.
+
+The raw data that will be send is shown below. Please use the "Send Data" button to transfer the information.
+
+
diff --git a/platform/www/lib/plugins/popularity/lang/en/lang.php b/platform/www/lib/plugins/popularity/lang/en/lang.php
new file mode 100644
index 0000000..af6797c
--- /dev/null
+++ b/platform/www/lib/plugins/popularity/lang/en/lang.php
@@ -0,0 +1,9 @@
+<?php
+
+$lang['name'] = 'Popularity Feedback (may take some time to load)';
+$lang['submit'] = 'Send Data';
+$lang['autosubmit'] = 'Automatically send data once a month';
+$lang['submissionFailed'] = 'The data couldn\'t be sent due to the following error:';
+$lang['submitDirectly'] = 'You can send the data manually by submitting the following form.';
+$lang['autosubmitError'] = 'The last autosubmit failed, because of the following error: ';
+$lang['lastSent'] = 'The data has been sent';
diff --git a/platform/www/lib/plugins/popularity/lang/en/submitted.txt b/platform/www/lib/plugins/popularity/lang/en/submitted.txt
new file mode 100644
index 0000000..30f2784
--- /dev/null
+++ b/platform/www/lib/plugins/popularity/lang/en/submitted.txt
@@ -0,0 +1,3 @@
+====== Popularity Feedback ======
+
+The data has been sent succesfully.
diff --git a/platform/www/lib/plugins/popularity/plugin.info.txt b/platform/www/lib/plugins/popularity/plugin.info.txt
new file mode 100644
index 0000000..8ffc136
--- /dev/null
+++ b/platform/www/lib/plugins/popularity/plugin.info.txt
@@ -0,0 +1,7 @@
+base popularity
+author Andreas Gohr
+email andi@splitbrain.org
+date 2015-07-15
+name Popularity Feedback Plugin
+desc Send anonymous data about your wiki to the DokuWiki developers
+url http://www.dokuwiki.org/plugin:popularity
diff --git a/platform/www/lib/plugins/remote.php b/platform/www/lib/plugins/remote.php
new file mode 100644
index 0000000..a3cbec7
--- /dev/null
+++ b/platform/www/lib/plugins/remote.php
@@ -0,0 +1,2 @@
+<?php
+dbg_deprecated('Autoloading. Do not require() files yourself.');
diff --git a/platform/www/lib/plugins/revert/admin.php b/platform/www/lib/plugins/revert/admin.php
new file mode 100644
index 0000000..2d11dc0
--- /dev/null
+++ b/platform/www/lib/plugins/revert/admin.php
@@ -0,0 +1,193 @@
+<?php
+
+use dokuwiki\ChangeLog\PageChangeLog;
+
+/**
+ * All DokuWiki plugins to extend the admin function
+ * need to inherit from this class
+ */
+class admin_plugin_revert extends DokuWiki_Admin_Plugin
+{
+ protected $cmd;
+ // some vars which might need tuning later
+ protected $max_lines = 800; // lines to read from changelog
+ protected $max_revs = 20; // numer of old revisions to check
+
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setupLocale();
+ }
+
+ /**
+ * access for managers
+ */
+ public function forAdminOnly()
+ {
+ return false;
+ }
+
+ /**
+ * return sort order for position in admin menu
+ */
+ public function getMenuSort()
+ {
+ return 40;
+ }
+
+ /**
+ * handle user request
+ */
+ public function handle()
+ {
+ }
+
+ /**
+ * output appropriate html
+ */
+ public function html()
+ {
+ global $INPUT;
+
+ echo $this->locale_xhtml('intro');
+
+ $this->printSearchForm();
+
+ if (is_array($INPUT->param('revert')) && checkSecurityToken()) {
+ $this->revertEdits($INPUT->arr('revert'), $INPUT->str('filter'));
+ } elseif ($INPUT->has('filter')) {
+ $this->listEdits($INPUT->str('filter'));
+ }
+ }
+
+ /**
+ * Display the form for searching spam pages
+ */
+ protected function printSearchForm()
+ {
+ global $lang, $INPUT;
+ echo '<form action="" method="post"><div class="no">';
+ echo '<label>'.$this->getLang('filter').': </label>';
+ echo '<input type="text" name="filter" class="edit" value="'.hsc($INPUT->str('filter')).'" /> ';
+ echo '<button type="submit">'.$lang['btn_search'].'</button> ';
+ echo '<span>'.$this->getLang('note1').'</span>';
+ echo '</div></form><br /><br />';
+ }
+
+ /**
+ * Start the reversion process
+ */
+ protected function revertEdits($revert, $filter)
+ {
+ echo '<hr /><br />';
+ echo '<p>'.$this->getLang('revstart').'</p>';
+
+ echo '<ul>';
+ foreach ($revert as $id) {
+ global $REV;
+
+ // find the last non-spammy revision
+ $data = '';
+ $pagelog = new PageChangeLog($id);
+ $old = $pagelog->getRevisions(0, $this->max_revs);
+ if (count($old)) {
+ foreach ($old as $REV) {
+ $data = rawWiki($id, $REV);
+ if (strpos($data, $filter) === false) break;
+ }
+ }
+
+ if ($data) {
+ saveWikiText($id, $data, 'old revision restored', false);
+ printf('<li><div class="li">'.$this->getLang('reverted').'</div></li>', $id, $REV);
+ } else {
+ saveWikiText($id, '', '', false);
+ printf('<li><div class="li">'.$this->getLang('removed').'</div></li>', $id);
+ }
+ @set_time_limit(10);
+ flush();
+ }
+ echo '</ul>';
+
+ echo '<p>'.$this->getLang('revstop').'</p>';
+ }
+
+ /**
+ * List recent edits matching the given filter
+ */
+ protected function listEdits($filter)
+ {
+ global $conf;
+ global $lang;
+ echo '<hr /><br />';
+ echo '<form action="" method="post"><div class="no">';
+ echo '<input type="hidden" name="filter" value="'.hsc($filter).'" />';
+ formSecurityToken();
+
+ $recents = getRecents(0, $this->max_lines);
+ echo '<ul>';
+
+ $cnt = 0;
+ foreach ($recents as $recent) {
+ if ($filter) {
+ if (strpos(rawWiki($recent['id']), $filter) === false) continue;
+ }
+
+ $cnt++;
+ $date = dformat($recent['date']);
+
+ echo ($recent['type']===DOKU_CHANGE_TYPE_MINOR_EDIT) ? '<li class="minor">' : '<li>';
+ echo '<div class="li">';
+ echo '<input type="checkbox" name="revert[]" value="'.hsc($recent['id']).
+ '" checked="checked" id="revert__'.$cnt.'" />';
+ echo ' <label for="revert__'.$cnt.'">'.$date.'</label> ';
+
+ echo '<a href="'.wl($recent['id'], "do=diff").'">';
+ $p = array();
+ $p['src'] = DOKU_BASE.'lib/images/diff.png';
+ $p['width'] = 15;
+ $p['height'] = 11;
+ $p['title'] = $lang['diff'];
+ $p['alt'] = $lang['diff'];
+ $att = buildAttributes($p);
+ echo "<img $att />";
+ echo '</a> ';
+
+ echo '<a href="'.wl($recent['id'], "do=revisions").'">';
+ $p = array();
+ $p['src'] = DOKU_BASE.'lib/images/history.png';
+ $p['width'] = 12;
+ $p['height'] = 14;
+ $p['title'] = $lang['btn_revs'];
+ $p['alt'] = $lang['btn_revs'];
+ $att = buildAttributes($p);
+ echo "<img $att />";
+ echo '</a> ';
+
+ echo html_wikilink(':'.$recent['id'], (useHeading('navigation'))?null:$recent['id']);
+ echo ' – '.htmlspecialchars($recent['sum']);
+
+ echo ' <span class="user">';
+ echo $recent['user'].' '.$recent['ip'];
+ echo '</span>';
+
+ echo '</div>';
+ echo '</li>';
+
+ @set_time_limit(10);
+ flush();
+ }
+ echo '</ul>';
+
+ echo '<p>';
+ echo '<button type="submit">'.$this->getLang('revert').'</button> ';
+ printf($this->getLang('note2'), hsc($filter));
+ echo '</p>';
+
+ echo '</div></form>';
+ }
+}
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/revert/admin.svg b/platform/www/lib/plugins/revert/admin.svg
new file mode 100644
index 0000000..2129d2d
--- /dev/null
+++ b/platform/www/lib/plugins/revert/admin.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13.5 7a6.5 6.5 0 0 1 6.5 6.5 6.5 6.5 0 0 1-6.5 6.5H10v-2h3.5c2.5 0 4.5-2 4.5-4.5S16 9 13.5 9H7.83l3.08 3.09L9.5 13.5 4 8l5.5-5.5 1.42 1.41L7.83 7h5.67M6 18h2v2H6v-2z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/plugins/revert/lang/en/intro.txt b/platform/www/lib/plugins/revert/lang/en/intro.txt
new file mode 100644
index 0000000..b8f3558
--- /dev/null
+++ b/platform/www/lib/plugins/revert/lang/en/intro.txt
@@ -0,0 +1,3 @@
+====== Revert Manager ======
+
+This page helps you with the automatic reversion of a spam attack. To find a list of spammy pages first enter a search string (eg. a spam URL), then confirm that the found pages are really spam and revert the edits.
diff --git a/platform/www/lib/plugins/revert/lang/en/lang.php b/platform/www/lib/plugins/revert/lang/en/lang.php
new file mode 100644
index 0000000..6bf867d
--- /dev/null
+++ b/platform/www/lib/plugins/revert/lang/en/lang.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * english language file
+ */
+
+// for admin plugins, the menu prompt to be displayed in the admin menu
+// if set here, the plugin doesn't need to override the getMenuText() method
+$lang['menu'] = 'Revert Manager';
+
+// custom language strings for the plugin
+
+$lang['filter'] = 'Search spammy pages';
+$lang['revert'] = 'Revert selected pages';
+$lang['reverted'] = '%s reverted to revision %s';
+$lang['removed'] = '%s removed';
+$lang['revstart'] = 'Reversion process started. This can take a long time. If the
+ script times out before finishing, you need to revert in smaller
+ chunks.';
+$lang['revstop'] = 'Reversion process finished successfully.';
+$lang['note1'] = 'Note: this search is case sensitive';
+$lang['note2'] = 'Note: the page will be reverted to the last version not containing the given spam term <i>%s</i>.';
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/revert/plugin.info.txt b/platform/www/lib/plugins/revert/plugin.info.txt
new file mode 100644
index 0000000..bba939d
--- /dev/null
+++ b/platform/www/lib/plugins/revert/plugin.info.txt
@@ -0,0 +1,7 @@
+base revert
+author Andreas Gohr
+email andi@splitbrain.org
+date 2015-07-15
+name Revert Manager
+desc Allows you to mass revert recent edits to remove Spam or vandalism
+url http://dokuwiki.org/plugin:revert
diff --git a/platform/www/lib/plugins/safefnrecode/action.php b/platform/www/lib/plugins/safefnrecode/action.php
new file mode 100644
index 0000000..952d95c
--- /dev/null
+++ b/platform/www/lib/plugins/safefnrecode/action.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * DokuWiki Plugin safefnrecode (Action Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+class action_plugin_safefnrecode extends DokuWiki_Action_Plugin
+{
+
+ /** @inheritdoc */
+ public function register(Doku_Event_Handler $controller)
+ {
+ $controller->register_hook('INDEXER_TASKS_RUN', 'BEFORE', $this, 'handleIndexerTasksRun');
+ }
+
+ /**
+ * Handle indexer event
+ *
+ * @param Doku_Event $event
+ * @param $param
+ */
+ public function handleIndexerTasksRun(Doku_Event $event, $param)
+ {
+ global $conf;
+ if ($conf['fnencode'] != 'safe') return;
+
+ if (!file_exists($conf['datadir'].'_safefn.recoded')) {
+ $this->recode($conf['datadir']);
+ touch($conf['datadir'].'_safefn.recoded');
+ }
+
+ if (!file_exists($conf['olddir'].'_safefn.recoded')) {
+ $this->recode($conf['olddir']);
+ touch($conf['olddir'].'_safefn.recoded');
+ }
+
+ if (!file_exists($conf['metadir'].'_safefn.recoded')) {
+ $this->recode($conf['metadir']);
+ touch($conf['metadir'].'_safefn.recoded');
+ }
+
+ if (!file_exists($conf['mediadir'].'_safefn.recoded')) {
+ $this->recode($conf['mediadir']);
+ touch($conf['mediadir'].'_safefn.recoded');
+ }
+ }
+
+ /**
+ * Recursive function to rename all safe encoded files to use the new
+ * square bracket post indicator
+ */
+ private function recode($dir)
+ {
+ $dh = opendir($dir);
+ if (!$dh) return;
+ while (($file = readdir($dh)) !== false) {
+ if ($file == '.' || $file == '..') continue; # cur and upper dir
+ if (is_dir("$dir/$file")) $this->recode("$dir/$file"); #recurse
+ if (strpos($file, '%') === false) continue; # no encoding used
+ $new = preg_replace('/(%[^\]]*?)\./', '\1]', $file); # new post indicator
+ if (preg_match('/%[^\]]+$/', $new)) $new .= ']'; # fix end FS#2122
+ rename("$dir/$file", "$dir/$new"); # rename it
+ }
+ closedir($dh);
+ }
+}
diff --git a/platform/www/lib/plugins/safefnrecode/plugin.info.txt b/platform/www/lib/plugins/safefnrecode/plugin.info.txt
new file mode 100644
index 0000000..3c6249d
--- /dev/null
+++ b/platform/www/lib/plugins/safefnrecode/plugin.info.txt
@@ -0,0 +1,7 @@
+base safefnrecode
+author Andreas Gohr
+email andi@splitbrain.org
+date 2012-07-28
+name safefnrecode plugin
+desc Changes existing page and foldernames for the change in the safe filename encoding
+url http://www.dokuwiki.org/plugin:safefnrecode
diff --git a/platform/www/lib/plugins/styling/README b/platform/www/lib/plugins/styling/README
new file mode 100644
index 0000000..a1a5e89
--- /dev/null
+++ b/platform/www/lib/plugins/styling/README
@@ -0,0 +1,27 @@
+styling Plugin for DokuWiki
+
+Allows to edit style.ini replacements
+
+All documentation for this plugin can be found at
+https://www.dokuwiki.org/plugin:styling
+
+If you install this plugin manually, make sure it is installed in
+lib/plugins/styling/ - if the folder is called different it
+will not work!
+
+Please refer to http://www.dokuwiki.org/plugins for additional info
+on how to install plugins in DokuWiki.
+
+----
+Copyright (C) Andreas Gohr <andi@splitbrain.org>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; version 2 of the License
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+See the COPYING file in your DokuWiki folder for details
diff --git a/platform/www/lib/plugins/styling/action.php b/platform/www/lib/plugins/styling/action.php
new file mode 100644
index 0000000..46245ca
--- /dev/null
+++ b/platform/www/lib/plugins/styling/action.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * DokuWiki Plugin styling (Action Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+class action_plugin_styling extends DokuWiki_Action_Plugin
+{
+
+ /**
+ * Registers a callback functions
+ *
+ * @param Doku_Event_Handler $controller DokuWiki's event controller object
+ * @return void
+ */
+ public function register(Doku_Event_Handler $controller)
+ {
+ $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handleHeader');
+ }
+
+ /**
+ * Adds the preview parameter to the stylesheet loading in non-js mode
+ *
+ * @param Doku_Event $event event object by reference
+ * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
+ * handler was registered]
+ * @return void
+ */
+ public function handleHeader(Doku_Event &$event, $param)
+ {
+ global $ACT;
+ global $INPUT;
+ if ($ACT != 'admin' || $INPUT->str('page') != 'styling') return;
+ /** @var admin_plugin_styling $admin */
+ $admin = plugin_load('admin', 'styling');
+ if (!$admin->isAccessibleByCurrentUser()) return;
+
+ // set preview
+ $len = count($event->data['link']);
+ for ($i = 0; $i < $len; $i++) {
+ if ($event->data['link'][$i]['rel'] == 'stylesheet' &&
+ strpos($event->data['link'][$i]['href'], 'lib/exe/css.php') !== false
+ ) {
+ $event->data['link'][$i]['href'] .= '&preview=1&tseed='.time();
+ }
+ }
+ }
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/styling/admin.php b/platform/www/lib/plugins/styling/admin.php
new file mode 100644
index 0000000..d454422
--- /dev/null
+++ b/platform/www/lib/plugins/styling/admin.php
@@ -0,0 +1,224 @@
+<?php
+/**
+ * DokuWiki Plugin styling (Admin Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+class admin_plugin_styling extends DokuWiki_Admin_Plugin
+{
+
+ public $ispopup = false;
+
+ /**
+ * @return int sort number in admin menu
+ */
+ public function getMenuSort()
+ {
+ return 1000;
+ }
+
+ /**
+ * @return bool true if only access for superuser, false is for superusers and moderators
+ */
+ public function forAdminOnly()
+ {
+ return true;
+ }
+
+ /**
+ * handle the different actions (also called from ajax)
+ */
+ public function handle()
+ {
+ global $INPUT;
+ $run = $INPUT->extract('run')->str('run');
+ if (!$run) return;
+ $run = 'run'.ucfirst($run);
+ $this->$run();
+ }
+
+ /**
+ * Render HTML output, e.g. helpful text and a form
+ */
+ public function html()
+ {
+ $class = 'nopopup';
+ if ($this->ispopup) $class = 'ispopup page';
+
+ echo '<div id="plugin__styling" class="'.$class.'">';
+ ptln('<h1>'.$this->getLang('menu').'</h1>');
+ $this->form();
+ echo '</div>';
+ }
+
+ /**
+ * Create the actual editing form
+ */
+ public function form()
+ {
+ global $conf;
+ global $ID;
+
+ $styleUtil = new \dokuwiki\StyleUtils($conf['template'], true, true);
+ $styleini = $styleUtil->cssStyleini();
+ $replacements = $styleini['replacements'];
+
+ if ($this->ispopup) {
+ $target = DOKU_BASE.'lib/plugins/styling/popup.php';
+ } else {
+ $target = wl($ID, array('do' => 'admin', 'page' => 'styling'));
+ }
+
+ if (empty($replacements)) {
+ echo '<p class="error">'.$this->getLang('error').'</p>';
+ } else {
+ echo $this->locale_xhtml('intro');
+
+ echo '<form class="styling" method="post" action="'.$target.'">';
+
+ echo '<table><tbody>';
+ foreach ($replacements as $key => $value) {
+ $name = tpl_getLang($key);
+ if (empty($name)) $name = $this->getLang($key);
+ if (empty($name)) $name = $key;
+
+ echo '<tr>';
+ echo '<td><label for="tpl__'.hsc($key).'">'.$name.'</label></td>';
+ echo '<td><input type="'.$this->colorType($value).'" name="tpl['.hsc($key).']" id="tpl__'.hsc($key).'"
+ value="'.hsc($this->colorValue($value)).'" dir="ltr" /></td>';
+ echo '</tr>';
+ }
+ echo '</tbody></table>';
+
+ echo '<p>';
+ echo '<button type="submit" name="run[preview]" class="btn_preview primary">'.
+ $this->getLang('btn_preview').'</button> ';
+ #FIXME only if preview.ini exists:
+ echo '<button type="submit" name="run[reset]">'.$this->getLang('btn_reset').'</button>';
+ echo '</p>';
+
+ echo '<p>';
+ echo '<button type="submit" name="run[save]" class="primary">'.$this->getLang('btn_save').'</button>';
+ echo '</p>';
+
+ echo '<p>';
+ #FIXME only if local.ini exists:
+ echo '<button type="submit" name="run[revert]">'.$this->getLang('btn_revert').'</button>';
+ echo '</p>';
+
+ echo '</form>';
+
+ echo tpl_locale_xhtml('style');
+ }
+ }
+
+ /**
+ * Adjust three char color codes to the 6 char one supported by browser's color input
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function colorValue($value)
+ {
+ if (preg_match('/^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/', $value, $match)) {
+ return '#' . $match[1] . $match[1] . $match[2] . $match[2] . $match[3] . $match[3];
+ }
+ return $value;
+ }
+
+ /**
+ * Decide the input type based on the value
+ *
+ * @param string $value
+ * @return string color|text
+ */
+ protected function colorType($value)
+ {
+ if (preg_match('/^#([0-9a-fA-F]{3}){1,2}$/', $value)) {
+ return 'color';
+ } else {
+ return 'text';
+ }
+ }
+
+ /**
+ * saves the preview.ini (alos called from ajax directly)
+ */
+ public function runPreview()
+ {
+ global $conf;
+ $ini = $conf['cachedir'].'/preview.ini';
+ io_saveFile($ini, $this->makeini());
+ }
+
+ /**
+ * deletes the preview.ini
+ */
+ protected function runReset()
+ {
+ global $conf;
+ $ini = $conf['cachedir'].'/preview.ini';
+ io_saveFile($ini, '');
+ }
+
+ /**
+ * deletes the local style.ini replacements
+ */
+ protected function runRevert()
+ {
+ $this->replaceIni('');
+ $this->runReset();
+ }
+
+ /**
+ * save the local style.ini replacements
+ */
+ protected function runSave()
+ {
+ $this->replaceIni($this->makeini());
+ $this->runReset();
+ }
+
+ /**
+ * create the replacement part of a style.ini from submitted data
+ *
+ * @return string
+ */
+ protected function makeini()
+ {
+ global $INPUT;
+
+ $ini = "[replacements]\n";
+ $ini .= ";These overwrites have been generated from the Template styling Admin interface\n";
+ $ini .= ";Any values in this section will be overwritten by that tool again\n";
+ foreach ($INPUT->arr('tpl') as $key => $val) {
+ $ini .= $key.' = "'.addslashes($val).'"'."\n";
+ }
+
+ return $ini;
+ }
+
+ /**
+ * replaces the replacement parts in the local ini
+ *
+ * @param string $new the new ini contents
+ */
+ protected function replaceIni($new)
+ {
+ global $conf;
+ $ini = DOKU_CONF."tpl/".$conf['template']."/style.ini";
+ if (file_exists($ini)) {
+ $old = io_readFile($ini);
+ $old = preg_replace('/\[replacements\]\n.*?(\n\[.*]|$)/s', '\\1', $old);
+ $old = trim($old);
+ } else {
+ $old = '';
+ }
+
+ io_makeFileDir($ini);
+ io_saveFile($ini, "$old\n\n$new");
+ }
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/styling/admin.svg b/platform/www/lib/plugins/styling/admin.svg
new file mode 100644
index 0000000..5d73870
--- /dev/null
+++ b/platform/www/lib/plugins/styling/admin.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M17.5 12a1.5 1.5 0 0 1-1.5-1.5A1.5 1.5 0 0 1 17.5 9a1.5 1.5 0 0 1 1.5 1.5 1.5 1.5 0 0 1-1.5 1.5m-3-4A1.5 1.5 0 0 1 13 6.5 1.5 1.5 0 0 1 14.5 5 1.5 1.5 0 0 1 16 6.5 1.5 1.5 0 0 1 14.5 8m-5 0A1.5 1.5 0 0 1 8 6.5 1.5 1.5 0 0 1 9.5 5 1.5 1.5 0 0 1 11 6.5 1.5 1.5 0 0 1 9.5 8m-3 4A1.5 1.5 0 0 1 5 10.5 1.5 1.5 0 0 1 6.5 9 1.5 1.5 0 0 1 8 10.5 1.5 1.5 0 0 1 6.5 12M12 3a9 9 0 0 0-9 9 9 9 0 0 0 9 9 1.5 1.5 0 0 0 1.5-1.5c0-.39-.15-.74-.39-1-.23-.27-.38-.62-.38-1a1.5 1.5 0 0 1 1.5-1.5H16a5 5 0 0 0 5-5c0-4.42-4.03-8-9-8z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/plugins/styling/lang/en/intro.txt b/platform/www/lib/plugins/styling/lang/en/intro.txt
new file mode 100644
index 0000000..4ea5517
--- /dev/null
+++ b/platform/www/lib/plugins/styling/lang/en/intro.txt
@@ -0,0 +1,2 @@
+This tool allows you to change certain style settings of your currently selected template.
+All changes are stored in a local configuration file and are upgrade safe. \ No newline at end of file
diff --git a/platform/www/lib/plugins/styling/lang/en/lang.php b/platform/www/lib/plugins/styling/lang/en/lang.php
new file mode 100644
index 0000000..e0011eb
--- /dev/null
+++ b/platform/www/lib/plugins/styling/lang/en/lang.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * English language file for styling plugin
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+// menu entry for admin plugins
+$lang['menu'] = 'Template Style Settings';
+
+$lang['js']['loader'] = 'Preview is loading...<br />if this does not goes away, your values may be faulty';
+$lang['js']['popup'] = 'Open as a popup';
+
+// custom language strings for the plugin
+$lang['error'] = 'Sorry, this template does not support this functionality.';
+
+$lang['btn_preview'] = 'Preview changes';
+$lang['btn_save'] = 'Save changes';
+$lang['btn_reset'] = 'Reset current changes';
+$lang['btn_revert'] = 'Revert styles back to template\'s default';
+
+// default guaranteed placeholders
+$lang['__text__'] = 'Main text color';
+$lang['__background__'] = 'Main background color';
+$lang['__text_alt__'] = 'Alternative text color';
+$lang['__background_alt__'] = 'Alternative background color';
+$lang['__text_neu__'] = 'Neutral text color';
+$lang['__background_neu__'] = 'Neutral background color';
+$lang['__border__'] = 'Border color';
+$lang['__highlight__'] = 'Highlight color (for search results mainly)';
+
+
+
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/styling/plugin.info.txt b/platform/www/lib/plugins/styling/plugin.info.txt
new file mode 100644
index 0000000..e374eaf
--- /dev/null
+++ b/platform/www/lib/plugins/styling/plugin.info.txt
@@ -0,0 +1,7 @@
+base styling
+author Andreas Gohr
+email andi@splitbrain.org
+date 2020-06-14
+name styling plugin
+desc Allows to edit style.ini replacements
+url https://www.dokuwiki.org/plugin:styling
diff --git a/platform/www/lib/plugins/styling/popup.php b/platform/www/lib/plugins/styling/popup.php
new file mode 100644
index 0000000..079062e
--- /dev/null
+++ b/platform/www/lib/plugins/styling/popup.php
@@ -0,0 +1,31 @@
+<?php
+// phpcs:disable PSR1.Files.SideEffects
+if (!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__) . '/../../../');
+require_once(DOKU_INC . 'inc/init.php');
+//close session
+session_write_close();
+header('Content-Type: text/html; charset=utf-8');
+header('X-UA-Compatible: IE=edge,chrome=1');
+
+/** @var admin_plugin_styling $plugin */
+$plugin = plugin_load('admin', 'styling');
+if (!$plugin->isAccessibleByCurrentUser()) die('only admins allowed');
+$plugin->ispopup = true;
+
+// handle posts
+$plugin->handle();
+
+// output plugin in a very minimal template:
+?><!DOCTYPE html>
+<html lang="<?php echo $conf['lang'] ?>" dir="<?php echo $lang['direction'] ?>">
+<head>
+ <meta charset="utf-8" />
+ <title><?php echo $plugin->getLang('menu') ?></title>
+ <?php tpl_metaheaders(false) ?>
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <?php echo tpl_favicon(array('favicon')) ?>
+</head>
+<body class="dokuwiki">
+ <?php $plugin->html() ?>
+</body>
+</html>
diff --git a/platform/www/lib/plugins/styling/script.js b/platform/www/lib/plugins/styling/script.js
new file mode 100644
index 0000000..7fa8b25
--- /dev/null
+++ b/platform/www/lib/plugins/styling/script.js
@@ -0,0 +1,92 @@
+jQuery(function () {
+
+ /**
+ * Function to reload the preview styles in the main window
+ *
+ * @param {Window} target the main window
+ */
+ function applyPreview(target) {
+ // remove style
+ var $style = target.jQuery('link[rel=stylesheet][href*="lib/exe/css.php"]');
+ $style.attr('href', '');
+
+ // append the loader screen
+ var $loader = target.jQuery('#plugin__styling_loader');
+ if (!$loader.length) {
+ $loader = target.jQuery('<div id="plugin__styling_loader">' + LANG.plugins.styling.loader + '</div>');
+ $loader.css({
+ 'position': 'absolute',
+ 'width': '100%',
+ 'height': '100%',
+ 'top': 0,
+ 'left': 0,
+ 'z-index': 5000,
+ 'background-color': '#fff',
+ 'opacity': '0.7',
+ 'color': '#000',
+ 'font-size': '2.5em',
+ 'text-align': 'center',
+ 'line-height': 1.5,
+ 'padding-top': '2em'
+ });
+ target.jQuery('body').append($loader);
+ }
+
+ // load preview in main window (timeout works around chrome updating CSS weirdness)
+ setTimeout(function () {
+ var now = new Date().getTime();
+ $style.attr('href', DOKU_BASE + 'lib/exe/css.php?preview=1&tseed=' + now);
+ }, 500);
+ }
+
+ var doreload = 1;
+ var $styling_plugin = jQuery('#plugin__styling');
+
+ // if we are not on the plugin page (either main or popup)
+ if (!$styling_plugin.length) {
+ // handle the preview cookie
+ if(DokuCookie.getValue('styling_plugin') == 1) {
+ applyPreview(window);
+ }
+ return; // nothing more to do here
+ }
+
+ /* ---- from here on we're in the popup or admin page ---- */
+
+ // add button on main page
+ if (!$styling_plugin.hasClass('ispopup')) {
+ var $form = $styling_plugin.find('form.styling').first();
+ var $btn = jQuery('<button>' + LANG.plugins.styling.popup + '</button>');
+ $form.prepend($btn);
+
+ $btn.on('click', function (e) {
+ var windowFeatures = "menubar=no,location=no,resizable=yes,scrollbars=yes,status=false,width=500,height=500";
+ window.open(DOKU_BASE + 'lib/plugins/styling/popup.php', 'styling_popup', windowFeatures);
+ e.preventDefault();
+ e.stopPropagation();
+ }).wrap('<p></p>');
+ return; // we exit here if this is not the popup
+ }
+
+ /* ---- from here on we're in the popup only ---- */
+
+ // reload the main page on close
+ window.onunload = function(e) {
+ if(doreload) {
+ DokuCookie.setValue('styling_plugin', 0);
+ if(window.opener) window.opener.document.location.reload();
+ }
+ return null;
+ };
+
+ // don't reload on our own buttons
+ jQuery(':button').click(function(e){
+ doreload = false;
+ });
+
+ // on first load apply preview
+ if(window.opener) applyPreview(window.opener);
+
+ // enable the preview cookie
+ DokuCookie.setValue('styling_plugin', 1);
+});
diff --git a/platform/www/lib/plugins/styling/style.less b/platform/www/lib/plugins/styling/style.less
new file mode 100644
index 0000000..be0e16a
--- /dev/null
+++ b/platform/www/lib/plugins/styling/style.less
@@ -0,0 +1,13 @@
+#plugin__styling {
+ button.primary {
+ font-weight: bold;
+ }
+
+ [dir=rtl] & table input {
+ text-align: right;
+ }
+}
+
+#plugin__styling_loader {
+ display: none;
+}
diff --git a/platform/www/lib/plugins/syntax.php b/platform/www/lib/plugins/syntax.php
new file mode 100644
index 0000000..a3cbec7
--- /dev/null
+++ b/platform/www/lib/plugins/syntax.php
@@ -0,0 +1,2 @@
+<?php
+dbg_deprecated('Autoloading. Do not require() files yourself.');
diff --git a/platform/www/lib/plugins/textinsert/README b/platform/www/lib/plugins/textinsert/README
new file mode 100644
index 0000000..ed0c173
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/README
@@ -0,0 +1,55 @@
+This is a macro substitution plugin which enables substitutions of both words and longer
+strings of text.
+
+Basic Syntax: #@Macro_Name@#
+
+It provides an administrative panel which makes for simple adding, editing and deletion
+of macros.
+
+Much of the functionality of this plugin can be more easily implemented with Dokuwiki's
+own entities facility: http://www.dokuwiki.org/entities. The advantage of TextInsert
+comes when dealing with extended blocks of text and with its ability to include other
+macros inside the primary macro definition. That is, it can accept:
+
+MACRO_1 This macro can include #@MACR0_2@# inside it.
+MACRO_2 a second macro
+
+The result is:
+ This macro can include a second macro inside it.
+
+It accepts only one level of macro inclusion, so that if MACRO_3 were included in
+MACRO_2, MACRO_3 would not be rendered.
+
+The macro definitions will also accept entities defined dokuwiki's conf/entities.conf and
+user-defined entities conf/entities.local.conf. Entities will be replaced in both the
+primary and the included macros.
+
+HTML Support
+#Macro_HTML@#
+A macro name with the _HTML suffix will be output as HTML, whereas without the _HTML suffix the
+HTML markup will be treated as literals. With it, <p> creates a paragraph; wthout it the <p>
+markup is printed to the screen.
+
+Translation Support
+#@LANG_name@#
+If a macro has this format, textinsert checks the file's namespace for a language specified
+as an ISO abbreviation, for instance nl for Dutch or de for German -- as in de:my_file. If it finds
+a language specification, it will check the textinsert/lang directory for either macros.php or a lang.php
+file in the relevant language directory, for instance lang/de/lang.php. It will then look for a
+$lang['name'] entry in lang.php or a $lang_<iso>['name'] entry in macros.php, and if
+it finds one, it will substitute this for the macro. Otherwise, it will substitute the entry for
+LANG_name in the textinsert database. So, there must be a default entry in that database.
+
+Translation macros can be embeded in other macros, including other translation macros.
+And other macros can emdedded in translation macros. They also accept entities, as
+described above.
+
+For HTML output, HTML tags are accepted in translation macros, but for the HTML
+to become effective, these must be included in a standard HTML macro definition
+string. In effect, they must be doubly bound to the HTML
+
+See http://dokuwiki.org/plugin:textinsert for further information.
+
+
+
+
diff --git a/platform/www/lib/plugins/textinsert/admin.php b/platform/www/lib/plugins/textinsert/admin.php
new file mode 100644
index 0000000..3d85347
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/admin.php
@@ -0,0 +1,292 @@
+<?php
+/**
+ *
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Myron Turner <turnermm02@shaw.ca>
+ */
+if(!defined('DOKU_INC')) die();
+
+define('REPLACE_DIR', DOKU_INC . 'data/meta/macros/');
+
+/**
+ * All DokuWiki plugins to extend the admin function
+ * need to inherit from this class
+ */
+class admin_plugin_textinsert extends DokuWiki_Admin_Plugin {
+
+ var $output = false;
+ var $macros_file;
+ var $macros_data; //used for html listings
+ /**
+ * handle user request
+ */
+ function __construct() {
+ if(!$this->getConf('farm')) {
+ define('MACROS_FILE', REPLACE_DIR . 'macros.ser');
+ }
+ else {
+ define('MACROS_FILE', metaFN('macros','.ser'));
+ }
+
+ }
+ function handle() {
+
+ $this->macros_file=MACROS_FILE;
+
+ if (!isset($_REQUEST['cmd'])) return; // first time - nothing to do
+
+ $this->output = '';
+ if (!checkSecurityToken()) return;
+ if (!is_array($_REQUEST['cmd'])) return;
+ $action = "";
+
+ // verify valid values
+ switch (key($_REQUEST['cmd'])) {
+ case 'add' :
+ $action = 'add';
+ $a = $this->add();
+ break;
+
+ case 'delete' :
+ $a = $this->del();
+ break;
+ case 'edit':
+ $a = $this->edit();
+ break;
+ }
+ // $this->output = print_r($a,true);
+ // $this->output .= print_r($_REQUEST,true);
+ }
+
+ function add() {
+ $a = $this->get_macros();
+ $macros = $_REQUEST['macro'];
+ $words = $_REQUEST['word'];
+
+ foreach ($macros AS $key=>$value) {
+ if(isset($value) && trim($value)) {
+ if(isset($words[$key]) && trim($words[$key])) {
+ $value = utf8_deaccent($value);
+ $a[$value] = htmlspecialchars (($words[$key]),ENT_NOQUOTES, 'UTF-8');
+ }
+ }
+ }
+
+ io_saveFile(MACROS_FILE,serialize($a));
+ return $a;
+ }
+
+ function del() {
+ $macros = $this->get_macros();
+
+ $deletions = $_REQUEST['delete'];
+ $keys = array_keys($deletions);
+ foreach ($keys AS $_key) {
+ unset($macros[$_key]);
+ }
+
+ io_saveFile(MACROS_FILE,serialize($macros));
+ return $macros;
+ }
+
+ function edit() {
+ $macros = $this->get_macros();
+
+ $encoded = $_REQUEST['encoded'];
+ $encoded = array_map('urldecode',$encoded);
+ foreach($encoded AS $k=>$val) {
+ $macros[$k] = htmlspecialchars ($val,ENT_NOQUOTES, 'UTF-8', false);
+ }
+ io_saveFile(MACROS_FILE,serialize($macros));
+ return $macros;
+ }
+
+ function get_macros() {
+ if(file_exists(MACROS_FILE)) {
+ $a = unserialize(file_get_contents(MACROS_FILE));
+ if(!is_array($a)) return array();
+ ksort($a);
+ return $a;
+ }
+ return array();
+ }
+
+ function get_delete_list() {
+ $macros = $this->macros_data;
+ ptln('<table cellspacing="4px" width="90%">');
+ foreach($macros as $macro=>$subst) {
+ ptln("<tr><td><input type='checkbox' name='delete[$macro]' value='$subst'>");
+ ptln( "<td style='padding:4px;'>$macro<td>$subst</td>");
+
+ }
+ ptln('</table>');
+ }
+
+ function get_edit_list() {
+ $macros = $this->macros_data;
+ ptln('<table cellspacing="4"><tr><th align="center">Macro</th><th align="center">' . $this->getLang('col_subst') .'</th></tr>');
+ foreach($macros as $macro=>$subst) {
+ ptln("<tr><td align='center'>$macro&nbsp;</td><td>");
+ $encoded = urlencode($subst);
+ if($subst != $encoded) {
+ ptln("<input type = 'hidden' name='encoded[$macro]' value='$encoded'>");
+ }
+ if(strlen($subst) > 80) {
+ ptln ("<textarea cols='55' rows='3' name='edit[$macro]' onchange='replace_encode(this)'>$subst</textarea></td></tr>");
+
+ }
+ else {
+ ptln ("<input type='text' size='80' name='edit[$macro]' onchange='replace_encode(this)' value='$subst'></td></tr>");
+ }
+
+ }
+ ptln('</table>');
+ }
+
+ function view_entries() {
+ $macros = $this->macros_data;
+ ptln('<table cellpadding="8px" width="90%">');
+ foreach($macros as $macro=>$subst) {
+ ptln( "<tr><td align='center'>$macro<td style='padding: 4px; border-bottom: 1px solid black;'>$subst</tr>");
+
+ }
+ ptln('</table>');
+ }
+
+ function js() {
+
+echo <<<JSFN
+
+ <script type="text/javascript">
+ //<![CDATA[
+ var replace_divs= new Array('macro_add','macro_del','macro_edit','ti_info','macro_list');
+ /**
+ * Edit onChange handler
+ * @param el input element which has been changed
+ * @desc if an encode hidden input already exists, its value
+ * is re-encoded from the text input's value
+ * If not, a new encoded hidden input is created with the encoded
+ * value. The encode input value is used to substitute the new edit values
+ * in the php edit() function
+ */
+ function replace_encode (el) {
+ var matches = el.name.match(/\[(.*)\]/);
+ if(matches[1]) {
+ var name = 'encoded['+matches[1]+']';
+ var val = el.value;
+ val = val.replace(/>/g,"&gt;");
+ val = val.replace(/</g,"&lt;");
+ if(!el.form[name]) {
+ var encoder = document.createElement('input');
+ encoder.type = 'hidden';
+ encoder.name = name;
+ encoder.value = encodeURIComponent(val);
+ el.form.appendChild(encoder);
+ }
+ else if(el.form[name]) {
+ el.form[name].value = encodeURIComponent(val);
+ }
+ }
+ }
+
+ function ti_getEL(n) {
+ return document.getElementById(n);
+ }
+
+ function replace_show(which) {
+ for(var i in replace_divs) {
+ ti_getEL(replace_divs[i]).style.display='none';
+ }
+ ti_getEL(which).style.display='block';
+ ti_getEL('ti_info_btn').style.display='inline';
+ }
+//]]>
+ </script>
+JSFN;
+
+ }
+ /**
+ * output appropriate html
+ */
+ function html() {
+ $this->macros_data = $this->get_macros();
+ $this->js();
+ if($this->output) {
+ ptln('<pre>' . $this->output . '</pre>');
+ }
+ ptln('<div style="padding:4px" id="ti_info">');
+ ptln('<div style="text-align:right;">');
+ ptln('<button class="button" style="padding:0px;margin:0px;" onclick="replace_show(\'ti_info_btn\');">');
+ ptln($this->getLang('hide_info') .'</button>&nbsp;&nbsp;&nbsp;&nbsp;');
+ ptln('</div>');
+ ptln('<h2>Info</h2>');
+ ptln( $this->locale_xhtml('intro') . '</div>');
+
+ ptln('<div style="padding-bottom:8px;">');
+ ptln('<button class="button" onclick="replace_show(\'macro_add\'); ">');
+ ptln($this->getLang('add_macros') .'</button>&nbsp;&nbsp;');
+
+ ptln('<button class="button" onclick="replace_show(\'macro_del\'); ">');
+ ptln($this->getLang('delete_macros') .'</button>&nbsp;&nbsp;');
+
+ ptln('<button class="button" onclick="replace_show(\'macro_edit\'); ">');
+ ptln($this->getLang('edit_macros') .'</button>&nbsp;&nbsp;');
+
+ ptln('<button class="button" onclick="ti_getEL(\'macro_list\').style.display=\'block\';ti_getEL(\'macro_list\').scrollIntoView();">');
+ ptln($this->getLang('view_macros') .'</button>&nbsp;&nbsp;');
+
+ ptln('<button class="button" onclick="ti_getEL(\'macro_list\').style.display=\'none\';">');
+ ptln($this->getLang('hide_macros') .'</button>&nbsp;&nbsp;');
+
+ ptln('<button class="button" id="ti_info_btn" style="display:none" onclick="ti_getEL(\'ti_info\').style.display=\'block\';">');
+ ptln($this->getLang('show_info') .'</button>');
+
+ ptln('</div>');
+ ptln('<form action="'.wl($ID).'" method="post">');
+
+ // output hidden values to ensure dokuwiki will return back to this plugin
+ ptln(' <input type="hidden" name="do" value="admin" />');
+ ptln(' <input type="hidden" name="page" value="'.$this->getPluginName().'" />');
+ formSecurityToken();
+
+ ptln('<div id="macro_add" style="display:none">');
+ ptln('<h2>' . $this->getLang('label_add') . '</h2>');
+ ptln( '<table cellspacing="8px"><tr><th>Macro</th><th>' . $this->getLang('col_subst') . '</th></tr>');
+ ptln('<tr><td> <input type="text" name="macro[A]" id="m_A" value="" /></td>');
+ ptln('<td> <input type="text" name="word[A]" size="80" id="w_A" value="" /></td></tr>');
+ ptln('<tr><td> <input type="text" name="macro[B]" id="m_B" value="" /></td>');
+ ptln('<td> <input type="text" name="word[B]" size="80" id="w_B" value="" /></td></tr>');
+ ptln('<tr><td> <input type="text" name="macro[C]" id="m_C" value="" /></td>');
+ ptln('<td> <input type="text" name="word[C]" size="80" id="w_C" value="" /></td></tr>');
+ ptln('<tr><td> <input type="text" name="macro[D]" id="m_D" value="" /></td>');
+ ptln('<td> <input type="text" name="word[D]" size="80" id="w_C" value="" /></td></tr>');
+ ptln('<tr><td> <input type="text" name="macro[E]" id="m_E" value="" /></td>');
+ ptln('<td> <input type="text" name="word[E]" size="80" id="w_E" value="" /></td>');
+ ptln('<tr><td> <input type="text" name="macro[F]" id="m_F" value="" /></td>');
+ ptln('<td> <textarea cols="45" name="word[F]" rows="4" id="w_F"></textarea></td>');
+ ptln('</table>');
+ ptln(' <input type="submit" name="cmd[add]" value="'.$this->getLang('btn_add').'" />');
+ ptln('</div><br />');
+
+ ptln('<div id="macro_del" style="display:none">');
+ ptln('<h2>' . $this->getLang('label_del') . '</h2>');
+ $this->get_delete_list();
+ ptln('<br /><input type="submit" name="cmd[delete]" value="'.$this->getLang('btn_del').'" />');
+ ptln('</div>');
+
+ ptln('<div id="macro_edit" style="display:none; padding: 8px;">');
+ ptln('<h2>' . $this->getLang('label_edit') . '</h2>');
+ $this->get_edit_list();
+ ptln('<br /><input type="submit" name="cmd[edit]" value="'.$this->getLang('btn_edit').'" />');
+ ptln('</div>');
+
+ ptln('</form>');
+
+ ptln('<br /><div id="macro_list" style="overflow:auto;display:block;">');
+ ptln('<h2>' . $this->getLang('label_list') . '</h2>');
+ $this->view_entries();
+ ptln('</div>');
+ }
+
+}
diff --git a/platform/www/lib/plugins/textinsert/conf/default.php b/platform/www/lib/plugins/textinsert/conf/default.php
new file mode 100644
index 0000000..efe3db4
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/conf/default.php
@@ -0,0 +1,5 @@
+<?php
+$conf['stdreplace'] = 1;
+$conf['farm'] = 0;
+
+
diff --git a/platform/www/lib/plugins/textinsert/conf/metadata.php b/platform/www/lib/plugins/textinsert/conf/metadata.php
new file mode 100644
index 0000000..56168e8
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/conf/metadata.php
@@ -0,0 +1,3 @@
+<?php
+$meta['stdreplace'] = array('onoff');
+$meta['farm'] = array('onoff'); \ No newline at end of file
diff --git a/platform/www/lib/plugins/textinsert/lang/de/lang.php b/platform/www/lib/plugins/textinsert/lang/de/lang.php
new file mode 100644
index 0000000..907fe6f
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/de/lang.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Thor Weinreich <thorweinreich@nefkom.net>
+ */
+$lang['btn_add'] = 'Hinzufügen';
+$lang['btn_del'] = 'Löschen';
+$lang['btn_edit'] = 'Änderungen speichern';
+$lang['label_list'] = 'Makro-Liste';
+$lang['label_add'] = 'Makros hinzufügen';
+$lang['label_del'] = 'Makros löschen';
+$lang['label_edit'] = 'Makros bearbeiten';
+$lang['col_subst'] = 'Makros ersetzen';
+$lang['add_macros'] = 'Makros hinzufügen';
+$lang['delete_macros'] = 'Makros löschen';
+$lang['edit_macros'] = 'Makros bearbeiten';
+$lang['view_macros'] = 'Makro-Liste anzeigen';
+$lang['hide_macros'] = 'Makro-Liste verbergen';
+$lang['hide_info'] = 'Info schließen';
+$lang['show_info'] = 'Info anzeigen';
diff --git a/platform/www/lib/plugins/textinsert/lang/de/settings.php b/platform/www/lib/plugins/textinsert/lang/de/settings.php
new file mode 100644
index 0000000..ca9e0e1
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/de/settings.php
@@ -0,0 +1,8 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Thor Weinreich <thorweinreich@nefkom.net>
+ */
+$lang['stdreplace'] = 'Standard Makro-Ersetzungen annehmen, wie sie in Namensraum-Templates benutzt werden';
diff --git a/platform/www/lib/plugins/textinsert/lang/en/intro.txt b/platform/www/lib/plugins/textinsert/lang/en/intro.txt
new file mode 100644
index 0000000..a89d5e5
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/en/intro.txt
@@ -0,0 +1,16 @@
+This panel enables you to add and delete macros and their replacement texts, and to edit them after they have been saved. A macro name can contain letters, numbers, underscores, hyphens and periods. For example: ''Macro_one.txt''. The replacement texts can accept HTML and can be of any length.
+
+You can add up to six macros at a time. The sixth is a text area which will allow for extended texts.
+Enter the macro name in the **Macro** column and the texts which they represent in the
+**Substitution** column.
+
+
+Deletions are unlimited; check off the box(es) next the macro(s) to be deleted and click
+the **Delete** button at the bottom of the screen. Editing is done through the Edit screen, where you are presented
+with all your macros. You can edit the texts of any number of macros.
+
+
+The macro list will not refresh until after you have submitted your edits, additions, or deletions
+by clicking the appropriate button at the bottom of the screen.
+
+
diff --git a/platform/www/lib/plugins/textinsert/lang/en/lang.php b/platform/www/lib/plugins/textinsert/lang/en/lang.php
new file mode 100644
index 0000000..7a01bc6
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/en/lang.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * English language file
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Myron turner <turnermm02@shaw.ca>
+ */
+
+// for admin plugins, the menu prompt to be displayed in the admin menu
+// if set here, the plugin doesn't need to override the getMenuText() method
+$lang['menu'] = 'TextInsert Macro Replacement Plugin';
+
+$lang['btn_add'] = 'Add';
+$lang['btn_del'] = 'Delete';
+$lang['btn_edit'] = 'Submit Edits';
+
+$lang['label_list'] = "Macro List";
+$lang['label_add'] = "Add Macros";
+$lang['label_del'] = "Delete Macros";
+$lang['label_edit'] = "Edit Macros";
+
+$lang['col_subst'] = 'Macro Substitution';
+$lang['add_macros'] = 'Add Macros';
+$lang['delete_macros'] = 'Delete Macros';
+$lang['edit_macros'] = 'Edit Macros';
+$lang['view_macros'] = 'View Macro List';
+$lang['hide_macros'] = 'Hide Macro List';
+$lang['hide_info'] = 'Close Info';
+$lang['show_info'] = 'Show Info';
+$lang['not_found'] = 'macro was not found in the macros database';
+
diff --git a/platform/www/lib/plugins/textinsert/lang/en/macros.php b/platform/www/lib/plugins/textinsert/lang/en/macros.php
new file mode 120000
index 0000000..21b1645
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/en/macros.php
@@ -0,0 +1 @@
+../../../../../../i18n/textinsert_strings.php \ No newline at end of file
diff --git a/platform/www/lib/plugins/textinsert/lang/en/settings.php b/platform/www/lib/plugins/textinsert/lang/en/settings.php
new file mode 100644
index 0000000..4083a83
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/en/settings.php
@@ -0,0 +1,3 @@
+<?php
+$lang['stdreplace'] = 'Accept standard macro replacements as used in namespace templates';
+$lang['farm'] = 'If this is a farm and you and would like each animal to have its own macro database, please select this option; otherwise all animals will share the same (i.e. the farmer\'s) database.';
diff --git a/platform/www/lib/plugins/textinsert/lang/es/intro.txt b/platform/www/lib/plugins/textinsert/lang/es/intro.txt
new file mode 100644
index 0000000..a89d5e5
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/es/intro.txt
@@ -0,0 +1,16 @@
+This panel enables you to add and delete macros and their replacement texts, and to edit them after they have been saved. A macro name can contain letters, numbers, underscores, hyphens and periods. For example: ''Macro_one.txt''. The replacement texts can accept HTML and can be of any length.
+
+You can add up to six macros at a time. The sixth is a text area which will allow for extended texts.
+Enter the macro name in the **Macro** column and the texts which they represent in the
+**Substitution** column.
+
+
+Deletions are unlimited; check off the box(es) next the macro(s) to be deleted and click
+the **Delete** button at the bottom of the screen. Editing is done through the Edit screen, where you are presented
+with all your macros. You can edit the texts of any number of macros.
+
+
+The macro list will not refresh until after you have submitted your edits, additions, or deletions
+by clicking the appropriate button at the bottom of the screen.
+
+
diff --git a/platform/www/lib/plugins/textinsert/lang/es/lang.php b/platform/www/lib/plugins/textinsert/lang/es/lang.php
new file mode 100644
index 0000000..7a01bc6
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/es/lang.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * English language file
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Myron turner <turnermm02@shaw.ca>
+ */
+
+// for admin plugins, the menu prompt to be displayed in the admin menu
+// if set here, the plugin doesn't need to override the getMenuText() method
+$lang['menu'] = 'TextInsert Macro Replacement Plugin';
+
+$lang['btn_add'] = 'Add';
+$lang['btn_del'] = 'Delete';
+$lang['btn_edit'] = 'Submit Edits';
+
+$lang['label_list'] = "Macro List";
+$lang['label_add'] = "Add Macros";
+$lang['label_del'] = "Delete Macros";
+$lang['label_edit'] = "Edit Macros";
+
+$lang['col_subst'] = 'Macro Substitution';
+$lang['add_macros'] = 'Add Macros';
+$lang['delete_macros'] = 'Delete Macros';
+$lang['edit_macros'] = 'Edit Macros';
+$lang['view_macros'] = 'View Macro List';
+$lang['hide_macros'] = 'Hide Macro List';
+$lang['hide_info'] = 'Close Info';
+$lang['show_info'] = 'Show Info';
+$lang['not_found'] = 'macro was not found in the macros database';
+
diff --git a/platform/www/lib/plugins/textinsert/lang/es/macros.php b/platform/www/lib/plugins/textinsert/lang/es/macros.php
new file mode 120000
index 0000000..21b1645
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/es/macros.php
@@ -0,0 +1 @@
+../../../../../../i18n/textinsert_strings.php \ No newline at end of file
diff --git a/platform/www/lib/plugins/textinsert/lang/es/settings.php b/platform/www/lib/plugins/textinsert/lang/es/settings.php
new file mode 100644
index 0000000..4083a83
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/es/settings.php
@@ -0,0 +1,3 @@
+<?php
+$lang['stdreplace'] = 'Accept standard macro replacements as used in namespace templates';
+$lang['farm'] = 'If this is a farm and you and would like each animal to have its own macro database, please select this option; otherwise all animals will share the same (i.e. the farmer\'s) database.';
diff --git a/platform/www/lib/plugins/textinsert/lang/fr/intro.txt b/platform/www/lib/plugins/textinsert/lang/fr/intro.txt
new file mode 100644
index 0000000..ce05cbd
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/fr/intro.txt
@@ -0,0 +1,10 @@
+Ce panneau vous permet d'ajouter et supprimer des macros et leur textes de remplacement, de les modifier après leur enregistrement. Un nom de macro peut contenir des lettres, des nombres, des tirets bas, traits d'union et points. Par exemple : ''Macro_one.txt''. Le texte de remplacement peut accepter le HTML et peut être de n'importe quelle longueur.
+
+Vous pouvez ajouter jusqu'à six macros à la fois. Le sixième est une zone de texte qui permettra de longs textes.
+Entrez le nom de la macro dans la colonne **Macro** et les textes qu'ils représentent dans la colonne **Remplacement**.
+
+
+Les suppressions sont illimitées ; cochez la devant la macro à supprimer et cliquez sur le bouton **Supprimer** en bas de l'écran. La modification se fait à travers l'écran de modification, où vous sont présenté toutes vos macros. Vous pouvez modifier les textes de n'importe quel nombre de macros.
+
+
+La liste macro ne s'actualise pas jusqu'à ce que vous avez envoyer vos modifications, ajouts ou suppressions en cliquant sur le bouton approprié en bas de l'écran. \ No newline at end of file
diff --git a/platform/www/lib/plugins/textinsert/lang/fr/lang.php b/platform/www/lib/plugins/textinsert/lang/fr/lang.php
new file mode 100644
index 0000000..2570f93
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/fr/lang.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Hérisson grognon <dodoperso@laposte.net>
+ * @author Ner0lph <forums@ner0lph.tk>
+ */
+$lang['menu'] = 'TextInsert Macro Replacement Plugin';
+$lang['btn_add'] = 'Ajouter';
+$lang['btn_del'] = 'Supprimer';
+$lang['btn_edit'] = 'Envoyer les modifications';
+$lang['label_list'] = 'Liste des macros';
+$lang['label_add'] = 'Ajouter des macros';
+$lang['label_del'] = 'Supprimer des macros';
+$lang['label_edit'] = 'Modifier des macros';
+$lang['col_subst'] = 'Substitution de macro';
+$lang['add_macros'] = 'Ajouter des macros';
+$lang['delete_macros'] = 'Supprimer des macros';
+$lang['edit_macros'] = 'Modifier des macros';
+$lang['view_macros'] = 'Afficher la liste des macros';
+$lang['hide_macros'] = 'Cacher la liste des macros';
+$lang['hide_info'] = 'Fermer les informations';
+$lang['show_info'] = 'Montrer les informations';
+$lang['not_found'] = 'Le macro n\'a pas été trouvé dans la base de données des macros.';
diff --git a/platform/www/lib/plugins/textinsert/lang/fr/settings.php b/platform/www/lib/plugins/textinsert/lang/fr/settings.php
new file mode 100644
index 0000000..92bd788
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/fr/settings.php
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Ner0lph <forums@ner0lph.tk>
+ * @author ubibene <services.m@benard.info>
+ */
+$lang['stdreplace'] = 'Accepter des remplacements standards de macros en tant que modèles de catégorie';
+$lang['farm'] = 'Si ceci était une ferme et que vous souhaitez que chaque animal ait sa propre base de macros, sélectionnez cette option; sinon tous les animaux partageront la même base de données (c\'est à dire celle du fermier).';
diff --git a/platform/www/lib/plugins/textinsert/lang/ja/intro.txt b/platform/www/lib/plugins/textinsert/lang/ja/intro.txt
new file mode 100644
index 0000000..ef806fd
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/ja/intro.txt
@@ -0,0 +1,16 @@
+この画面では、マクロと代替テキストの追加と削除、保存後の編集ができます。
+マクロ名は、文字・数字・アンダースコア・ハイフン・ピリオドを使用します。
+例:''Macro_one.txt''
+代替テキストは HTML も許容しますし、任意長です。
+
+一度に 6 マクロを追加できます。
+テキスト欄は拡張テキストを入力できます。
+**マクロ**欄にマクロ名、**置換**欄に代替テキストを入力します。
+
+削除に制限はありません。
+削除するマクロの隣にあるチェックを外し、画面下の**削除**ボタンをクリックします。
+編集は編集画面で行います。
+編集画面には登録されたマクロをすべて表示されています。
+複数のマクロのテキスト編集も可能です。
+
+画面下にあるボタンをクリックして編集・追加・削除を実行するまでマクロ一覧は更新されません。 \ No newline at end of file
diff --git a/platform/www/lib/plugins/textinsert/lang/ja/lang.php b/platform/www/lib/plugins/textinsert/lang/ja/lang.php
new file mode 100644
index 0000000..f9b95ba
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/ja/lang.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Hideaki SAWADA <chuno@live.jp>
+ */
+$lang['menu'] = 'TextInsert マクロ置換プラグイン';
+$lang['btn_add'] = '追加';
+$lang['btn_del'] = '削除';
+$lang['btn_edit'] = '編集の実行';
+$lang['label_list'] = 'マクロ一覧';
+$lang['label_add'] = 'マクロ追加';
+$lang['label_del'] = 'マクロ削除';
+$lang['label_edit'] = 'マクロ編集';
+$lang['col_subst'] = 'マクロ置換';
+$lang['add_macros'] = 'マクロ追加';
+$lang['delete_macros'] = 'マクロ削除';
+$lang['edit_macros'] = 'マクロ編集';
+$lang['view_macros'] = 'マクロ一覧を表示';
+$lang['hide_macros'] = 'マクロ一覧を非表示';
+$lang['hide_info'] = '解説を非表示';
+$lang['show_info'] = '解説を表示';
diff --git a/platform/www/lib/plugins/textinsert/lang/ja/settings.php b/platform/www/lib/plugins/textinsert/lang/ja/settings.php
new file mode 100644
index 0000000..c5ef2c1
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/ja/settings.php
@@ -0,0 +1,9 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Hideaki SAWADA <chuno@live.jp>
+ */
+$lang['stdreplace'] = '名前空間テンプレートで使用されている標準マクロ置換を許容する';
+$lang['farm'] = 'この Wiki が牧場で各動物に独自のマクロデータベースを持たせたい場合、このオプションを選択してください。選択しない場合、全ての動物は同一の(つまり牧場主の)データベースを共有することになります。';
diff --git a/platform/www/lib/plugins/textinsert/lang/nl/intro.txt b/platform/www/lib/plugins/textinsert/lang/nl/intro.txt
new file mode 100644
index 0000000..8e35a1e
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/nl/intro.txt
@@ -0,0 +1,11 @@
+Met dit luik kan je macro's en hun vervangingsteksten onderhouden: aanmaken, schrappen en bewerken nadat ze opgeslagen zijn. Een macro naam kan letters, cijfers, onderlijningsstreepjes, koppeltekens en punten bevatten. Voorbeeld: ''Macro_one.txt''. De vervangtekst kan in HTML zijn en gelijk welke lengte.
+
+Zes macro's tegelijk kan je toevoegen. De zesde is de tekstzone waar je uitgebreide teksten kan invoeren.
+Geef een macro naam in de **Macro** kolom en de tekst die hier aan gekoppeld is in de **Vervangteksten** kolom.
+
+Schrappen kan je onbeperkt uitvoeren. Vink het vakje of de vakjes aan naast de macro's die geschrapt moeten
+worden. Klik daarna op de **Schrappen** knop onderaan het scherm. Bewerken doe je via het bewerkingsscherm waar
+je alle macro's ziet staan. Je bewerkt teksten van zoveel macro's als je maar wil.
+
+De macro lijst wordt niet hernieuwd zolang de wijzigingen, toevoegingen, schrappingen niet doorgevoerd zijn
+door te klikken op de voorziene knop onderaan het scherm. \ No newline at end of file
diff --git a/platform/www/lib/plugins/textinsert/lang/nl/lang.php b/platform/www/lib/plugins/textinsert/lang/nl/lang.php
new file mode 100644
index 0000000..ce1b255
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/nl/lang.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Myron turner <turnermm02@shaw.ca>
+ * @author hugo smet <hugo.smet@scarlet.be>
+ */
+$lang['menu'] = 'TextInsert Macro Substitutie Plugin';
+$lang['btn_add'] = 'Toevoegen';
+$lang['btn_del'] = 'Schrappen';
+$lang['btn_edit'] = 'Wijzigingen doorvoeren';
+$lang['label_list'] = 'Macro Lijst';
+$lang['label_add'] = 'Macro\'s Toevoegen';
+$lang['label_del'] = 'Macro\'s Schrappen';
+$lang['label_edit'] = 'Macro\'s Bewerken';
+$lang['col_subst'] = 'Macro Vervangteksten';
+$lang['add_macros'] = 'Toevoegen';
+$lang['delete_macros'] = 'Schrappen';
+$lang['edit_macros'] = 'Bewerken';
+$lang['view_macros'] = 'Lijst Tonen';
+$lang['hide_macros'] = 'Lijst Verbergen';
+$lang['hide_info'] = 'Info Sluiten';
+$lang['show_info'] = 'Info Tonen';
diff --git a/platform/www/lib/plugins/textinsert/lang/nl/settings.php b/platform/www/lib/plugins/textinsert/lang/nl/settings.php
new file mode 100644
index 0000000..afb2901
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/nl/settings.php
@@ -0,0 +1,8 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Hugo Smet <hugo.smet@scarlet.be>
+ */
+$lang['stdreplace'] = 'Standaard macro substituties aanvaarden zoals deze in naamruimte sjablonen gebruikt worden';
diff --git a/platform/www/lib/plugins/textinsert/lang/pt-br/intro.txt b/platform/www/lib/plugins/textinsert/lang/pt-br/intro.txt
new file mode 100644
index 0000000..974e84c
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/pt-br/intro.txt
@@ -0,0 +1,13 @@
+Esse painel permite adicionar e excluir macros e seus textos de substituição e editá-los depois de salvos. Um nome de macro pode conter letras, números, sublinhados, hífens e pontos. Por exemplo: '' Macro_one.txt ''. Os textos de substituição podem aceitar HTML e podem ser de qualquer tamanho.
+
+Você pode adicionar até seis macros por vez. A sexta é uma área de texto que permitirá textos estendidos.
+Insira o nome da macro na coluna ** Macro ** e os textos que eles representam na
+** Substituição ** coluna.
+
+Deleções são ilimitadas; marque a caixa ao lado da(s) macro(s) a ser (em) excluída(s) e clique
+no botão ** Excluir ** na parte inferior da tela. A edição é feita através da tela Editar, onde você é apresentado
+com todas as suas macros. Você pode editar os textos de qualquer número de macros.
+
+
+A lista de macros não será atualizada até que você tenha enviado suas edições, adições ou exclusões
+clicando no botão apropriado na parte inferior da tela. \ No newline at end of file
diff --git a/platform/www/lib/plugins/textinsert/lang/pt-br/lang.php b/platform/www/lib/plugins/textinsert/lang/pt-br/lang.php
new file mode 100644
index 0000000..874bb9d
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/pt-br/lang.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Alexandre Belchior <alexbelchior@gmail.com>
+ */
+$lang['menu'] = 'Plugin de Substituição de Macro TextInsert';
+$lang['btn_add'] = 'Adicionar';
+$lang['btn_del'] = 'Delete';
+$lang['btn_edit'] = 'Enviar edições';
+$lang['label_list'] = 'Lista de Macro';
+$lang['label_add'] = 'Adicionar macros';
+$lang['label_del'] = 'Deletar macros';
+$lang['label_edit'] = 'Editar macros';
+$lang['col_subst'] = 'Substituição de macro';
+$lang['add_macros'] = 'Adicionar macros';
+$lang['delete_macros'] = 'Deletar macros';
+$lang['edit_macros'] = 'Editar macros';
+$lang['view_macros'] = 'Visualizar lista de macro';
+$lang['hide_macros'] = 'Ocultar lista de macro';
+$lang['hide_info'] = 'Fechar informação';
+$lang['show_info'] = 'Mostrar informação';
diff --git a/platform/www/lib/plugins/textinsert/lang/pt-br/settings.php b/platform/www/lib/plugins/textinsert/lang/pt-br/settings.php
new file mode 100644
index 0000000..db02fd6
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/pt-br/settings.php
@@ -0,0 +1,9 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Alexandre Belchior <alexbelchior@gmail.com>
+ */
+$lang['stdreplace'] = 'Aceite substituições de macro padrão como usadas em modelos de namespace';
+$lang['farm'] = 'Se isto é uma fazenda e você e gostaria que cada animal tivesse seu próprio banco de dados de macros, selecione essa opção; caso contrário, todos os animais compartilharão o mesmo banco de dados (ou seja, do fazendeiro).';
diff --git a/platform/www/lib/plugins/textinsert/lang/ru/intro.txt b/platform/www/lib/plugins/textinsert/lang/ru/intro.txt
new file mode 100644
index 0000000..e432c6b
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/ru/intro.txt
@@ -0,0 +1,10 @@
+Панель позволяет добавлять, редактировать и удалять макросы и их подстановки. Имена макросов могут содержать буквы, цифры, символ подчёркивания, дефис и точку. Пример: ''Macro_one.txt''. Подстановки неограничены в длине и могут содержать HTML-разметку.
+
+В режиме //добавления// можно создать до шести макросов. Шестое текстовое поле предназначено для длинных текстов. В колонке **Macro** введите имя нового макроса, а в колонке **Подстановки** --- замещающий текст.
+
+В режиме //удаления// появляются флажки для выбора удаляемых макросов. Разовое количество удалений не ограничено.
+
+В режиме //редактирования// все поля подстановок открываются для исправления. Разовое количество исправлений не ограничено.
+
+Список макросов обновится только после нажатия соответствующей функциональной кнопки внизу списка.
+
diff --git a/platform/www/lib/plugins/textinsert/lang/ru/lang.php b/platform/www/lib/plugins/textinsert/lang/ru/lang.php
new file mode 100644
index 0000000..25e2fe7
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/ru/lang.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Aleksandr Selivanov <alexgearbox@yandex.ru>
+ * @author RainbowSpike <1@2.ru>
+ */
+$lang['menu'] = 'Плагин макрозамен TextInsert';
+$lang['btn_add'] = 'Добавить';
+$lang['btn_del'] = 'Удалить';
+$lang['btn_edit'] = 'Подтвердить изменения';
+$lang['label_list'] = 'Список макросов';
+$lang['label_add'] = 'Добавить макросы';
+$lang['label_del'] = 'Удалить макросы';
+$lang['label_edit'] = 'Изменить макросы';
+$lang['col_subst'] = 'Подстановки';
+$lang['add_macros'] = 'Добавить макросы';
+$lang['delete_macros'] = 'Удалить макросы';
+$lang['edit_macros'] = 'Изменить макросы';
+$lang['view_macros'] = 'Показать список макросов';
+$lang['hide_macros'] = 'Скрыть список макросов';
+$lang['hide_info'] = 'Скрыть справку';
+$lang['show_info'] = 'Показать справку';
+$lang['not_found'] = 'Макрос не найден в перечне макросов';
diff --git a/platform/www/lib/plugins/textinsert/lang/ru/settings.php b/platform/www/lib/plugins/textinsert/lang/ru/settings.php
new file mode 100644
index 0000000..07a8850
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/ru/settings.php
@@ -0,0 +1,9 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author RainbowSpike <1@2.ru>
+ * @author Aleksandr Selivanov <alexgearbox@yandex.ru>
+ */
+$lang['stdreplace'] = 'Допускать стандартные переменные, используемые для шаблонов пространств имён';
diff --git a/platform/www/lib/plugins/textinsert/lang/zh/intro.txt b/platform/www/lib/plugins/textinsert/lang/zh/intro.txt
new file mode 100644
index 0000000..8846119
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/zh/intro.txt
@@ -0,0 +1,11 @@
+通过此面板,您可以添加、删除宏及其替换文本,保存后也可以在次对其进行编辑。 宏名称可以包含字母,数字,下划线,连字符和句号。 例如:''Macro_one.txt''。替换文本支持 HTML,且不限制长度。
+
+您一次最多可以添加六个宏。 第六个是允许扩展文本的文本区域。
+在**宏**列表中输入宏的名称,并在另一边中输入它们的**替换**文本。
+
+
+选中要删除的宏旁边的框,然后单击屏幕底部的**删除**按钮,即可删除宏。
+编辑是通过编辑界面完成的,这个界面会显示你所有的宏。您可以编辑任意数量的宏的内容。
+
+
+在您通过屏幕底部的相应按钮提交编辑、添加或删除之后,宏列表才会刷新。 \ No newline at end of file
diff --git a/platform/www/lib/plugins/textinsert/lang/zh/lang.php b/platform/www/lib/plugins/textinsert/lang/zh/lang.php
new file mode 100644
index 0000000..09d578a
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/lang/zh/lang.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author RainSlide <RainSlide@outlook.com>
+ */
+$lang['menu'] = 'TextInsert 宏插件';
+$lang['btn_add'] = '添加';
+$lang['btn_del'] = '删除';
+$lang['btn_edit'] = '提交编辑';
+$lang['label_list'] = '宏列表';
+$lang['label_add'] = '添加宏';
+$lang['label_del'] = '删除宏';
+$lang['label_edit'] = '编辑宏';
+$lang['col_subst'] = '替换文本';
+$lang['add_macros'] = '添加宏';
+$lang['delete_macros'] = '删除宏';
+$lang['edit_macros'] = '编辑宏';
+$lang['view_macros'] = '显示宏列表';
+$lang['hide_macros'] = '隐藏宏列表';
+$lang['hide_info'] = '关闭信息';
+$lang['show_info'] = '显示信息';
diff --git a/platform/www/lib/plugins/textinsert/manager.dat b/platform/www/lib/plugins/textinsert/manager.dat
new file mode 100644
index 0000000..a12c912
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/manager.dat
@@ -0,0 +1,2 @@
+downloadurl=https://github.com/turnermm/TextInsert/zipball/master
+installed=Thu, 29 Apr 2021 06:46:29 -0300
diff --git a/platform/www/lib/plugins/textinsert/plugin.info.txt b/platform/www/lib/plugins/textinsert/plugin.info.txt
new file mode 100644
index 0000000..ae031d0
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/plugin.info.txt
@@ -0,0 +1,9 @@
+base textinsert
+author Myron Turner
+email turnermm02@shaw.ca
+date 2019-10-03
+name textinsert Plugin
+desc replace macros with text subsitutions
+url https://www.dokuwiki.org/plugin:textinsert
+
+
diff --git a/platform/www/lib/plugins/textinsert/syntax.php b/platform/www/lib/plugins/textinsert/syntax.php
new file mode 100644
index 0000000..82a60b6
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/syntax.php
@@ -0,0 +1,261 @@
+<?php
+/**
+ *
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Myron Turner <turnermm02@shaw.ca>
+ *
+ */
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+define('REPLACE_DIR', DOKU_INC . 'data/meta/macros/');
+define('MACROS_FILE', REPLACE_DIR . 'macros.ser');
+
+
+/**
+ * All DokuWiki plugins to extend the parser/rendering mechanism
+ * need to inherit from this class
+ */
+class syntax_plugin_textinsert extends DokuWiki_Syntax_Plugin {
+ var $macros;
+ var $translations;
+ var $ns;
+ /**
+ * return some info
+ */
+
+ /**
+ * What kind of syntax are we?
+ */
+ function getType(){
+ return 'substition';
+ }
+
+ /**
+ * Where to sort in?
+ */
+ function getSort(){
+ return 155;
+ }
+
+
+ /**
+ * Connect pattern to lexer
+ */
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern('#@\!?[\w\-\._]+\!?@#',$mode,'plugin_textinsert');
+ $this->Lexer->addSpecialPattern('#@\!\![\w\-\._]+@#',$mode,'plugin_textinsert');
+ $this->Lexer->addSpecialPattern('#@[\w\-\._]+~.*?~@#',$mode,'plugin_textinsert');
+ $this->Lexer->addSpecialPattern('#@[\w\-\._]+[\r\n]+~[^\r\n]+~@#',$mode,'plugin_textinsert');
+ }
+
+
+ /**
+ * Handle the match
+ */
+ function handle($match, $state, $pos, Doku_Handler $handler){
+
+ $html=false;
+ $translation = false;
+ $match = substr($match,2,-2);
+ $match = trim($match);
+ if(strpos($match, 'HTML')) $html=true;
+ if(strpos($match, 'LANG_') !== false) {
+ $translation=true;
+ list($prefix,$trans) = explode('_',$match,2);
+ }
+
+ global $ID;
+ list($ns,$rest) = explode(':',$ID,2);
+ if(@file_exists($filename = DOKU_PLUGIN . "textinsert/lang/$ns/lang.php")) {
+ include $filename;
+ $this->translations = $lang;
+
+ }
+
+ if(@file_exists($filename = DOKU_PLUGIN . "textinsert/lang/$ns/macros.php")) {
+ include $filename;
+ $ar = 'lang_' .$ns;
+ $tr = $$ar;
+ if($this->translations) {
+ $this->translations = array_merge($lang,$tr);
+ }
+ else $this->translations = $tr;
+ }
+
+ if(!empty($ns)) {
+ $this->ns = $ns;
+ }
+ $this->macros = $this->get_macros();
+
+
+
+ while(preg_match('#(\*\*|//|__|\'\').*?\1#m',$match )) {
+ $match = preg_replace_callback(
+ '#(\*\*|//|__|\'\')(.*?)(\1)#',
+ function($matches) {
+ $matches[1] = str_replace(array('**','//','__','\'\'',),array('<b>','<em>','<u>','<code>'),$matches[1]);
+ $matches[3] = str_replace(array('**','//','__','\'\''),array('</b>','</em>','</u>','</code>'),$matches[3]);
+ return $matches[1] . $matches[2] . $matches[3];
+ },$match );
+ }
+
+ if(preg_match('/(.*?)~([\s\S]+)~$/',$match,$subtitution)) {
+ $match=$subtitution[1];
+ $subtitution[2] = str_replace('\\,','&#44;',$subtitution[2]);
+ $substitutions=explode(',',$subtitution[2]);
+ $substitutions = preg_replace('#\/\/.+#',"",$substitutions);
+ $substitutions = preg_replace('#\\\n#',"<br />",$substitutions);
+ }
+
+ if(!array_key_exists($match, $this->macros) ) {
+ $err = $this->getLang('not_found');
+ msg("$match $err", -1);
+ $match = "";
+ }
+ else {
+ if($translation && isset($this->translations[$trans])){
+ $match = $this->translations[$trans];
+ }
+ else {
+ $match =$this->macros[$match];
+ }
+ }
+
+ if(!is_array($substitutions)) $substitutions = array();
+ for($i=0; $i<count($substitutions); $i++) {
+ $search = '%' . ($i+1);
+ $match = str_replace ($search , trim($substitutions[$i]), $match);
+ }
+
+ $match = $this->get_inserts($match,$translation);
+
+ if($html) {
+ $match = str_replace('&lt;','<',$match);
+ $match = str_replace('&gt;','>',$match);
+ }
+
+ return array($state,$match);
+ }
+
+ /**
+ * Create output
+ */
+ function render($mode, Doku_Renderer $renderer, $data) {
+ global $INFO;
+ if($mode == 'xhtml'){
+ list($state, $word) = $data;
+ If(strpos($word,'_ID_') !== false ) {
+ $word = str_replace('_ID_',$INFO['id'], $word);
+ }
+ $renderer->doc .= $word;
+ return true;
+ }
+ return false;
+ }
+
+ function get_macros() {
+ $a = array();
+ if(file_exists(MACROS_FILE)) {
+ $a = unserialize(file_get_contents(MACROS_FILE));
+ }
+ else if($this->getConf('farm')) {
+ $a = unserialize(file_get_contents(metaFN('macros','.ser')));
+ }
+ $r = $this->get_std_replacements() ;
+ $result = array_merge($r,$a);
+ return array_merge($r,$a);
+ }
+
+ function get_inserts($match,$translation) {
+ $inserts = array();
+
+ // replace embedded macros
+ if(preg_match_all('/#@(.*?)@#/',$match,$inserts)) {
+ $keys = $inserts[1];
+ $pats = $inserts[0];
+
+ for($i=0; $i<count($keys); $i++) {
+ $insert = $this->macros[$keys[$i]];
+ if($translation ||strpos($keys[$i], 'LANG_') !== false) {
+ list($prefix,$trans) = explode('_',$keys[$i],2);
+ $_insert = $this->translations[$trans];
+ if($_insert) $insert =$_insert;
+ }
+ $match = str_replace($pats[$i],$insert,$match);
+ }
+
+ } // end replace embedded macros
+
+
+ $entities = getEntities();
+ $e_keys = array_keys($entities);
+ $e_values = array_values($entities);
+ $match = str_replace($e_keys,$e_values,$match);
+
+ return $match;
+ }
+
+ function get_std_replacements() {
+ if(!$this->getConf('stdreplace')) return array();
+ global $conf;
+ global $INFO;
+ global $ID;
+
+ $file = noNS($ID);
+ $page = cleanID($file) ;
+
+ $names =array(
+ 'ID',
+ 'NS',
+ 'FILE',
+ '!FILE',
+ '!FILE!',
+ 'PAGE',
+ '!PAGE',
+ '!!PAGE',
+ '!PAGE!',
+ 'USER',
+ 'DATE',
+ '_ID_'
+ );
+
+ $values = array(
+ $ID,
+ getNS($ID),
+ $file,
+ utf8_ucfirst($file),
+ utf8_strtoupper($file),
+ $page,
+ utf8_ucfirst($page),
+ utf8_ucwords($page),
+ utf8_strtoupper($page),
+ $_SERVER['REMOTE_USER'],
+ strftime($conf['dformat'], time()),
+ '_ID_'
+ );
+ $std_replacements = array();
+ for($i=0; $i<count($names) ; $i++) {
+ $std_replacements[$names[$i]] = $values[$i];
+ }
+
+ return $std_replacements;
+}
+
+ function write_debug($what, $screen = false) {
+ return;
+ $what=print_r($what,true);
+ if($screen) {
+ msg('<pre>' . $what . '</pre>');
+ return;
+ }
+ $handle=fopen("textinsert.txt",'a');
+ fwrite($handle,"$what\n");
+ fclose($handle);
+ }
+}
+
+
diff --git a/platform/www/lib/plugins/textinsert/version b/platform/www/lib/plugins/textinsert/version
new file mode 100644
index 0000000..4fb8186
--- /dev/null
+++ b/platform/www/lib/plugins/textinsert/version
@@ -0,0 +1,2 @@
+19-Oct_03-20_19
+
diff --git a/platform/www/lib/plugins/translation/.travis.yml b/platform/www/lib/plugins/translation/.travis.yml
new file mode 100644
index 0000000..c7eb5a6
--- /dev/null
+++ b/platform/www/lib/plugins/translation/.travis.yml
@@ -0,0 +1,13 @@
+# Config file for travis-ci.org
+
+language: php
+php:
+ - "7.1"
+ - "7.0"
+ - "5.6"
+env:
+ - DOKUWIKI=master
+ - DOKUWIKI=stable
+before_install: wget https://raw.github.com/splitbrain/dokuwiki-travis/master/travis.sh
+install: sh travis.sh
+script: cd _test && phpunit --stderr --group plugin_translation
diff --git a/platform/www/lib/plugins/translation/README b/platform/www/lib/plugins/translation/README
new file mode 100644
index 0000000..1a5b5ef
--- /dev/null
+++ b/platform/www/lib/plugins/translation/README
@@ -0,0 +1,25 @@
+translation Plugin for DokuWiki
+
+All documentation for this plugin can be found at
+http://www.dokuwiki.org/plugin:translation
+
+If you install this plugin manually, make sure it is installed in
+lib/plugins/translation/ - if the folder is called different it
+will not work!
+
+Please refer to http://www.dokuwiki.org/plugins for additional info
+on how to install plugins in DokuWiki.
+
+----
+Copyright (C) Andreas Gohr <andi@splitbrain.org>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; version 2 of the License
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+See the COPYING file in your DokuWiki folder for details
diff --git a/platform/www/lib/plugins/translation/_test/basic.test.php b/platform/www/lib/plugins/translation/_test/basic.test.php
new file mode 100644
index 0000000..870c7a7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/_test/basic.test.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * General tests for the translation plugin
+ *
+ * @group plugin_translation
+ * @group plugins
+ */
+class basic_plugin_translation_test extends DokuWikiTest {
+
+ protected $pluginsEnabled = array('translation');
+
+ public static function buildTransID_testdata() {
+ return array(
+ array(
+ 'en',
+ 'ns:page',
+ 'de es',
+ array(':ns:page', 'en'),
+ ),
+ array(
+ '',
+ 'ns:page',
+ 'de es',
+ array(':ns:page', 'en'),
+ ),
+ array(
+ 'de',
+ 'ns:page',
+ 'de es',
+ array(':de:ns:page', 'de'),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider buildTransID_testdata
+ *
+ * @param $inputLang
+ * @param $inputID
+ * @param $translationsOption
+ * @param $expected
+ */
+ public function test_buildTransID($inputLang, $inputID, $translationsOption, $expected) {
+ global $conf;
+ $conf['plugin']['translation']['translations'] = $translationsOption;
+ /** @var helper_plugin_translation $helper */
+ $helper = plugin_load('helper', 'translation', true);
+
+ $actual_result = $helper->buildTransID($inputLang, $inputID);
+
+ $this->assertEquals($expected, $actual_result);
+ }
+
+
+ public static function redirectStart_testdata() {
+ return array(
+ array(
+ 'start',
+ 'de es',
+ 'de,en-US;q=0.8,en;q=0.5,fr;q=0.3',
+ ':de:start',
+ 'redirect to translated page',
+ ),
+ array(
+ 'start',
+ 'de es',
+ 'en-US,de;q=0.8,en;q=0.5,fr;q=0.3',
+ array(),
+ 'do not redirect if basic namespace is correct lang',
+ ),
+ array(
+ 'de:start',
+ 'en de es',
+ 'en-US,en;q=0.8,fr;q=0.5',
+ array(),
+ 'do not redirect anything other than exactly $conf[\'start\']',
+ ),
+ );
+ }
+
+
+ /**
+ * @dataProvider redirectStart_testdata
+ *
+ * @param $input
+ * @param $translationsOption
+ * @param $httpAcceptHeader
+ * @param $expected
+ */
+ public function test_redirectStart($input, $translationsOption, $httpAcceptHeader, $expected, $msg) {
+ global $conf;
+ $conf['plugin']['translation']['translations'] = $translationsOption;
+ $conf['plugin']['translation']['redirectstart'] = 1;
+
+ /** @var helper_plugin_translation $helper */
+ $helper = plugin_load('helper', 'translation');
+ $helper->loadTranslationNamespaces();
+
+ $request = new TestRequest();
+ $request->setServer('HTTP_ACCEPT_LANGUAGE', $httpAcceptHeader);
+
+ $response = $request->get(array('id' => $input));
+ $actual = $response->getHeader('Location');
+
+ if (is_string($actual)) {
+ list(, $actual) = explode('doku.php?id=', $actual);
+ }
+
+ $this->assertEquals($expected, $actual, $msg);
+ }
+
+}
diff --git a/platform/www/lib/plugins/translation/_test/general.test.php b/platform/www/lib/plugins/translation/_test/general.test.php
new file mode 100644
index 0000000..ec1675c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/_test/general.test.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * General tests for the translation plugin
+ *
+ * @group plugin_translation
+ * @group plugins
+ */
+class general_plugin_translation_test extends DokuWikiTest {
+
+ /**
+ * Simple test to make sure the plugin.info.txt is in correct format
+ */
+ public function test_plugininfo() {
+ $file = __DIR__.'/../plugin.info.txt';
+ $this->assertFileExists($file);
+
+ $info = confToHash($file);
+
+ $this->assertArrayHasKey('base', $info);
+ $this->assertArrayHasKey('author', $info);
+ $this->assertArrayHasKey('email', $info);
+ $this->assertArrayHasKey('date', $info);
+ $this->assertArrayHasKey('name', $info);
+ $this->assertArrayHasKey('desc', $info);
+ $this->assertArrayHasKey('url', $info);
+
+ $this->assertEquals('translation', $info['base']);
+ $this->assertRegExp('/^https?:\/\//', $info['url']);
+ $this->assertTrue(mail_isvalid($info['email']));
+ $this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']);
+ $this->assertTrue(false !== strtotime($info['date']));
+ }
+
+ /**
+ * Test to ensure that every conf['...'] entry in conf/default.php has a corresponding meta['...'] entry in
+ * conf/metadata.php.
+ */
+ public function test_plugin_conf() {
+ $conf_file = __DIR__.'/../conf/default.php';
+ if (file_exists($conf_file)){
+ include($conf_file);
+ }
+ $meta_file = __DIR__.'/../conf/metadata.php';
+ if (file_exists($meta_file)) {
+ include($meta_file);
+ }
+
+ $this->assertEquals(gettype($conf), gettype($meta),'Both ' . DOKU_PLUGIN . 'translation/conf/default.php and ' . DOKU_PLUGIN . 'translation/conf/metadata.php have to exist and contain the same keys.');
+
+ if (gettype($conf) != 'NULL' && gettype($meta) != 'NULL') {
+ foreach($conf as $key => $value) {
+ $this->assertArrayHasKey($key, $meta, 'Key $meta[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'translation/conf/metadata.php');
+ }
+
+ foreach($meta as $key => $value) {
+ $this->assertArrayHasKey($key, $conf, 'Key $conf[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'translation/conf/default.php');
+ }
+ }
+
+ }
+}
diff --git a/platform/www/lib/plugins/translation/action.php b/platform/www/lib/plugins/translation/action.php
new file mode 100644
index 0000000..6c79a70
--- /dev/null
+++ b/platform/www/lib/plugins/translation/action.php
@@ -0,0 +1,302 @@
+<?php
+/**
+ * Translation Plugin: Simple multilanguage plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Guy Brand <gb@isis.u-strasbg.fr>
+ */
+
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Class action_plugin_translation
+ */
+class action_plugin_translation extends DokuWiki_Action_Plugin {
+
+ /**
+ * For the helper plugin
+ * @var helper_plugin_translation
+ */
+ var $helper = null;
+
+ var $locale;
+
+ /**
+ * Constructor. Load helper plugin
+ */
+ function __construct() {
+ $this->helper = plugin_load('helper', 'translation');
+ }
+
+ /**
+ * Registers a callback function for a given event
+ *
+ * @param Doku_Event_Handler $controller
+ */
+ function register(Doku_Event_Handler $controller) {
+ $scriptName = basename($_SERVER['PHP_SELF']);
+
+ // should the lang be applied to UI?
+ if($this->getConf('translateui')) {
+ switch($scriptName) {
+ case 'js.php':
+ $controller->register_hook('INIT_LANG_LOAD', 'BEFORE', $this, 'translation_js');
+ $controller->register_hook('JS_CACHE_USE', 'BEFORE', $this, 'translation_jscache');
+ break;
+
+ case 'ajax.php':
+ $controller->register_hook('INIT_LANG_LOAD', 'BEFORE', $this, 'translate_media_manager');
+ break;
+
+ case 'mediamanager.php':
+ $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'setJsCacheKey');
+ break;
+
+ default:
+ $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'setJsCacheKey');
+ }
+ }
+
+ if($scriptName !== 'js.php' && $scriptName !== 'ajax.php') {
+ $controller->register_hook('DOKUWIKI_STARTED', 'BEFORE', $this, 'translation_hook');
+ $controller->register_hook('DETAIL_STARTED', 'BEFORE', $this, 'translation_hook');
+ $controller->register_hook('MEDIAMANAGER_STARTED', 'BEFORE', $this, 'translation_hook');
+ }
+
+ $controller->register_hook('SEARCH_QUERY_PAGELOOKUP', 'AFTER', $this, 'translation_search');
+ $controller->register_hook('COMMON_PAGETPL_LOAD', 'AFTER', $this, 'page_template_replacement');
+ }
+
+ /**
+ * Hook Callback. Make current language available as page template placeholder and handle
+ * original language copying
+ *
+ * @param Doku_Event $event
+ * @param $args
+ */
+ function page_template_replacement(Doku_Event $event, $args) {
+ global $ID;
+
+ // load orginal content as template?
+ if($this->getConf('copytrans') && $this->helper->istranslatable($ID, false)) {
+ // look for existing translations
+ $translations = $this->helper->getAvailableTranslations($ID);
+ if($translations) {
+ // find original language (might've been provided via parameter or use first translation)
+ $orig = (string) $_REQUEST['fromlang'];
+ if(!$orig) $orig = array_shift(array_keys($translations));
+
+ // load file
+ $origfile = $translations[$orig];
+ $event->data['tpl'] = io_readFile(wikiFN($origfile));
+
+ // prefix with warning
+ $warn = io_readFile($this->localFN('totranslate'));
+ if($warn) $warn .= "\n\n";
+ $event->data['tpl'] = $warn . $event->data['tpl'];
+
+ // show user a choice of translations if any
+ if(count($translations) > 1) {
+ $links = array();
+ foreach($translations as $t => $l) {
+ $links[] = '<a href="' . wl($ID, array('do' => 'edit', 'fromlang' => $t)) . '">' . $this->helper->getLocalName($t) . '</a>';
+ }
+
+ msg(
+ sprintf(
+ $this->getLang('transloaded'),
+ $this->helper->getLocalName($orig),
+ join(', ', $links)
+ )
+ );
+ }
+
+ }
+ }
+
+ // apply placeholders
+ $event->data['tpl'] = str_replace('@LANG@', $this->helper->realLC(''), $event->data['tpl']);
+ $event->data['tpl'] = str_replace('@TRANS@', $this->helper->getLangPart($ID), $event->data['tpl']);
+ }
+
+ /**
+ * Hook Callback. Load correct translation when loading JavaScript
+ *
+ * @param Doku_Event $event
+ * @param $args
+ */
+ function translation_js(Doku_Event $event, $args) {
+ global $conf;
+ if(!isset($_GET['lang'])) return;
+ if(!in_array($_GET['lang'], $this->helper->translations)) return;
+ $lang = $_GET['lang'];
+ $event->data = $lang;
+ $conf['lang'] = $lang;
+ }
+
+ /**
+ * Hook Callback. Pass language code to JavaScript dispatcher
+ *
+ * @param Doku_Event $event
+ * @param $args
+ * @return bool
+ */
+ function setJsCacheKey(Doku_Event $event, $args) {
+ if(!isset($this->locale)) return false;
+ $count = count($event->data['script']);
+ for($i = 0; $i < $count; $i++) {
+ if(strpos($event->data['script'][$i]['src'], '/lib/exe/js.php') !== false) {
+ $event->data['script'][$i]['src'] .= '&lang=' . hsc($this->locale);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Hook Callback. Make sure the JavaScript is translation dependent
+ *
+ * @param Doku_Event $event
+ * @param $args
+ */
+ function translation_jscache(Doku_Event $event, $args) {
+ if(!isset($_GET['lang'])) return;
+ if(!in_array($_GET['lang'], $this->helper->translations)) return;
+
+ $lang = $_GET['lang'];
+ // reuse the constructor to reinitialize the cache key
+ if(method_exists($event->data, '__construct')) {
+ // New PHP 5 style constructor
+ $event->data->__construct(
+ $event->data->key . $lang,
+ $event->data->ext
+ );
+ } else {
+ // Old PHP 4 style constructor - deprecated
+ $event->data->cache(
+ $event->data->key . $lang,
+ $event->data->ext
+ );
+ }
+ }
+
+ /**
+ * Hook Callback. Translate the AJAX loaded media manager
+ *
+ * @param Doku_Event $event
+ * @param $args
+ */
+ function translate_media_manager(Doku_Event $event, $args) {
+ global $conf;
+ if(isset($_REQUEST['ID'])) {
+ $id = getID();
+ $lc = $this->helper->getLangPart($id);
+ } elseif(isset($_SESSION[DOKU_COOKIE]['translationlc'])) {
+ $lc = $_SESSION[DOKU_COOKIE]['translationlc'];
+ } else {
+ return;
+ }
+ if(!$lc) return;
+
+ $conf['lang'] = $lc;
+ $event->data = $lc;
+ }
+
+ /**
+ * Hook Callback. Change the UI language in foreign language namespaces
+ *
+ * @param Doku_Event $event
+ * @param $args
+ * @return bool
+ */
+ function translation_hook(Doku_Event $event, $args) {
+ global $ID;
+ /** @noinspection PhpUnusedLocalVariableInspection we include the language file later on */
+ global $lang;
+ global $conf;
+ global $ACT;
+ // redirect away from start page?
+ if($this->getConf('redirectstart') && $ID == $conf['start'] && $ACT == 'show') {
+ $lc = $this->helper->getBrowserLang();
+
+ list($translatedStartpage,) = $this->helper->buildTransID($lc, $conf['start']);
+ if (cleanID($translatedStartpage) !== cleanID($ID)) {
+ send_redirect(wl($translatedStartpage, '', true));
+ }
+ }
+
+ // check if we are in a foreign language namespace
+ $lc = $this->helper->getLangPart($ID);
+
+ // store language in session (for page related views only)
+ if(in_array($ACT, array('show', 'recent', 'diff', 'edit', 'preview', 'source', 'subscribe'))) {
+ $_SESSION[DOKU_COOKIE]['translationlc'] = $lc;
+ }
+ if(!$lc) $lc = $_SESSION[DOKU_COOKIE]['translationlc'];
+ if(!$lc) return false;
+ $this->locale = $lc;
+
+ if(!$this->getConf('translateui')) {
+ return true;
+ }
+
+ if(file_exists(DOKU_INC . 'inc/lang/' . $lc . '/lang.php')) {
+ require(DOKU_INC . 'inc/lang/' . $lc . '/lang.php');
+ }
+ $conf['lang_before_translation'] = $conf['lang']; //store for later access in syntax plugin
+ $conf['lang'] = $lc;
+
+ return true;
+ }
+
+ /**
+ * Hook Callback. Resort page match results so that results are ordered by translation, having the
+ * default language first
+ *
+ * @param Doku_Event $event
+ * @param $args
+ */
+ function translation_search(Doku_Event $event, $args) {
+
+ if($event->data['has_titles']) {
+ // sort into translation slots
+ $res = array();
+ foreach($event->result as $r => $t) {
+ $tr = $this->helper->getLangPart($r);
+ if(!is_array($res["x$tr"])) $res["x$tr"] = array();
+ $res["x$tr"][] = array($r, $t);
+ }
+ // sort by translations
+ ksort($res);
+ // combine
+ $event->result = array();
+ foreach($res as $r) {
+ foreach($r as $l) {
+ $event->result[$l[0]] = $l[1];
+ }
+ }
+ } else {
+ # legacy support for old DokuWiki hooks
+
+ // sort into translation slots
+ $res = array();
+ foreach($event->result as $r) {
+ $tr = $this->helper->getLangPart($r);
+ if(!is_array($res["x$tr"])) $res["x$tr"] = array();
+ $res["x$tr"][] = $r;
+ }
+ // sort by translations
+ ksort($res);
+ // combine
+ $event->result = array();
+ foreach($res as $r) {
+ $event->result = array_merge($event->result, $r);
+ }
+ }
+ }
+
+}
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/translation/admin.php b/platform/www/lib/plugins/translation/admin.php
new file mode 100644
index 0000000..f8d95a1
--- /dev/null
+++ b/platform/www/lib/plugins/translation/admin.php
@@ -0,0 +1,101 @@
+<?php
+
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+class admin_plugin_translation extends DokuWiki_Admin_Plugin {
+ function forAdminOnly() {
+ return false;
+ }
+
+ function handle() {
+ }
+
+ function html() {
+
+ /** @var helper_plugin_translation $helper */
+ $helper = plugin_load('helper', "translation");
+ $default_language = $helper->defaultlang;
+
+ /** @var Doku_Renderer_xhtml $xhtml_renderer */
+ $xhtml_renderer = p_get_renderer('xhtml');
+
+ echo "<h1>" . $this->getLang("menu") . "</h1>";
+ echo "<table id='outdated_translations' class=\"inline\">";
+ echo "<tr><th>default: $default_language</th>";
+ if ($this->getConf('show_path')) {
+ echo "<th>" . $this->getLang('path') . "</th>";
+ }
+ foreach ($helper->translations as $t) {
+ if($t === $default_language) {
+ continue;
+ }
+ echo "<th>$t</th>";
+ }
+ echo "</tr>";
+
+ $pages = $this->getAllPages();
+ foreach ($pages as $page) {
+ if (!$helper->getLangPart($page["id"]) === $default_language ||
+ !$helper->istranslatable($page["id"], false) ||
+ !page_exists($page["id"])
+ ) {
+ continue;
+ }
+ // We have an existing and translatable page in the default language
+ $showRow = false;
+ $row = "<tr><td>" . $xhtml_renderer->internallink($page['id'],null,null,true) . "</td>";
+ if ($this->getConf('show_path')) {
+ $row .= "<td>" . $xhtml_renderer->internallink($page['id'],$page['id'],null,true) . "</td>";
+ }
+
+ list($lc, $idpart) = $helper->getTransParts($page["id"]);
+
+ foreach ($helper->translations as $t) {
+ if ($t === $default_language) {
+ continue;
+ }
+
+ list($translID, $name) = $helper->buildTransID($t, $idpart);
+
+
+ $difflink = '';
+ if(!page_exists($translID)) {
+ $class = "missing";
+ $title = $this->getLang("missing");
+ $showRow = true;
+ } else {
+ $translfn = wikiFN($translID);
+ if($page['mtime'] > filemtime($translfn)) {
+ $class = "outdated";
+ $difflink = " <a href='";
+ $difflink .= $helper->getOldDiffLink($page["id"], $page['mtime']);
+ $difflink .= "'>(diff)</a>";
+ $title = $this->getLang('old');
+ $showRow = true;
+ } else {
+ $class = "current";
+ $title = $this->getLang('current');
+ }
+ }
+ $row .= "<td class='$class'>" . $xhtml_renderer->internallink($translID,$title,null,true) . $difflink . "</td>";
+ }
+ $row .= "</tr>";
+
+ if ($showRow) {
+ echo $row;
+ }
+
+ }
+ echo "</table>";
+
+ }
+
+ function getAllPages() {
+ $namespace = $this->getConf("translationns");
+ $dir = dirname(wikiFN("$namespace:foo"));
+ $pages = array();
+ search($pages, $dir, 'search_allpages',array());
+ return $pages;
+ }
+}
diff --git a/platform/www/lib/plugins/translation/admin.svg b/platform/www/lib/plugins/translation/admin.svg
new file mode 100644
index 0000000..c3d51ee
--- /dev/null
+++ b/platform/www/lib/plugins/translation/admin.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12.87 15.07l-2.54-2.51.03-.03A17.52 17.52 0 0 0 14.07 6H17V4h-7V2H8v2H1v2h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04M18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12m-2.62 7l1.62-4.33L19.12 17h-3.24z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/conf/default.php b/platform/www/lib/plugins/translation/conf/default.php
new file mode 100644
index 0000000..3a20131
--- /dev/null
+++ b/platform/www/lib/plugins/translation/conf/default.php
@@ -0,0 +1,19 @@
+<?php
+/**
+ * Default options for the translation plugin
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+$conf['translations'] = '';
+$conf['translationns'] = '';
+$conf['skiptrans'] = '';
+$conf['dropdown'] = 0;
+$conf['translateui'] = 0;
+$conf['redirectstart'] = 0;
+$conf['checkage'] = 0;
+$conf['about'] = '';
+$conf['localabout'] = 0;
+$conf['display'] = 'langcode,title';
+$conf['copytrans'] = 0;
+$conf['show_path'] = 1;
diff --git a/platform/www/lib/plugins/translation/conf/metadata.php b/platform/www/lib/plugins/translation/conf/metadata.php
new file mode 100644
index 0000000..d9c4d22
--- /dev/null
+++ b/platform/www/lib/plugins/translation/conf/metadata.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * Options for the translation plugin
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+$meta['translations'] = array('string','_pattern' => '/^(|[a-zA-Z\- ,]+)$/');
+$meta['translationns'] = array('string','_pattern' => '/^(|[\w:\-]+)$/');
+$meta['skiptrans'] = array('string');
+$meta['dropdown'] = array('onoff');
+$meta['display'] = array('multicheckbox',
+ '_choices' => array('langcode','name','flag','title','twolines'));
+$meta['translateui'] = array('onoff');
+$meta['redirectstart'] = array('onoff');
+$meta['checkage'] = array('onoff');
+$meta['about'] = array('string','_pattern' => '/^(|[\w:\-]+)$/');
+$meta['localabout'] = array('onoff');
+$meta['copytrans'] = array('onoff');
+$meta['show_path'] = array('onoff');
+
diff --git a/platform/www/lib/plugins/translation/flags/af.gif b/platform/www/lib/plugins/translation/flags/af.gif
new file mode 100644
index 0000000..9889408
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/af.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/ar.gif b/platform/www/lib/plugins/translation/flags/ar.gif
new file mode 100644
index 0000000..179961b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/ar.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/da.gif b/platform/www/lib/plugins/translation/flags/da.gif
new file mode 100644
index 0000000..03e75bd
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/da.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/de.gif b/platform/www/lib/plugins/translation/flags/de.gif
new file mode 100644
index 0000000..75728dd
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/de.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/el.gif b/platform/www/lib/plugins/translation/flags/el.gif
new file mode 100644
index 0000000..b4c8c04
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/el.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/en.gif b/platform/www/lib/plugins/translation/flags/en.gif
new file mode 100644
index 0000000..3c6bce1
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/en.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/es.gif b/platform/www/lib/plugins/translation/flags/es.gif
new file mode 100644
index 0000000..c27d65e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/es.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/et.gif b/platform/www/lib/plugins/translation/flags/et.gif
new file mode 100644
index 0000000..9397a2d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/et.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/fa.gif b/platform/www/lib/plugins/translation/flags/fa.gif
new file mode 100644
index 0000000..156040f
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/fa.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/fr.gif b/platform/www/lib/plugins/translation/flags/fr.gif
new file mode 100644
index 0000000..43d0b80
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/fr.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/ga.gif b/platform/www/lib/plugins/translation/flags/ga.gif
new file mode 100644
index 0000000..506ad28
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/ga.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/he.gif b/platform/www/lib/plugins/translation/flags/he.gif
new file mode 100644
index 0000000..c8483ae
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/he.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/hu.gif b/platform/www/lib/plugins/translation/flags/hu.gif
new file mode 100644
index 0000000..6142d86
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/hu.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/it.gif b/platform/www/lib/plugins/translation/flags/it.gif
new file mode 100644
index 0000000..d79e90e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/it.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/ja.gif b/platform/www/lib/plugins/translation/flags/ja.gif
new file mode 100644
index 0000000..444c1d0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/ja.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/ko.gif b/platform/www/lib/plugins/translation/flags/ko.gif
new file mode 100644
index 0000000..1cddbe7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/ko.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ad.gif b/platform/www/lib/plugins/translation/flags/more/ad.gif
new file mode 100644
index 0000000..57b4997
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ad.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ae.gif b/platform/www/lib/plugins/translation/flags/more/ae.gif
new file mode 100644
index 0000000..78d15b6
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ae.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ag.gif b/platform/www/lib/plugins/translation/flags/more/ag.gif
new file mode 100644
index 0000000..48f8e7b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ag.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ai.gif b/platform/www/lib/plugins/translation/flags/more/ai.gif
new file mode 100644
index 0000000..1cbc579
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ai.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/al.gif b/platform/www/lib/plugins/translation/flags/more/al.gif
new file mode 100644
index 0000000..c44fe0a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/al.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/am.gif b/platform/www/lib/plugins/translation/flags/more/am.gif
new file mode 100644
index 0000000..2915e30
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/am.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/an.gif b/platform/www/lib/plugins/translation/flags/more/an.gif
new file mode 100644
index 0000000..cb570c6
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/an.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ao.gif b/platform/www/lib/plugins/translation/flags/more/ao.gif
new file mode 100644
index 0000000..8c854fa
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ao.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ar.gif b/platform/www/lib/plugins/translation/flags/more/ar.gif
new file mode 100644
index 0000000..a9f71f7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ar.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/as.gif b/platform/www/lib/plugins/translation/flags/more/as.gif
new file mode 100644
index 0000000..d776ec2
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/as.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/at.gif b/platform/www/lib/plugins/translation/flags/more/at.gif
new file mode 100644
index 0000000..87e1217
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/at.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/au.gif b/platform/www/lib/plugins/translation/flags/more/au.gif
new file mode 100644
index 0000000..5269c6a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/au.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/aw.gif b/platform/www/lib/plugins/translation/flags/more/aw.gif
new file mode 100644
index 0000000..27fdb4d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/aw.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ax.gif b/platform/www/lib/plugins/translation/flags/more/ax.gif
new file mode 100644
index 0000000..0ceb684
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ax.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/az.gif b/platform/www/lib/plugins/translation/flags/more/az.gif
new file mode 100644
index 0000000..d771618
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/az.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ba.gif b/platform/www/lib/plugins/translation/flags/more/ba.gif
new file mode 100644
index 0000000..9bf5f0a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ba.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bb.gif b/platform/www/lib/plugins/translation/flags/more/bb.gif
new file mode 100644
index 0000000..b7d08e5
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bb.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bd.gif b/platform/www/lib/plugins/translation/flags/more/bd.gif
new file mode 100644
index 0000000..0fd27ec
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bd.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/be.gif b/platform/www/lib/plugins/translation/flags/more/be.gif
new file mode 100644
index 0000000..ae09bfb
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/be.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bf.gif b/platform/www/lib/plugins/translation/flags/more/bf.gif
new file mode 100644
index 0000000..9d6772c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bf.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bg.gif b/platform/www/lib/plugins/translation/flags/more/bg.gif
new file mode 100644
index 0000000..11cf8ff
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bg.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bh.gif b/platform/www/lib/plugins/translation/flags/more/bh.gif
new file mode 100644
index 0000000..56aa72b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bh.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bi.gif b/platform/www/lib/plugins/translation/flags/more/bi.gif
new file mode 100644
index 0000000..6e2cbe1
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bi.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bj.gif b/platform/www/lib/plugins/translation/flags/more/bj.gif
new file mode 100644
index 0000000..e676116
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bj.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/blankflag.gif b/platform/www/lib/plugins/translation/flags/more/blankflag.gif
new file mode 100644
index 0000000..9935f82
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/blankflag.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bm.gif b/platform/www/lib/plugins/translation/flags/more/bm.gif
new file mode 100644
index 0000000..9feb87b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bm.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bn.gif b/platform/www/lib/plugins/translation/flags/more/bn.gif
new file mode 100644
index 0000000..b7b6b0f
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bn.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bo.gif b/platform/www/lib/plugins/translation/flags/more/bo.gif
new file mode 100644
index 0000000..4844f85
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bo.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bs.gif b/platform/www/lib/plugins/translation/flags/more/bs.gif
new file mode 100644
index 0000000..c0a741e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bs.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bt.gif b/platform/www/lib/plugins/translation/flags/more/bt.gif
new file mode 100644
index 0000000..abe2f3c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bt.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bv.gif b/platform/www/lib/plugins/translation/flags/more/bv.gif
new file mode 100644
index 0000000..6202d1f
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bv.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bw.gif b/platform/www/lib/plugins/translation/flags/more/bw.gif
new file mode 100644
index 0000000..986ab63
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bw.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/by.gif b/platform/www/lib/plugins/translation/flags/more/by.gif
new file mode 100644
index 0000000..43ffcd4
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/by.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/bz.gif b/platform/www/lib/plugins/translation/flags/more/bz.gif
new file mode 100644
index 0000000..791737f
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/bz.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ca.gif b/platform/www/lib/plugins/translation/flags/more/ca.gif
new file mode 100644
index 0000000..457d966
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ca.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/catalonia.gif b/platform/www/lib/plugins/translation/flags/more/catalonia.gif
new file mode 100644
index 0000000..73df9a0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/catalonia.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cc.gif b/platform/www/lib/plugins/translation/flags/more/cc.gif
new file mode 100644
index 0000000..3f78327
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cc.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cd.gif b/platform/www/lib/plugins/translation/flags/more/cd.gif
new file mode 100644
index 0000000..1df717a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cd.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cf.gif b/platform/www/lib/plugins/translation/flags/more/cf.gif
new file mode 100644
index 0000000..35787ca
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cf.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cg.gif b/platform/www/lib/plugins/translation/flags/more/cg.gif
new file mode 100644
index 0000000..e0a62a5
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cg.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ch.gif b/platform/www/lib/plugins/translation/flags/more/ch.gif
new file mode 100644
index 0000000..d5c0e5b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ch.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ci.gif b/platform/www/lib/plugins/translation/flags/more/ci.gif
new file mode 100644
index 0000000..844120a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ci.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ck.gif b/platform/www/lib/plugins/translation/flags/more/ck.gif
new file mode 100644
index 0000000..2edb739
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ck.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cl.gif b/platform/www/lib/plugins/translation/flags/more/cl.gif
new file mode 100644
index 0000000..cbc370e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cl.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cm.gif b/platform/www/lib/plugins/translation/flags/more/cm.gif
new file mode 100644
index 0000000..1fb102b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cm.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/co.gif b/platform/www/lib/plugins/translation/flags/more/co.gif
new file mode 100644
index 0000000..d0e15ca
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/co.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cr.gif b/platform/www/lib/plugins/translation/flags/more/cr.gif
new file mode 100644
index 0000000..0728dd6
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cr.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cs.gif b/platform/www/lib/plugins/translation/flags/more/cs.gif
new file mode 100644
index 0000000..101db64
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cs.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cu.gif b/platform/www/lib/plugins/translation/flags/more/cu.gif
new file mode 100644
index 0000000..291255c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cu.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cv.gif b/platform/www/lib/plugins/translation/flags/more/cv.gif
new file mode 100644
index 0000000..43c6c6c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cv.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cx.gif b/platform/www/lib/plugins/translation/flags/more/cx.gif
new file mode 100644
index 0000000..a5b4308
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cx.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cy.gif b/platform/www/lib/plugins/translation/flags/more/cy.gif
new file mode 100644
index 0000000..35c661e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cy.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/cz.gif b/platform/www/lib/plugins/translation/flags/more/cz.gif
new file mode 100644
index 0000000..0a605e5
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/cz.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/dj.gif b/platform/www/lib/plugins/translation/flags/more/dj.gif
new file mode 100644
index 0000000..212406d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/dj.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/dm.gif b/platform/www/lib/plugins/translation/flags/more/dm.gif
new file mode 100644
index 0000000..2f87f3c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/dm.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/do.gif b/platform/www/lib/plugins/translation/flags/more/do.gif
new file mode 100644
index 0000000..f7d0bad
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/do.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/dz.gif b/platform/www/lib/plugins/translation/flags/more/dz.gif
new file mode 100644
index 0000000..ed580a7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/dz.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ec.gif b/platform/www/lib/plugins/translation/flags/more/ec.gif
new file mode 100644
index 0000000..9e41e0e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ec.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/eg.gif b/platform/www/lib/plugins/translation/flags/more/eg.gif
new file mode 100644
index 0000000..6857c7d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/eg.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/eh.gif b/platform/www/lib/plugins/translation/flags/more/eh.gif
new file mode 100644
index 0000000..dd0391c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/eh.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/england.gif b/platform/www/lib/plugins/translation/flags/more/england.gif
new file mode 100644
index 0000000..933a4f0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/england.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/er.gif b/platform/www/lib/plugins/translation/flags/more/er.gif
new file mode 100644
index 0000000..3d4d612
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/er.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/et.gif b/platform/www/lib/plugins/translation/flags/more/et.gif
new file mode 100644
index 0000000..f77995d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/et.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/europeanunion.gif b/platform/www/lib/plugins/translation/flags/more/europeanunion.gif
new file mode 100644
index 0000000..28a762a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/europeanunion.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/fam.gif b/platform/www/lib/plugins/translation/flags/more/fam.gif
new file mode 100644
index 0000000..7d52885
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/fam.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/fi.gif b/platform/www/lib/plugins/translation/flags/more/fi.gif
new file mode 100644
index 0000000..8d3a191
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/fi.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/fj.gif b/platform/www/lib/plugins/translation/flags/more/fj.gif
new file mode 100644
index 0000000..486151c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/fj.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/fk.gif b/platform/www/lib/plugins/translation/flags/more/fk.gif
new file mode 100644
index 0000000..37b5ecf
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/fk.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/fm.gif b/platform/www/lib/plugins/translation/flags/more/fm.gif
new file mode 100644
index 0000000..7f8723b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/fm.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/fo.gif b/platform/www/lib/plugins/translation/flags/more/fo.gif
new file mode 100644
index 0000000..4a90fc0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/fo.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ga.gif b/platform/www/lib/plugins/translation/flags/more/ga.gif
new file mode 100644
index 0000000..23fd5f0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ga.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gd.gif b/platform/www/lib/plugins/translation/flags/more/gd.gif
new file mode 100644
index 0000000..25ea312
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gd.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ge.gif b/platform/www/lib/plugins/translation/flags/more/ge.gif
new file mode 100644
index 0000000..faa7f12
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ge.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gf.gif b/platform/www/lib/plugins/translation/flags/more/gf.gif
new file mode 100644
index 0000000..43d0b80
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gf.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gh.gif b/platform/www/lib/plugins/translation/flags/more/gh.gif
new file mode 100644
index 0000000..273fb7d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gh.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gi.gif b/platform/www/lib/plugins/translation/flags/more/gi.gif
new file mode 100644
index 0000000..7b1984b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gi.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gl.gif b/platform/www/lib/plugins/translation/flags/more/gl.gif
new file mode 100644
index 0000000..ef445be
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gl.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gm.gif b/platform/www/lib/plugins/translation/flags/more/gm.gif
new file mode 100644
index 0000000..6847c5a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gm.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gn.gif b/platform/www/lib/plugins/translation/flags/more/gn.gif
new file mode 100644
index 0000000..a982ac6
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gn.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gp.gif b/platform/www/lib/plugins/translation/flags/more/gp.gif
new file mode 100644
index 0000000..31166db
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gp.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gq.gif b/platform/www/lib/plugins/translation/flags/more/gq.gif
new file mode 100644
index 0000000..8b4e0cc
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gq.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gs.gif b/platform/www/lib/plugins/translation/flags/more/gs.gif
new file mode 100644
index 0000000..ccc96ec
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gs.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gt.gif b/platform/www/lib/plugins/translation/flags/more/gt.gif
new file mode 100644
index 0000000..7e94d1d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gt.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gu.gif b/platform/www/lib/plugins/translation/flags/more/gu.gif
new file mode 100644
index 0000000..eafef68
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gu.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gw.gif b/platform/www/lib/plugins/translation/flags/more/gw.gif
new file mode 100644
index 0000000..55f7571
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gw.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/gy.gif b/platform/www/lib/plugins/translation/flags/more/gy.gif
new file mode 100644
index 0000000..1cb4cd7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/gy.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/hk.gif b/platform/www/lib/plugins/translation/flags/more/hk.gif
new file mode 100644
index 0000000..798af96
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/hk.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/hm.gif b/platform/www/lib/plugins/translation/flags/more/hm.gif
new file mode 100644
index 0000000..5269c6a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/hm.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/hn.gif b/platform/www/lib/plugins/translation/flags/more/hn.gif
new file mode 100644
index 0000000..6c4ffe8
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/hn.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/hr.gif b/platform/www/lib/plugins/translation/flags/more/hr.gif
new file mode 100644
index 0000000..557c660
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/hr.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ht.gif b/platform/www/lib/plugins/translation/flags/more/ht.gif
new file mode 100644
index 0000000..059604a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ht.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/id.gif b/platform/www/lib/plugins/translation/flags/more/id.gif
new file mode 100644
index 0000000..865161b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/id.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/in.gif b/platform/www/lib/plugins/translation/flags/more/in.gif
new file mode 100644
index 0000000..1cd8027
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/in.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/io.gif b/platform/www/lib/plugins/translation/flags/more/io.gif
new file mode 100644
index 0000000..de7e7ab
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/io.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/iq.gif b/platform/www/lib/plugins/translation/flags/more/iq.gif
new file mode 100644
index 0000000..c34fe3c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/iq.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/is.gif b/platform/www/lib/plugins/translation/flags/more/is.gif
new file mode 100644
index 0000000..b42502d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/is.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ja.gif b/platform/www/lib/plugins/translation/flags/more/ja.gif
new file mode 100644
index 0000000..444c1d0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ja.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/jm.gif b/platform/www/lib/plugins/translation/flags/more/jm.gif
new file mode 100644
index 0000000..0bed67c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/jm.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/jo.gif b/platform/www/lib/plugins/translation/flags/more/jo.gif
new file mode 100644
index 0000000..03daf8a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/jo.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ke.gif b/platform/www/lib/plugins/translation/flags/more/ke.gif
new file mode 100644
index 0000000..c2b5d45
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ke.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/kg.gif b/platform/www/lib/plugins/translation/flags/more/kg.gif
new file mode 100644
index 0000000..72a4d41
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/kg.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/kh.gif b/platform/www/lib/plugins/translation/flags/more/kh.gif
new file mode 100644
index 0000000..30a1831
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/kh.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ki.gif b/platform/www/lib/plugins/translation/flags/more/ki.gif
new file mode 100644
index 0000000..4a0751a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ki.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/km.gif b/platform/www/lib/plugins/translation/flags/more/km.gif
new file mode 100644
index 0000000..5859595
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/km.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/kn.gif b/platform/www/lib/plugins/translation/flags/more/kn.gif
new file mode 100644
index 0000000..bb9cc34
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/kn.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ko.gif b/platform/www/lib/plugins/translation/flags/more/ko.gif
new file mode 100644
index 0000000..1cddbe7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ko.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/kp.gif b/platform/www/lib/plugins/translation/flags/more/kp.gif
new file mode 100644
index 0000000..6e0ca09
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/kp.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/kw.gif b/platform/www/lib/plugins/translation/flags/more/kw.gif
new file mode 100644
index 0000000..1efc734
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/kw.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ky.gif b/platform/www/lib/plugins/translation/flags/more/ky.gif
new file mode 100644
index 0000000..d3d02ee
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ky.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/kz.gif b/platform/www/lib/plugins/translation/flags/more/kz.gif
new file mode 100644
index 0000000..24baebe
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/kz.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/la.gif b/platform/www/lib/plugins/translation/flags/more/la.gif
new file mode 100644
index 0000000..d14cf4d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/la.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/lb.gif b/platform/www/lib/plugins/translation/flags/more/lb.gif
new file mode 100644
index 0000000..003d83a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/lb.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/lc.gif b/platform/www/lib/plugins/translation/flags/more/lc.gif
new file mode 100644
index 0000000..f5fe5bf
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/lc.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/li.gif b/platform/www/lib/plugins/translation/flags/more/li.gif
new file mode 100644
index 0000000..713c58e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/li.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/lk.gif b/platform/www/lib/plugins/translation/flags/more/lk.gif
new file mode 100644
index 0000000..1b3ee7f
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/lk.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/lr.gif b/platform/www/lib/plugins/translation/flags/more/lr.gif
new file mode 100644
index 0000000..435af9e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/lr.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ls.gif b/platform/www/lib/plugins/translation/flags/more/ls.gif
new file mode 100644
index 0000000..427ae95
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ls.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/lt.gif b/platform/www/lib/plugins/translation/flags/more/lt.gif
new file mode 100644
index 0000000..dee9c60
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/lt.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/lu.gif b/platform/www/lib/plugins/translation/flags/more/lu.gif
new file mode 100644
index 0000000..7d7293e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/lu.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/lv.gif b/platform/www/lib/plugins/translation/flags/more/lv.gif
new file mode 100644
index 0000000..17e71b7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/lv.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ly.gif b/platform/www/lib/plugins/translation/flags/more/ly.gif
new file mode 100644
index 0000000..a654c30
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ly.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ma.gif b/platform/www/lib/plugins/translation/flags/more/ma.gif
new file mode 100644
index 0000000..fc78411
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ma.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mc.gif b/platform/www/lib/plugins/translation/flags/more/mc.gif
new file mode 100644
index 0000000..02a7c8e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mc.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/md.gif b/platform/www/lib/plugins/translation/flags/more/md.gif
new file mode 100644
index 0000000..e4b8a7e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/md.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/me.gif b/platform/www/lib/plugins/translation/flags/more/me.gif
new file mode 100644
index 0000000..a260453
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/me.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mg.gif b/platform/www/lib/plugins/translation/flags/more/mg.gif
new file mode 100644
index 0000000..a91b577
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mg.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mh.gif b/platform/www/lib/plugins/translation/flags/more/mh.gif
new file mode 100644
index 0000000..92f5f48
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mh.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mk.gif b/platform/www/lib/plugins/translation/flags/more/mk.gif
new file mode 100644
index 0000000..7aeb831
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mk.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ml.gif b/platform/www/lib/plugins/translation/flags/more/ml.gif
new file mode 100644
index 0000000..53d6f49
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ml.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mm.gif b/platform/www/lib/plugins/translation/flags/more/mm.gif
new file mode 100644
index 0000000..9e0a275
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mm.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mn.gif b/platform/www/lib/plugins/translation/flags/more/mn.gif
new file mode 100644
index 0000000..dff8ea5
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mn.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mo.gif b/platform/www/lib/plugins/translation/flags/more/mo.gif
new file mode 100644
index 0000000..66cf5b4
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mo.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mp.gif b/platform/www/lib/plugins/translation/flags/more/mp.gif
new file mode 100644
index 0000000..73b7147
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mp.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mq.gif b/platform/www/lib/plugins/translation/flags/more/mq.gif
new file mode 100644
index 0000000..570bc5d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mq.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mr.gif b/platform/www/lib/plugins/translation/flags/more/mr.gif
new file mode 100644
index 0000000..f52fcf0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mr.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ms.gif b/platform/www/lib/plugins/translation/flags/more/ms.gif
new file mode 100644
index 0000000..5e5a67a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ms.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mt.gif b/platform/www/lib/plugins/translation/flags/more/mt.gif
new file mode 100644
index 0000000..45c709f
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mt.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mu.gif b/platform/www/lib/plugins/translation/flags/more/mu.gif
new file mode 100644
index 0000000..081ab45
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mu.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mv.gif b/platform/www/lib/plugins/translation/flags/more/mv.gif
new file mode 100644
index 0000000..46b6387
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mv.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mw.gif b/platform/www/lib/plugins/translation/flags/more/mw.gif
new file mode 100644
index 0000000..ad045a0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mw.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mx.gif b/platform/www/lib/plugins/translation/flags/more/mx.gif
new file mode 100644
index 0000000..ddc75d0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mx.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/my.gif b/platform/www/lib/plugins/translation/flags/more/my.gif
new file mode 100644
index 0000000..fc7d523
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/my.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/mz.gif b/platform/www/lib/plugins/translation/flags/more/mz.gif
new file mode 100644
index 0000000..7d63508
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/mz.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/na.gif b/platform/www/lib/plugins/translation/flags/more/na.gif
new file mode 100644
index 0000000..c0babe7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/na.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/nc.gif b/platform/www/lib/plugins/translation/flags/more/nc.gif
new file mode 100644
index 0000000..b1e91b9
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/nc.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ne.gif b/platform/www/lib/plugins/translation/flags/more/ne.gif
new file mode 100644
index 0000000..ff4eaf0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ne.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/nf.gif b/platform/www/lib/plugins/translation/flags/more/nf.gif
new file mode 100644
index 0000000..c83424c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/nf.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ng.gif b/platform/www/lib/plugins/translation/flags/more/ng.gif
new file mode 100644
index 0000000..bdde7cb
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ng.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ni.gif b/platform/www/lib/plugins/translation/flags/more/ni.gif
new file mode 100644
index 0000000..d05894d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ni.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/np.gif b/platform/www/lib/plugins/translation/flags/more/np.gif
new file mode 100644
index 0000000..1096893
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/np.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/nr.gif b/platform/www/lib/plugins/translation/flags/more/nr.gif
new file mode 100644
index 0000000..2e4c0c5
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/nr.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/nu.gif b/platform/www/lib/plugins/translation/flags/more/nu.gif
new file mode 100644
index 0000000..618210a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/nu.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/nz.gif b/platform/www/lib/plugins/translation/flags/more/nz.gif
new file mode 100644
index 0000000..028a5dc
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/nz.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/om.gif b/platform/www/lib/plugins/translation/flags/more/om.gif
new file mode 100644
index 0000000..2b8c775
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/om.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/pa.gif b/platform/www/lib/plugins/translation/flags/more/pa.gif
new file mode 100644
index 0000000..d518b2f
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/pa.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/pe.gif b/platform/www/lib/plugins/translation/flags/more/pe.gif
new file mode 100644
index 0000000..3bc7639
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/pe.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/pf.gif b/platform/www/lib/plugins/translation/flags/more/pf.gif
new file mode 100644
index 0000000..849297a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/pf.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/pg.gif b/platform/www/lib/plugins/translation/flags/more/pg.gif
new file mode 100644
index 0000000..2d20b07
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/pg.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ph.gif b/platform/www/lib/plugins/translation/flags/more/ph.gif
new file mode 100644
index 0000000..12b380a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ph.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/pk.gif b/platform/www/lib/plugins/translation/flags/more/pk.gif
new file mode 100644
index 0000000..f3f62c2
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/pk.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/pl.gif b/platform/www/lib/plugins/translation/flags/more/pl.gif
new file mode 100644
index 0000000..bf10646
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/pl.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/pm.gif b/platform/www/lib/plugins/translation/flags/more/pm.gif
new file mode 100644
index 0000000..99bf6fd
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/pm.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/pn.gif b/platform/www/lib/plugins/translation/flags/more/pn.gif
new file mode 100644
index 0000000..4bc86a1
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/pn.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/pr.gif b/platform/www/lib/plugins/translation/flags/more/pr.gif
new file mode 100644
index 0000000..6d5d589
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/pr.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ps.gif b/platform/www/lib/plugins/translation/flags/more/ps.gif
new file mode 100644
index 0000000..6afa3b7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ps.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/pw.gif b/platform/www/lib/plugins/translation/flags/more/pw.gif
new file mode 100644
index 0000000..5854510
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/pw.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/py.gif b/platform/www/lib/plugins/translation/flags/more/py.gif
new file mode 100644
index 0000000..f2e66af
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/py.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/qa.gif b/platform/www/lib/plugins/translation/flags/more/qa.gif
new file mode 100644
index 0000000..2e843ff
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/qa.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/re.gif b/platform/www/lib/plugins/translation/flags/more/re.gif
new file mode 100644
index 0000000..43d0b80
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/re.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/rs.gif b/platform/www/lib/plugins/translation/flags/more/rs.gif
new file mode 100644
index 0000000..3bd1fb2
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/rs.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/rw.gif b/platform/www/lib/plugins/translation/flags/more/rw.gif
new file mode 100644
index 0000000..0d095f7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/rw.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sb.gif b/platform/www/lib/plugins/translation/flags/more/sb.gif
new file mode 100644
index 0000000..8f5ff83
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sb.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sc.gif b/platform/www/lib/plugins/translation/flags/more/sc.gif
new file mode 100644
index 0000000..31b4767
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sc.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/scotland.gif b/platform/www/lib/plugins/translation/flags/more/scotland.gif
new file mode 100644
index 0000000..03f3f1d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/scotland.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sd.gif b/platform/www/lib/plugins/translation/flags/more/sd.gif
new file mode 100644
index 0000000..53ae214
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sd.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sg.gif b/platform/www/lib/plugins/translation/flags/more/sg.gif
new file mode 100644
index 0000000..5663d39
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sg.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sh.gif b/platform/www/lib/plugins/translation/flags/more/sh.gif
new file mode 100644
index 0000000..dcc7f3b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sh.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/si.gif b/platform/www/lib/plugins/translation/flags/more/si.gif
new file mode 100644
index 0000000..23852b5
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/si.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sj.gif b/platform/www/lib/plugins/translation/flags/more/sj.gif
new file mode 100644
index 0000000..6202d1f
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sj.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sk.gif b/platform/www/lib/plugins/translation/flags/more/sk.gif
new file mode 100644
index 0000000..1b3f22b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sk.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sl.gif b/platform/www/lib/plugins/translation/flags/more/sl.gif
new file mode 100644
index 0000000..f0f3492
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sl.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sm.gif b/platform/www/lib/plugins/translation/flags/more/sm.gif
new file mode 100644
index 0000000..04d98de
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sm.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sn.gif b/platform/www/lib/plugins/translation/flags/more/sn.gif
new file mode 100644
index 0000000..6dac870
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sn.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/so.gif b/platform/www/lib/plugins/translation/flags/more/so.gif
new file mode 100644
index 0000000..f196169
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/so.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sr.gif b/platform/www/lib/plugins/translation/flags/more/sr.gif
new file mode 100644
index 0000000..0f7499a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sr.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/st.gif b/platform/www/lib/plugins/translation/flags/more/st.gif
new file mode 100644
index 0000000..4f1e6e0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/st.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sv.gif b/platform/www/lib/plugins/translation/flags/more/sv.gif
new file mode 100644
index 0000000..2d7b159
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sv.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sy.gif b/platform/www/lib/plugins/translation/flags/more/sy.gif
new file mode 100644
index 0000000..dc8bd50
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sy.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/sz.gif b/platform/www/lib/plugins/translation/flags/more/sz.gif
new file mode 100644
index 0000000..f37aaf8
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/sz.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/tc.gif b/platform/www/lib/plugins/translation/flags/more/tc.gif
new file mode 100644
index 0000000..11a8c23
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/tc.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/td.gif b/platform/www/lib/plugins/translation/flags/more/td.gif
new file mode 100644
index 0000000..7aa8a10
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/td.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/tf.gif b/platform/www/lib/plugins/translation/flags/more/tf.gif
new file mode 100644
index 0000000..51a4325
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/tf.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/tg.gif b/platform/www/lib/plugins/translation/flags/more/tg.gif
new file mode 100644
index 0000000..ca6b4e7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/tg.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/tj.gif b/platform/www/lib/plugins/translation/flags/more/tj.gif
new file mode 100644
index 0000000..2fe38d4
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/tj.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/tk.gif b/platform/www/lib/plugins/translation/flags/more/tk.gif
new file mode 100644
index 0000000..3d3a727
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/tk.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/tl.gif b/platform/www/lib/plugins/translation/flags/more/tl.gif
new file mode 100644
index 0000000..df22d58
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/tl.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/tm.gif b/platform/www/lib/plugins/translation/flags/more/tm.gif
new file mode 100644
index 0000000..36d0994
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/tm.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/tn.gif b/platform/www/lib/plugins/translation/flags/more/tn.gif
new file mode 100644
index 0000000..917d428
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/tn.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/to.gif b/platform/www/lib/plugins/translation/flags/more/to.gif
new file mode 100644
index 0000000..d7ed4d1
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/to.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/tt.gif b/platform/www/lib/plugins/translation/flags/more/tt.gif
new file mode 100644
index 0000000..47d3b80
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/tt.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/tv.gif b/platform/www/lib/plugins/translation/flags/more/tv.gif
new file mode 100644
index 0000000..3c33827
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/tv.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/tw.gif b/platform/www/lib/plugins/translation/flags/more/tw.gif
new file mode 100644
index 0000000..cacfd9b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/tw.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/tz.gif b/platform/www/lib/plugins/translation/flags/more/tz.gif
new file mode 100644
index 0000000..82b52ca
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/tz.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ua.gif b/platform/www/lib/plugins/translation/flags/more/ua.gif
new file mode 100644
index 0000000..5d6cd83
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ua.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ug.gif b/platform/www/lib/plugins/translation/flags/more/ug.gif
new file mode 100644
index 0000000..58b731a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ug.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/um.gif b/platform/www/lib/plugins/translation/flags/more/um.gif
new file mode 100644
index 0000000..3b4c848
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/um.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/us.gif b/platform/www/lib/plugins/translation/flags/more/us.gif
new file mode 100644
index 0000000..8f198f7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/us.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/uy.gif b/platform/www/lib/plugins/translation/flags/more/uy.gif
new file mode 100644
index 0000000..12848c7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/uy.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/uz.gif b/platform/www/lib/plugins/translation/flags/more/uz.gif
new file mode 100644
index 0000000..dc9daec
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/uz.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/va.gif b/platform/www/lib/plugins/translation/flags/more/va.gif
new file mode 100644
index 0000000..2bd7446
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/va.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/vc.gif b/platform/www/lib/plugins/translation/flags/more/vc.gif
new file mode 100644
index 0000000..4821381
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/vc.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ve.gif b/platform/www/lib/plugins/translation/flags/more/ve.gif
new file mode 100644
index 0000000..19ce6c1
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ve.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/vg.gif b/platform/www/lib/plugins/translation/flags/more/vg.gif
new file mode 100644
index 0000000..1fc0f96
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/vg.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/vi.gif b/platform/www/lib/plugins/translation/flags/more/vi.gif
new file mode 100644
index 0000000..66f9e74
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/vi.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/vu.gif b/platform/www/lib/plugins/translation/flags/more/vu.gif
new file mode 100644
index 0000000..8a8b2b0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/vu.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/wales.gif b/platform/www/lib/plugins/translation/flags/more/wales.gif
new file mode 100644
index 0000000..901d175
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/wales.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/wf.gif b/platform/www/lib/plugins/translation/flags/more/wf.gif
new file mode 100644
index 0000000..eaa954b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/wf.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ws.gif b/platform/www/lib/plugins/translation/flags/more/ws.gif
new file mode 100644
index 0000000..a51f939
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ws.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/ye.gif b/platform/www/lib/plugins/translation/flags/more/ye.gif
new file mode 100644
index 0000000..7b0183d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/ye.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/yt.gif b/platform/www/lib/plugins/translation/flags/more/yt.gif
new file mode 100644
index 0000000..a2267c0
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/yt.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/za.gif b/platform/www/lib/plugins/translation/flags/more/za.gif
new file mode 100644
index 0000000..ede5258
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/za.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/zm.gif b/platform/www/lib/plugins/translation/flags/more/zm.gif
new file mode 100644
index 0000000..b2851d2
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/zm.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/more/zw.gif b/platform/www/lib/plugins/translation/flags/more/zw.gif
new file mode 100644
index 0000000..02901f6
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/more/zw.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/nl.gif b/platform/www/lib/plugins/translation/flags/nl.gif
new file mode 100644
index 0000000..c1c8f46
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/nl.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/no.gif b/platform/www/lib/plugins/translation/flags/no.gif
new file mode 100644
index 0000000..6202d1f
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/no.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/pt-br.gif b/platform/www/lib/plugins/translation/flags/pt-br.gif
new file mode 100644
index 0000000..8c86616
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/pt-br.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/pt.gif b/platform/www/lib/plugins/translation/flags/pt.gif
new file mode 100644
index 0000000..e735f74
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/pt.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/ro.gif b/platform/www/lib/plugins/translation/flags/ro.gif
new file mode 100644
index 0000000..f5d5f12
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/ro.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/ru.gif b/platform/www/lib/plugins/translation/flags/ru.gif
new file mode 100644
index 0000000..b525c46
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/ru.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/sv.gif b/platform/www/lib/plugins/translation/flags/sv.gif
new file mode 100644
index 0000000..80f6285
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/sv.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/th.gif b/platform/www/lib/plugins/translation/flags/th.gif
new file mode 100644
index 0000000..0130792
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/th.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/tr.gif b/platform/www/lib/plugins/translation/flags/tr.gif
new file mode 100644
index 0000000..e407d55
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/tr.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/vi.gif b/platform/www/lib/plugins/translation/flags/vi.gif
new file mode 100644
index 0000000..f1e20c9
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/vi.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/flags/zh.gif b/platform/www/lib/plugins/translation/flags/zh.gif
new file mode 100644
index 0000000..b052530
--- /dev/null
+++ b/platform/www/lib/plugins/translation/flags/zh.gif
Binary files differ
diff --git a/platform/www/lib/plugins/translation/helper.php b/platform/www/lib/plugins/translation/helper.php
new file mode 100644
index 0000000..e6bc3b5
--- /dev/null
+++ b/platform/www/lib/plugins/translation/helper.php
@@ -0,0 +1,446 @@
+<?php
+/**
+ * Translation Plugin: Simple multilanguage plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Class helper_plugin_translation
+ */
+class helper_plugin_translation extends DokuWiki_Plugin {
+ var $translations = array();
+ var $translationNs = '';
+ var $defaultlang = '';
+ var $LN = array(); // hold native names
+ var $opts = array(); // display options
+
+ /**
+ * Initialize
+ */
+ function __construct() {
+ global $conf;
+ require_once(DOKU_INC . 'inc/pageutils.php');
+ require_once(DOKU_INC . 'inc/utf8.php');
+
+ $this->loadTranslationNamespaces();
+
+ // load language names
+ $this->LN = confToHash(dirname(__FILE__) . '/lang/langnames.txt');
+
+ // display options
+ $this->opts = $this->getConf('display');
+ $this->opts = explode(',', $this->opts);
+ $this->opts = array_map('trim', $this->opts);
+ $this->opts = array_fill_keys($this->opts, true);
+
+ // get default translation
+ if(!empty($conf['lang_before_translation'])) {
+ $dfl = $conf['lang'];
+ } else {
+ $dfl = $conf['lang_before_translation'];
+ }
+ if(in_array($dfl, $this->translations)) {
+ $this->defaultlang = $dfl;
+ } else {
+ $this->defaultlang = '';
+ array_unshift($this->translations, '');
+ }
+
+ $this->translationNs = cleanID($this->getConf('translationns'));
+ if($this->translationNs) $this->translationNs .= ':';
+ }
+
+ /**
+ * Parse 'translations'-setting into $this->translations
+ */
+ public function loadTranslationNamespaces() {
+ // load wanted translation into array
+ $this->translations = strtolower(str_replace(',', ' ', $this->getConf('translations')));
+ $this->translations = array_unique(array_filter(explode(' ', $this->translations)));
+ sort($this->translations);
+ }
+
+ /**
+ * Check if the given ID is a translation and return the language code.
+ *
+ * @param string $id
+ * @return string
+ */
+ function getLangPart($id) {
+ list($lng) = $this->getTransParts($id);
+ return $lng;
+ }
+
+ /**
+ * Check if the given ID is a translation and return the language code and
+ * the id part.
+ *
+ * @param string $id
+ * @return array
+ */
+ function getTransParts($id) {
+ $rx = '/^' . $this->translationNs . '(' . join('|', $this->translations) . '):(.*)/';
+ if(preg_match($rx, $id, $match)) {
+ return array($match[1], $match[2]);
+ }
+ return array('', $id);
+ }
+
+ /**
+ * Returns the browser language if it matches with one of the configured
+ * languages
+ */
+ function getBrowserLang() {
+ global $conf;
+ $langs = $this->translations;
+ if (!in_array($conf['lang'], $langs)) {
+ $langs[] = $conf['lang'];
+ }
+ $rx = '/(^|,|:|;|-)(' . join('|', $langs) . ')($|,|:|;|-)/i';
+ if(preg_match($rx, $_SERVER['HTTP_ACCEPT_LANGUAGE'], $match)) {
+ return strtolower($match[2]);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the ID and name to the wanted translation, empty
+ * $lng is default lang
+ *
+ * @param string $lng
+ * @param string $idpart
+ * @return array
+ */
+ function buildTransID($lng, $idpart) {
+ if($lng && in_array($lng, $this->translations)) {
+ $link = ':' . $this->translationNs . $lng . ':' . $idpart;
+ $name = $lng;
+ } else {
+ $link = ':' . $this->translationNs . $idpart;
+ $name = $this->realLC('');
+ }
+ return array($link, $name);
+ }
+
+ /**
+ * Returns the real language code, even when an empty one is given
+ * (eg. resolves th default language)
+ *
+ * @param string $lc
+ * @return string
+ */
+ function realLC($lc) {
+ global $conf;
+ if($lc) {
+ return $lc;
+ } elseif(!$conf['lang_before_translation']) {
+ return $conf['lang'];
+ } else {
+ return $conf['lang_before_translation'];
+ }
+ }
+
+ /**
+ * Check if current ID should be translated and any GUI
+ * should be shown
+ *
+ * @param string $id
+ * @param bool $checkact
+ * @return bool
+ */
+ function istranslatable($id, $checkact = true) {
+ global $ACT;
+
+ if($checkact && $ACT != 'show') return false;
+ if($this->translationNs && strpos($id, $this->translationNs) !== 0) return false;
+ $skiptrans = trim($this->getConf('skiptrans'));
+ if($skiptrans && preg_match('/' . $skiptrans . '/ui', ':' . $id)) return false;
+ $meta = p_get_metadata($id);
+ if(!empty($meta['plugin']['translation']['notrans'])) return false;
+
+ return true;
+ }
+
+ /**
+ * Return the (localized) about link
+ */
+ function showAbout() {
+ global $ID;
+
+ $curlc = $this->getLangPart($ID);
+
+ $about = $this->getConf('about');
+ if($this->getConf('localabout')) {
+ list(/* $lc */, $idpart) = $this->getTransParts($about);
+ list($about, /* $name */) = $this->buildTransID($curlc, $idpart);
+ $about = cleanID($about);
+ }
+
+ $out = '';
+ $out .= '<sup>';
+ $out .= html_wikilink($about, '?');
+ $out .= '</sup>';
+
+ return $out;
+ }
+
+ /**
+ * Returns a list of (lc => link) for all existing translations of a page
+ *
+ * @param $id
+ * @return array
+ */
+ function getAvailableTranslations($id) {
+ $result = array();
+
+ list($lc, $idpart) = $this->getTransParts($id);
+
+ foreach($this->translations as $t) {
+ if($t == $lc) continue; //skip self
+ list($link, $name) = $this->buildTransID($t, $idpart);
+ if(page_exists($link)) {
+ $result[$name] = $link;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Creates an UI for linking to the available and configured translations
+ *
+ * Can be called from the template or via the ~~TRANS~~ syntax component.
+ */
+ public function showTranslations() {
+ global $conf;
+ global $INFO;
+
+ if(!$this->istranslatable($INFO['id'])) return '';
+ $this->checkage();
+
+ list($lc, $idpart) = $this->getTransParts($INFO['id']);
+ $lang = $this->realLC($lc);
+
+ $out = '<div class="plugin_translation">';
+
+ //show title and about
+ if(isset($this->opts['title'])) {
+ $out .= '<span>' . $this->getLang('translations');
+ if($this->getConf('about')) $out .= $this->showAbout();
+ $out .= ':</span> ';
+ if(isset($this->opts['twolines'])) $out .= '<br />';
+ }
+
+ // open wrapper
+ if($this->getConf('dropdown')) {
+ // select needs its own styling
+ if($INFO['exists']) {
+ $class = 'wikilink1';
+ } else {
+ $class = 'wikilink2';
+ }
+ if(isset($this->opts['flag'])) {
+ $flag = DOKU_BASE . 'lib/plugins/translation/flags/' . hsc($lang) . '.gif';
+ }else{
+ $flag = '';
+ }
+
+ if($conf['userewrite']) {
+ $action = wl();
+ } else {
+ $action = script();
+ }
+
+ $out .= '<form action="' . $action . '" id="translation__dropdown">';
+ if($flag) $out .= '<img src="' . $flag . '" alt="' . hsc($lang) . '" height="11" class="' . $class . '" /> ';
+ $out .= '<select name="id" class="' . $class . '">';
+ } else {
+ $out .= '<ul>';
+ }
+
+ // insert items
+
+ array_shift($this->translations);
+ foreach($this->translations as $t) {
+ $out .= $this->getTransItem($t, $idpart);
+ }
+
+ // close wrapper
+ if($this->getConf('dropdown')) {
+ $out .= '</select>';
+ $out .= '<input name="go" type="submit" value="&rarr;" />';
+ $out .= '</form>';
+ } else {
+ $out .= '</ul>';
+ }
+
+ // show about if not already shown
+ if(!isset($this->opts['title']) && $this->getConf('about')) {
+ $out .= '&nbsp';
+ $out .= $this->showAbout();
+ }
+
+ $out .= '</div>';
+
+ return $out;
+ }
+
+ /**
+ * Return the local name
+ *
+ * @param $lang
+ * @return string
+ */
+ function getLocalName($lang) {
+ if($this->LN[$lang]) {
+ return $this->LN[$lang];
+ }
+ return $lang;
+ }
+
+ /**
+ * Create the link or option for a single translation
+ *
+ * @param $lc string The language code
+ * @param $idpart string The ID of the translated page
+ * @returns string The item
+ */
+ function getTransItem($lc, $idpart) {
+ global $ID;
+ global $conf;
+
+ list($link, $lang) = $this->buildTransID($lc, $idpart);
+ $link = cleanID($link);
+
+ // class
+ if(page_exists($link, '', false)) {
+ $class = 'wikilink1';
+ } else {
+ $class = 'wikilink2';
+ }
+
+ // local language name
+ $localname = $this->getLocalName($lang);
+
+ $divClass = 'li';
+ // current?
+ if($ID == $link) {
+ $sel = ' selected="selected"';
+ $class .= ' cur';
+ $divClass .= ' cur';
+ } else {
+ $sel = '';
+ }
+
+ // flag
+ $flag = false;
+ $style = '';
+ if(isset($this->opts['flag'])) {
+ $flag = DOKU_BASE . 'lib/plugins/translation/flags/' . hsc($lang) . '.gif';
+ $style = ' style="background-image: url(\'' . $flag . '\')"';
+ $class .= ' flag';
+ }
+
+ // what to display as name
+ if(isset($this->opts['name'])) {
+ $display = hsc($localname);
+ if(isset($this->opts['langcode'])) $display .= ' (' . hsc($lang) . ')';
+ } elseif(isset($this->opts['langcode'])) {
+ $display = hsc($lang);
+ } else {
+ $display = '&nbsp;';
+ }
+
+ // prepare output
+ $out = '';
+ if($this->getConf('dropdown')) {
+ if($conf['useslash']) $link = str_replace(':', '/', $link);
+
+ $out .= '<option class="' . $class . '" title="' . hsc($localname) . '" value="' . $link . '"' . $sel . $style . '>';
+ $out .= $display;
+ $out .= '</option>';
+ } else {
+ $out .= "<li><div class='$divClass'>";
+ $out .= '<a href="' . wl($link) . '" class="' . $class . '" title="' . hsc($localname) . '">';
+ if($flag) $out .= '<img src="' . $flag . '" alt="' . hsc($lang) . '" height="11" />';
+ $out .= $display;
+ $out .= '</a>';
+ $out .= '</div></li>';
+ }
+
+ return $out;
+ }
+
+ /**
+ * Checks if the current page is a translation of a page
+ * in the default language. Displays a notice when it is
+ * older than the original page. Tries to link to a diff
+ * with changes on the original since the translation
+ */
+ function checkage() {
+ global $ID;
+ global $INFO;
+ if(!$this->getConf('checkage')) return;
+ if(!$INFO['exists']) return;
+ $lng = $this->getLangPart($ID);
+ if($lng == $this->defaultlang) return;
+
+ $rx = '/^' . $this->translationNs . '((' . join('|', $this->translations) . '):)?/';
+ $idpart = preg_replace($rx, '', $ID);
+
+ // compare modification times
+ list($orig, /* $name */) = $this->buildTransID($this->defaultlang, $idpart);
+ $origfn = wikiFN($orig);
+ if($INFO['lastmod'] >= @filemtime($origfn)) return;
+
+ // get revision from before translation
+ $orev = 0;
+
+ $changelog = new PageChangelog($orig);
+ $revs = $changelog->getRevisions(0, 100);
+ foreach($revs as $rev) {
+ if($rev < $INFO['lastmod']) {
+ $orev = $rev;
+ break;
+ }
+ }
+
+ // see if the found revision still exists
+ if($orev && !page_exists($orig, $orev)) $orev = 0;
+
+ // build the message and display it
+ $orig = cleanID($orig);
+ $msg = sprintf($this->getLang('outdated'), wl($orig));
+
+ $difflink = $this->getOldDiffLink($orig, $INFO['lastmod']);
+ if ($difflink) {
+ $msg .= sprintf(' ' . $this->getLang('diff'), $difflink);
+ }
+
+ echo '<div class="notify">' . $msg . '</div>';
+ }
+
+ function getOldDiffLink($id, $lastmod) {
+ // get revision from before translation
+ $orev = false;
+ $changelog = new PageChangelog($id);
+ $revs = $changelog->getRevisions(0, 100);
+ foreach($revs as $rev) {
+ if($rev < $lastmod) {
+ $orev = $rev;
+ break;
+ }
+ }
+ if($orev && !page_exists($id, $orev)) {
+ return false;
+ }
+ $id = cleanID($id);
+ return wl($id, array('do' => 'diff', 'rev' => $orev));
+
+ }
+}
diff --git a/platform/www/lib/plugins/translation/lang/be/lang.php b/platform/www/lib/plugins/translation/lang/be/lang.php
new file mode 100644
index 0000000..dd9d9ae
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/be/lang.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * Belarusian language file
+ *
+ * @author Rainbow-Spike <rainbow_spike@derpy.ru>
+ */
+
+$lang['translations'] = 'Пераклад гэтай старонкі';
+$lang['outdated'] = 'Гэты пераклад старэй, чым <a href="%s" class="wikilink1">арыгінальная старонка</a> і можа быць не актуальным.';
+$lang['diff'] = 'Глядзіце <a href="%s" class="wikilink1">было зменена</a>.';
+$lang['transloaded'] = 'Сэнс перакладу гэтай старонкі %s быў папярэдне загружаны для зручнасці перакладу. <br /> Таксама можаце выкарыстоўваць наступныя пераклады: %s.';
+$lang['menu'] = 'састарэлыя і адсутныя пераклады';
+$lang['missing'] = 'Адсутнічае!';
+$lang['old'] = 'застарэлы';
+$lang['current'] = 'бягучы';
+$lang['path'] = 'Шлях';
diff --git a/platform/www/lib/plugins/translation/lang/be/settings.php b/platform/www/lib/plugins/translation/lang/be/settings.php
new file mode 100644
index 0000000..9627847
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/be/settings.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * Belarusian language file
+ *
+ * @author Rainbow-Spike <rainbow_spike@derpy.ru>
+ */
+
+$lang['translations'] = 'Спіс падтрымоўваных моў перакладу (двохсимвольні коды ISO). Падзяляйце значэння коскамі або прабеламі.';
+$lang['translationns'] = 'Калі вы хочаце перавесці толькі пэўны Прастору імёнаў, тады ўпішыце тут яго імя.';
+$lang['skiptrans'] = 'Калі імя старонкі адпавядае гэтаму рэгулярнаму выразу, тады не адлюстроўваць меню перакладаў.';
+$lang['dropdown'] = 'Выкарыстаць выпадальны спіс для адлюстравання даступных перакладаў (рэкамендуецца, калі больш за 5 перакладаў)';
+$lang['translateui'] = 'Павінна мова інтэрфейсу карыстальніка таксама перамыкацца мовы Прасторы імёнаў?';
+$lang['redirectstart'] = 'Павінна стартавая старонка аўтаматычна перенаправлятись на Прастору імёнаў мовы, выкарыстоўваючы детектекцію мовы аглядальніка?';
+$lang['about'] = 'Калі ласка, Увядзіце імя старонкі, на якой будзе руж\'растлумачана функцыі перакладу для вашых карыстальнікаў. Яна будзе звязана з выбарам мовы.';
+$lang['localabout'] = 'Выкарыстаць лакалізаваную версію старонкі руж\'тлумачэнняў (замест адной глабальнай старонкі руж\'тлумачэнняў).';
+$lang['checkage'] = 'Адлюстроўваць папярэджанне аб магчымай не актуальнасць перакладу старонак?';
+$lang['display'] = 'Абярыце што б вы хацелі адлюстроўваць у перамыкачы моў. Заўвага: выкарыстоўваць сцяг краіны для пераключальніка моў не рэкамендуецца экспертамі па выгодзе выкарыстання інтэрфейсу.';
+
+$lang['copytrans'] = 'Скапіяваць тэкст на мове арыгінала да рэдактара ў пачатку новага перакладу?';
+$lang['show_path'] = 'Паказаць шлях адсутнічае на старонцы перакладу?';
diff --git a/platform/www/lib/plugins/translation/lang/be/totranslate.txt b/platform/www/lib/plugins/translation/lang/be/totranslate.txt
new file mode 100644
index 0000000..07eadbc
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/be/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Гэтая старонка яшчэ не цалкам перакладзеная. Калі ласка, дапамажыце завяршыць пераклад.**\\ //(выдаліце гэты абзац пасля завяршэння перакладу)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/bn/lang.php b/platform/www/lib/plugins/translation/lang/bn/lang.php
new file mode 100644
index 0000000..5bfc73b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/bn/lang.php
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author ninetailz <ninetailz1125@gmail.com>
+ */
+$lang['translations'] = 'এই পাতার অনুবাদ';
+$lang['outdated'] = 'এই অনুবাদ <a href="%s" class="wiki link1">মূল পাতা</ a> তুলনায় পুরোনো হয় এবং পুরান হতে পারে.';
+$lang['diff'] = 'দেখুন কি <a href="%s" class="wikilink1">পরিবর্তন</ a> হয়েছে';
diff --git a/platform/www/lib/plugins/translation/lang/bn/settings.php b/platform/www/lib/plugins/translation/lang/bn/settings.php
new file mode 100644
index 0000000..d1df06b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/bn/settings.php
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author ninetailz <ninetailz1125@gmail.com>
+ */
+$lang['about'] = 'এখানে একটি পৃষ্ঠায় নাম লিখুন যেখানে অনুবাদের বৈশিষ্ট্যটি ব্যবহারকারীদের জন্য ব্যাখ্যা করা আছে. এটা ভাষা নির্বাচক থেকে লিঙ্ক করা হবে.';
+$lang['checkage'] = 'সম্ভবত পুরোনো অনুবাদের বিষয়ে সাবধান.';
+$lang['copytrans'] = 'একটি নতুন অনুবাদ শুরু যখন সম্পাদক মধ্যে মূল ভাষা টেক্সট কপি করুন?';
diff --git a/platform/www/lib/plugins/translation/lang/bn/totranslate.txt b/platform/www/lib/plugins/translation/lang/bn/totranslate.txt
new file mode 100644
index 0000000..46e5f1c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/bn/totranslate.txt
@@ -0,0 +1 @@
+FixMe ** এই পাতা সম্পূর্ণরূপে এখনো অনুবাদ করা হয়নি. অনুবাদ সমাপ্তির সাহায্য করুন. ** \ \ / / (অনুবাদ সমাপ্ত হয় একবার এই অনুচ্ছেদ মুছে ফেলুন) / / \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/ca/lang.php b/platform/www/lib/plugins/translation/lang/ca/lang.php
new file mode 100644
index 0000000..c74c568
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/ca/lang.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Jordi Robert Sancho <jrobertsancho@gmail.com>
+ */
+$lang['translations'] = 'Traduccions d\'aquesta pàgina';
+$lang['outdated'] = 'Aquesta traducció és més antiga que la <a href="%s" class="wikilink1">pàgina original</a> i pot estar desactualitzada.';
+$lang['diff'] = 'Veure que ha <a href="%s" class="wikilink1">canviat</a>.';
+$lang['transloaded'] = 'Els continguts de la traducció d\'aquesta pàgina a %s han sigut pre-carregats per facilitar la traducció.<br>Però pots basar la teva traducció en les següents traduccions: %s';
+$lang['menu'] = 'traduccions desactualitzades i que falten';
+$lang['missing'] = 'Falta!';
+$lang['old'] = 'desactualitzat';
+$lang['current'] = 'actualitzat';
+$lang['path'] = 'Ruta';
diff --git a/platform/www/lib/plugins/translation/lang/ca/settings.php b/platform/www/lib/plugins/translation/lang/ca/settings.php
new file mode 100644
index 0000000..4a356cc
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/ca/settings.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Jordi Robert Sancho <jrobertsancho@gmail.com>
+ */
+$lang['translations'] = 'Llista separada per espais d\'idiomes de traducció';
+$lang['translationns'] = 'Si només vols traduccions sota un cert espai de noms, posa\'l aquí.';
+$lang['skiptrans'] = 'Quan el nom de la pàgina coincideix amb aquesta expressió regular, no mostris el menú de traducció.';
+$lang['dropdown'] = 'Utilitzar una llista desplegable per mostrar les traduccions (recomanat per a més de 5 idiomes).';
+$lang['translateui'] = 'L\'idioma de la interfície d\'usuari s\'hauria de canviar també en els espais de noms en llengües estrangeres?';
+$lang['redirectstart'] = 'La pàgina d\'inici hauria de redirigir a un espai de noms d\'idioma mitjançant la detecció d\'idioma del navegador?';
+$lang['about'] = 'Introdueix un nom de pàgina aquí, on la funció de traducció s\'explica als usuaris. Estarà connectat des del selector d\'idioma.';
+$lang['localabout'] = 'Utilitzar versions localitzades de la pàgina \'quant a\' (en lloc d\'un \'quant a\' global).';
+$lang['checkage'] = 'Advertir sobre possibles traduccions obsoletes.';
+$lang['display'] = 'Selecciona el que vulguis que es mostri al seleccionador d\'idioma. Recorda que els experts en usabilitat no recomanen fer servir banderes de país.';
+$lang['copytrans'] = 'Copiar el text en l\'idioma original en l\'editor quan s\'inicia una nova traducció?';
+$lang['show_path'] = 'Mostrar la ruta a la pàgina de traducció que falta?';
diff --git a/platform/www/lib/plugins/translation/lang/ca/totranslate.txt b/platform/www/lib/plugins/translation/lang/ca/totranslate.txt
new file mode 100644
index 0000000..96820e8
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/ca/totranslate.txt
@@ -0,0 +1 @@
+ARREGLA'M **Aquesta pàgina encara no està totalment traduïda. Si us plau, ajuda completant la traducció.**\\//(treu aquest paràgraf en acabar la traducció)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/cs/lang.php b/platform/www/lib/plugins/translation/lang/cs/lang.php
new file mode 100644
index 0000000..faca1ae
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/cs/lang.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Jaroslav Lichtblau <jlichtblau@seznam.cz>
+ */
+$lang['translations'] = 'Překlady této stránky';
+$lang['outdated'] = 'Tento překlad je starší než <a href="%s" class="wikilink1">originální stránka</a> a nejspíše i zastaralý.';
+$lang['diff'] = 'Zobrazit <a href="%s" class="wikilink1">změny</a>.';
+$lang['transloaded'] = 'Text pro překlad této stránky do %s byl pro ulehčení překládání automaticky načten.<br />Můžete ale použít předešlé dostupné překlady: %s.';
+$lang['menu'] = 'zastaralé a chybějící překlady';
+$lang['missing'] = 'Chybí!';
+$lang['old'] = 'zastaralý';
+$lang['current'] = 'aktuální';
+$lang['path'] = 'Cesta';
diff --git a/platform/www/lib/plugins/translation/lang/cs/settings.php b/platform/www/lib/plugins/translation/lang/cs/settings.php
new file mode 100644
index 0000000..f24ab34
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/cs/settings.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Jaroslav Lichtblau <jlichtblau@seznam.cz>
+ */
+$lang['translations'] = 'Seznam přeložených jazyků (ISO kódů) oddělený mezerami. Nezahrnujte defaultní jazyk!';
+$lang['translationns'] = 'Chcete-li udržovat překlad jen pro konkrétní jmenný prostor, vložte jeho jméno sem.';
+$lang['skiptrans'] = 'Pokud jméno stránky obsahuje tento regulární výraz, nezobrazovat překladové menu.';
+$lang['dropdown'] = 'Použít rozbalovací seznam dostupných překladů (doporučeno pro 5 a více jazyků).';
+$lang['translateui'] = 'Mělo by se přeložit i uživatelské rozhraní při změně překladu stránky?';
+$lang['redirectstart'] = 'Má hlavní stránka automaticky přesměrovávat na dostupnou jazykovou verzi jmenného prostoru dle nastavení jazyka prohlížeče?';
+$lang['about'] = 'Vložte jméno stránky s nápovědou ohledně možnosti překládat stránky na DokuWiki s pomoci Translation pluginu. Tento odkaz bude k dispozici z výběru přeložených jazyků.';
+$lang['localabout'] = 'Použít přeložené verze stran o aplikaci (namísto té globální).';
+$lang['checkage'] = 'Upozorňovat na možné zastaralé překlady.';
+$lang['display'] = 'Vybrat co se má zobrazovat v menu pro výběr jazyka. Experti na použitelnost webu nedoporučují zobrazování obrázků vlajek zemí pro výběr jazyka.';
+$lang['copytrans'] = 'Kopírovat výchozí jazykovou verzi do editoru pro nový překlad?';
+$lang['show_path'] = 'Zobrazit cestu na chybějící stránku překladu?';
diff --git a/platform/www/lib/plugins/translation/lang/cs/totranslate.txt b/platform/www/lib/plugins/translation/lang/cs/totranslate.txt
new file mode 100644
index 0000000..5cdeee6
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/cs/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Tato stránka ještě není plně přeložena. Pomozte s dokončením překladu.**\\ //(odstraňte tento odstavec, jakmile je překlad dokončen)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/cy/lang.php b/platform/www/lib/plugins/translation/lang/cy/lang.php
new file mode 100644
index 0000000..18e12e7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/cy/lang.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Alan Davies <ben.brynsadler@gmail.com>
+ */
+$lang['translations'] = 'Cyfieithiadau\'r dudalen hon';
+$lang['outdated'] = 'Mae\'r cyfieithiad hwn yn hŷn na\'r <a href="%s" class="wikilink1">dudalen wreiddiol</a> a gall fod wedi dyddio.';
+$lang['diff'] = 'Gweld beth sydd wedi <a href="%s" class="wikilink1">newid</a>.';
+$lang['transloaded'] = 'Cafodd cynnwys y dudalen hon mewn %s ei raglwytho er mwyn hwyluso\'r cyfieithu.<br />Er gallwch chi seilio\'ch cyfieithiad ar y cyfieithiadau canlynol sy\'n bodoli\'n barod: %s';
diff --git a/platform/www/lib/plugins/translation/lang/cy/settings.php b/platform/www/lib/plugins/translation/lang/cy/settings.php
new file mode 100644
index 0000000..21d5315
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/cy/settings.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Alan Davies <ben.brynsadler@gmail.com>
+ */
+$lang['translations'] = 'Rhestr gwahanwyd gan goma o iaith gyfieithu (codau ISO)';
+$lang['translationns'] = 'Os ydych chi am osod y cyfieithiadau o dan namespace penodol yn unig, rhowch e yma.';
+$lang['skiptrans'] = 'Pan fydd enw\'r dudalen yn bodloni\'r mynegiad rheolaidd, paid dangos y dewislen cyfieithu.';
+$lang['dropdown'] = 'Defnyddio cwymprestr i ddangos y cyfieithiadau (awgrymir am fwy na 5 iaith).';
+$lang['translateui'] = 'A ddylai iaith rhyngwyneb y defnyddiwr gael ei newid mewn namespaces ieithoedd estron hefyd?';
+$lang['redirectstart'] = 'A ddylai\'r dudalen gychwyn ailgyfeirio yn awtomatig i mewn i namespace iaith gan ddefnyddio datgeliad iaith y porwr?';
+$lang['about'] = 'Rhowch enw tudalen yma lle caiff y nodwedd cyfieithu ei esbonio ar gyfer eich defnyddwyr. Caiff ei gysylltu o\'r dewisydd iaith.';
+$lang['localabout'] = 'Defnyddio fersiynau lleoledig o\'r dudalen \'ynghylch\' (yn hytrach nag un dudalen \'ynghylch\' gyffredinol).';
+$lang['checkage'] = 'Rhybuddio ynghylch cyfieithiadau sydd efallai wedi dyddio.';
+$lang['display'] = 'Dewiswch yr hyn hoffech chi weld yn y dewisydd iaith. \'Dyw defnyddio baneri gwlad ddim i\'w awgrymu yn ôl arbenigwyr.';
+$lang['copytrans'] = 'Copïo testun y iaith wreiddiol i\'r golygydd wrth ddechrau cyfieithiad newydd?';
diff --git a/platform/www/lib/plugins/translation/lang/cy/totranslate.txt b/platform/www/lib/plugins/translation/lang/cy/totranslate.txt
new file mode 100644
index 0000000..da8bfaa
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/cy/totranslate.txt
@@ -0,0 +1 @@
+FIXME **'Dyw'r dudalen heb ei chyfieithu'n llawn eto. Cynorthwywch gan gyflawni'r cyfieithiad.**\\ //(tynnych y paragraff hwn unwaith i chi orffen y cyfieithu)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/da/lang.php b/platform/www/lib/plugins/translation/lang/da/lang.php
new file mode 100644
index 0000000..af5f450
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/da/lang.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Markus Petersen <markus@mdev.dk>
+ * @author Soren Birk <soer9648@eucl.dk>
+ */
+$lang['translations'] = 'Oversættelser af denne side';
+$lang['outdated'] = 'Denne oversættelse er ældre end den <a href="%s" class="wikilink1">originale side</a> og er muligvis forældet.';
+$lang['diff'] = 'Se hvad der er <a href="%s" class="wikilink1">ændret</a>.';
+$lang['transloaded'] = 'Indholdet af denne sides oversættelse i %s er blevet præ-indlæst for lettere oversættelse. <br />Du kan basere din oversættelse på følgende nuværende oversættelser: %s.';
diff --git a/platform/www/lib/plugins/translation/lang/da/settings.php b/platform/www/lib/plugins/translation/lang/da/settings.php
new file mode 100644
index 0000000..7b43734
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/da/settings.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Markus Petersen <markus@mdev.dk>
+ * @author Soren Birk <soer9648@eucl.dk>
+ * @author Jacob Palm <mail@jacobpalm.dk>
+ */
+$lang['translations'] = 'Mellemrums-separeret liste a oversættelsessprog (ISO koder). Lad være med at inkludere standardsproget.';
+$lang['translationns'] = 'Hvis du kun vil have oversættelser under et bestemt navnerum, indsæt det her.';
+$lang['skiptrans'] = 'Hvis navnet på siden matcher dette regulære udtryk, så lad være med at vise oversættelsesmenuen.';
+$lang['dropdown'] = 'Benyt en rulleliste til at vise oversættelserne (anbefales til 5 sprog eller mere).';
+$lang['translateui'] = 'Skal brugerfladens sprog også skiftes i fremmedsprogets navnerum?';
+$lang['redirectstart'] = 'Skal startsiden automatisk henvise til et sprog-navnerum vha browserens sprog-genkendelse?';
+$lang['about'] = 'Skriv et sidenavn her hvor oversættelsesfunktionen er forklaret for dine brugere. Siden vil blive linket til fra sprogvælgeren.';
+$lang['localabout'] = 'Anvend lokaliserede versions af "Om" siden (i stedet for en global "Om" side)';
+$lang['checkage'] = 'Advar om mulige forældede oversættelser.';
+$lang['display'] = 'Angiv hvad du ønsker der skal vises menuen til valg af sprog. Bemærk venligst, at det frarådes at benytte landeflag til sprogvalg.';
+$lang['copytrans'] = 'Kopier tekst fra originalt sporg ind i editorern når en ny oversættelse påbegyndes?';
diff --git a/platform/www/lib/plugins/translation/lang/da/totranslate.txt b/platform/www/lib/plugins/translation/lang/da/totranslate.txt
new file mode 100644
index 0000000..3109105
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/da/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Denne side er endnu ikke fuldt oversat. Måske kan du hjælpe med at færdiggøre oversættelsen?**\\ //(fjern dette afsnit når siden er oversat)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/de-informal/lang.php b/platform/www/lib/plugins/translation/lang/de-informal/lang.php
new file mode 100644
index 0000000..83c73aa
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/de-informal/lang.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author F. Mueller-Donath <j.felix@mueller-donath.de>
+ */
+$lang['translations'] = 'Übersetzungen dieser Seite';
+$lang['outdated'] = 'Diese Übersetzung ist älter als das <a href="%s" class="wikilink1">Original</a> und ist eventuell veraltet.';
+$lang['diff'] = '<a href="%s" class="wikilink1">Änderungen</a> zeigen.';
+$lang['transloaded'] = 'Der Inhalt dieser Seite auf %s wurde in den Editor geladen um die Übersetzung zu erleichtern.<br />Du kannst deine Arbeit auch mit einer der folgenden vorhandenen Übersetzungen beginnen: %s.';
+$lang['menu'] = 'veraltete und fehlende Übersetzungen';
+$lang['missing'] = 'Fehlt!';
+$lang['old'] = 'veraltet';
+$lang['current'] = 'aktuell';
+$lang['path'] = 'Pfad';
diff --git a/platform/www/lib/plugins/translation/lang/de-informal/settings.php b/platform/www/lib/plugins/translation/lang/de-informal/settings.php
new file mode 100644
index 0000000..a0db7a2
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/de-informal/settings.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author F. Mueller-Donath <j.felix@mueller-donath.de>
+ */
+$lang['translations'] = 'Liste der Sprachen (ISO codes), mittels Leerzeichen separiert. Die Default-Sprache nicht angeben.';
+$lang['translationns'] = 'Wenn die Übersetzung nur unterhalb eines Namensraumes gelten soll, diesen hier angeben.';
+$lang['skiptrans'] = 'Wenn der Seitennamen dem regulären Ausdruck entspricht, dann den Sprachumschalter nicht anzeigen.';
+$lang['dropdown'] = 'Eine Auswahlliste benutzen, um die Übersetzungen anzuzeigen (zu bevorzugen bei mehr als fünf Sprachen).';
+$lang['translateui'] = 'Soll die Sprache der Benutzerschnittstelle auch in die jeweilige Fremdspache umgeschaltet werden?';
+$lang['redirectstart'] = 'Anhand des Browsers des Benutzers erkennen, welche Sprache angezeigt werden soll. (Startseite leitet in den passenden Namensraum um).';
+$lang['about'] = 'Gebe hier eine Seite an, welche den Mechanismus der Übersetzung erklärt. Sie wird vom Sprachumschalter verlinkt.';
+$lang['localabout'] = 'Sprachspezifische Versionen der oben angegebenen Seite (anstelle einer globalen) nutzen.';
+$lang['checkage'] = 'Warnungen von möglicherweise veralteten Übersetzungen anzeigen.';
+$lang['display'] = 'Gib hier an welches/r Symbol/Text im Sprachumschalter angezeigt werden soll. (Die Nutzung von länderspezifischen Flaggen wird aus Gründen der Benutzbarkeit nicht empfohlen.)';
+$lang['copytrans'] = 'Original Sprachversion in den Editor kopieren wenn eine neue Übersetzung begonnen wird?';
+$lang['show_path'] = 'Seitenpfad in der Übersicht der fehlenden Übersetzungen anzeigen?';
diff --git a/platform/www/lib/plugins/translation/lang/de-informal/totranslate.txt b/platform/www/lib/plugins/translation/lang/de-informal/totranslate.txt
new file mode 100644
index 0000000..934a71e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/de-informal/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Diese Seite wurde noch nicht vollständig übersetzt. Bitte hilf bei der Übersetzung.**\\ //(diesen Absatz entfernen, wenn die Übersetzung abgeschlossen wurde)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/de/lang.php b/platform/www/lib/plugins/translation/lang/de/lang.php
new file mode 100644
index 0000000..750b6c6
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/de/lang.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+$lang['translations'] = 'Übersetzungen dieser Seite';
+$lang['outdated'] = 'Diese Übersetzung ist älter als das <a href="%s" class="wikilink1">Original</a> und ist eventuell veraltet.';
+$lang['diff'] = '<a href="%s" class="wikilink1">Änderungen</a> zeigen.';
+$lang['transloaded'] = 'Der Inhalt dieser Seite auf %s wurde in den Editor geladen um die Übersetzung zu erleichtern.<br />Sie können Ihre Arbeit auch mit einer der folgenden vorhandenen Übersetzungen beginnen: %s.';
+$lang['menu'] = "veraltete und fehlende Übersetzungen";
+$lang['missing'] = 'Fehlt!';
+$lang['old'] = 'veraltet';
+$lang['current'] = 'aktuell';
+$lang['path'] = 'Pfad';
diff --git a/platform/www/lib/plugins/translation/lang/de/settings.php b/platform/www/lib/plugins/translation/lang/de/settings.php
new file mode 100644
index 0000000..ce7fbda
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/de/settings.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+$lang['translations'] = 'Liste der Sprachen (ISO codes), mittels Leerzeichen separiert. Die Default-Sprache nicht angeben.';
+$lang['translationns'] = 'Wenn die Übersetzung nur unterhalb eines Namensraumes gelten soll, diesen hier angeben.';
+$lang['skiptrans'] = 'Wenn der Seitennamen dem regulären Ausdruck entspricht, dann den Sprachumschalter nicht anzeigen.';
+$lang['dropdown'] = 'Eine Auswahlliste benutzen, um die Übersetzungen anzuzeigen (zu bevorzugen bei mehr als fünf Sprachen).';
+$lang['translateui'] = 'Soll die Sprache der Benutzerschnittstelle auch in die jeweilige Fremdspache umgeschaltet werden?';
+$lang['redirectstart'] = 'Anhand des Browsers des Benutzers erkennen, welche Sprache angezeigt werden soll. (Startseite leitet in den passenden Namensraum um).';
+$lang['about'] = 'Geben Sie hier eine Seite an, welche den Mechanismus der Übersetzung erklärt. Sie wird vom Sprachumschalter verlinkt.';
+$lang['localabout'] = 'Sprachspezifische Versionen der oben angegebenen Seite (anstelle einer globalen) nutzen.';
+$lang['checkage'] = 'Warnungen von möglicherweise veralteten Übersetzungen anzeigen.';
+$lang['display'] = 'Geben Sie an welches/r Symbol/Text im Sprachumschalter angezeigt werden soll. (Die Nutzung von länderspezifischen Flaggen wird aus Gründen der Benutzbarkeit nicht empfohlen.)';
+$lang['copytrans'] = 'Original Sprachversion in den Editor kopieren wenn eine neue Übersetzung begonnen wird?';
+$lang['show_path'] = 'Seitenpfad in der Übersicht der fehlenden Übersetzungen anzeigen?';
diff --git a/platform/www/lib/plugins/translation/lang/de/totranslate.txt b/platform/www/lib/plugins/translation/lang/de/totranslate.txt
new file mode 100644
index 0000000..37d03ae
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/de/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Diese Seite wurde noch nicht vollständig übersetzt. Bitte helfen Sie bei der Übersetzung.**\\ //(diesen Absatz entfernen, wenn die Übersetzung abgeschlossen wurde)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/en/lang.php b/platform/www/lib/plugins/translation/lang/en/lang.php
new file mode 100644
index 0000000..304d298
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/en/lang.php
@@ -0,0 +1,11 @@
+<?php
+
+$lang['translations'] = 'Translations of this page';
+$lang['outdated'] = 'This translation is older than the <a href="%s" class="wikilink1">original page</a> and might be outdated.';
+$lang['diff'] = 'See what has <a href="%s" class="wikilink1">changed</a>.';
+$lang['transloaded'] = 'The contents of this page\'s translation in %s have been pre-loaded for easy translation.<br />But you can base your translation on the following existing translations: %s.';
+$lang['menu'] = "outdated and missing translations";
+$lang['missing'] = 'Missing!';
+$lang['old'] = 'outdated';
+$lang['current'] = 'up-to-date';
+$lang['path'] = 'Path';
diff --git a/platform/www/lib/plugins/translation/lang/en/settings.php b/platform/www/lib/plugins/translation/lang/en/settings.php
new file mode 100644
index 0000000..bda50a6
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/en/settings.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * English language file
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+$lang['translations'] = 'Space separated list of translation languages (ISO codes).';
+$lang['translationns'] = 'If you only want translations below a certain namespace, put it here.';
+$lang['skiptrans'] = 'When the pagename matches this regular expression, don\'t show the translation menu.';
+$lang['dropdown'] = 'Use a dropdown list to display the translations (recommended for more than 5 languages).';
+$lang['translateui'] = 'Should the language of the user interface be switched in foreign language namespaces, too?';
+$lang['redirectstart'] = 'Should the start page automatically redirect into a language namespace using browser language detection?';
+$lang['about'] = 'Enter a pagename here where the translation feature is explained for your users. It will be linked from the language selector.';
+$lang['localabout'] = 'Use localized versions of about page (instead of one global about page).';
+$lang['checkage'] = 'Warn about possibly outdated translations.';
+$lang['display'] = 'Select what you\'d like to have shown in the language selector. Note that using country flags for language selection is not recommended by usability experts.';
+
+$lang['copytrans'] = 'Copy original language text into the editor when starting a new translation?';
+$lang['show_path'] = 'Show path on the missing translation page?'; \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/en/totranslate.txt b/platform/www/lib/plugins/translation/lang/en/totranslate.txt
new file mode 100644
index 0000000..ab42d5f
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/en/totranslate.txt
@@ -0,0 +1 @@
+FIXME **This page is not fully translated, yet. Please help completing the translation.**\\ //(remove this paragraph once the translation is finished)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/eo/lang.php b/platform/www/lib/plugins/translation/lang/eo/lang.php
new file mode 100644
index 0000000..3b325da
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/eo/lang.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Robert Bogenschneider <bogi@uea.org>
+ */
+$lang['translations'] = 'Tradukoj de tiu paĝo';
+$lang['outdated'] = 'Tiu traduko estas pli malnova ol la <a href="%s" class="wikilink1">origina paĝo</a> kaj povus esti malaktuala.';
+$lang['diff'] = 'Vidi kio <a href="%s" class="wikilink1">ŝanĝiĝis</a>.';
+$lang['transloaded'] = 'La enhavo de la paĝtraduko en %s disponeblas por facila tradukado.<br />Sed vi povas bazi vian tradukon sur la sekvaj tradukoj: %s.';
diff --git a/platform/www/lib/plugins/translation/lang/eo/settings.php b/platform/www/lib/plugins/translation/lang/eo/settings.php
new file mode 100644
index 0000000..6edf642
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/eo/settings.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Robert Bogenschneider <bogi@uea.org>
+ */
+$lang['translations'] = 'Spaco-disigita listo de tradukaj lingvoj (ISO-kodoj).';
+$lang['translationns'] = 'Se vi volas traduki nur ene de certa nomspaco, indiku ĝin.';
+$lang['skiptrans'] = 'Ne montri la tradukmenuon, kiam la paĝnomo kongruas al tiu regula esprimo.';
+$lang['dropdown'] = 'Uzi falmenuon por montri la tradukojn (rekomendata por pli ol 5 lingvoj).';
+$lang['translateui'] = 'Ĉu ankaŭ ŝanĝi la lingvon de la uzanto-interfaco en alilingvaj nomspacoj?';
+$lang['redirectstart'] = 'Ĉu la startpaĝo aŭtomate redirektiĝu al lingva nomspaco laŭ foliumila rekonado?';
+$lang['about'] = 'Paĝnomo, kie klariĝas la tradukad-funkcio al uzantoj. La lingvo-selektilo ligos tien.';
+$lang['localabout'] = 'Uzi lokajn versiojn de la pri-paĝo (anstataŭ unu ĝenerala pri-paĝo).';
+$lang['checkage'] = 'Averti pri eble malaktualaj tradukoj.';
+$lang['display'] = 'Kion montri en la lingvo-selektilo. Notu ke uzeblec-fakuloj ne rekomendas uzi landajn flagetojn por lingvo-elekto.';
+$lang['copytrans'] = 'Ĉu kopii la originlingvan tekston en la redaktokampon por komenci novan tradukon?';
diff --git a/platform/www/lib/plugins/translation/lang/eo/totranslate.txt b/platform/www/lib/plugins/translation/lang/eo/totranslate.txt
new file mode 100644
index 0000000..1987959
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/eo/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Tiu paĝo ankoraŭ ne plene tradukiĝis. Bv. helpi kompletigi la tradukon.**\\ //(forigu tiun alineon post fintraduko)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/es/lang.php b/platform/www/lib/plugins/translation/lang/es/lang.php
new file mode 100644
index 0000000..f4786c2
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/es/lang.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Domingo Redal <docxml@gmail.com>
+ * @author Camilo Sampedro <sampedro1903@gmail.com>
+ * @author carlos <carloscyb@gmail.com>
+ */
+$lang['translations'] = 'Traducciones de esta página';
+$lang['outdated'] = 'Esta traducción es más antigua que la <a href="%s" class="wikilink1">página original</a> y podría estar obsoleta.';
+$lang['diff'] = 'Ver lo que <a href="%s" class="wikilink1">ha cambiado</a>.';
+$lang['transloaded'] = 'Los contenidos de la traducción de esta página en %s han sido precargados para facilitar la traducción.<br />Pero puedes basar tu traducción en las siguientes traducciones existentes: %s.';
+$lang['menu'] = 'traducciones obsoletas y ausentes';
+$lang['missing'] = '¡Ausente!';
+$lang['old'] = 'obsoleta';
+$lang['current'] = 'actualizado';
+$lang['path'] = 'ruta';
diff --git a/platform/www/lib/plugins/translation/lang/es/settings.php b/platform/www/lib/plugins/translation/lang/es/settings.php
new file mode 100644
index 0000000..723fb12
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/es/settings.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Domingo Redal <docxml@gmail.com>
+ * @author Cristina Amor <princesa.7@gmail.com>
+ * @author Camilo Sampedro <sampedro1903@gmail.com>
+ * @author carlos <carloscyb@gmail.com>
+ */
+$lang['translations'] = 'Lista de lenguajes para traducción (Códigos ISO), separados por espacios. No incluir el lenguaje por defecto.';
+$lang['translationns'] = 'Si sólo quieres traducciones en determinados espacios de nombre, indícalos aquí.';
+$lang['skiptrans'] = 'Cuando el nombre de la página concuerda con esta expresión regular, no mostrar el menú de traducción.';
+$lang['dropdown'] = 'Utiliza una lista desplegable para mostrar las traducciones (recomendado para más de 5 idiomas).';
+$lang['translateui'] = '¿También debería el lenguaje del interfaz de usuario cambiarse en los espacios de nombre foráneos?';
+$lang['redirectstart'] = '¿Debería la página principal redireccionar automáticamente a una página de un idioma según sea detectado por el navegador?';
+$lang['about'] = 'Introduce aquí un nombre de página donde se explique a tus usuarios la funcionalidad de traducción. Se enlazará desde el selector de lenguaje.';
+$lang['localabout'] = 'Utiliza versiones localizadas de la página \'acerca de\' (en lugar de una página \'acerca de\' global)';
+$lang['checkage'] = 'Alertar sobre posibles traducciones obsoletas.';
+$lang['display'] = 'Selecciona lo que quieras que sea mostrado en el selector de idioma. Ten en cuenta que el uso de parámetros de país para la selección de idioma no está recomendada por los expertos en usabilidad.';
+$lang['copytrans'] = '¿Mostrar el texto en el idioma original en el editor cuando se comienza una nueva traducción?';
+$lang['show_path'] = '¿Muestra la ruta en la página de traducción ausente?';
diff --git a/platform/www/lib/plugins/translation/lang/es/totranslate.txt b/platform/www/lib/plugins/translation/lang/es/totranslate.txt
new file mode 100644
index 0000000..6dc2803
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/es/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Esta página no está completamente traducida, aún. Por favor, contribuye a su traducción.**\\ //(Elimina este párrafo una vez la traducción esté completa)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/fa/lang.php b/platform/www/lib/plugins/translation/lang/fa/lang.php
new file mode 100644
index 0000000..2abecd7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/fa/lang.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Sam01 <m.sajad079@gmail.com>
+ */
+$lang['translations'] = 'ترجمه‌های این صفحه';
+$lang['outdated'] = 'این ترجمه از a href="%s" class="wikilink1">صفحه‌ی اصلی</a> قدیمی‌تر است و ممکن است منسوخ شده باشد.';
+$lang['diff'] = 'ببینید چه چیزی <a href="%s" class="wikilink1">تغییر کرده</a>.';
+$lang['transloaded'] = 'محتویات این ترجمه‌ی صفحه در %s برای ترجمه‌ی آسان از قبل پر شده‌است. <br />اما شما می‌توانید پایه‌ی ترجمه‌هایتان را در ترجمه‌های موجود زیر ببینید: %s.';
+$lang['menu'] = 'ترجمه‌های منسوخ‌ شده و پیدا نشده';
+$lang['missing'] = 'پیدا نشده!';
+$lang['old'] = 'منسوخ شده';
+$lang['current'] = 'به روز';
+$lang['path'] = 'مسیر';
diff --git a/platform/www/lib/plugins/translation/lang/fa/settings.php b/platform/www/lib/plugins/translation/lang/fa/settings.php
new file mode 100644
index 0000000..2331783
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/fa/settings.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Sam01 <m.sajad079@gmail.com>
+ */
+$lang['translations'] = 'فضای لیست جداشده‌ی زبان‌های ترجمه شده (کدهای آی‌اس‌او)';
+$lang['translationns'] = 'اگر شما فقط می‌خواهید ترجمه‌ها زیر یک فضای‌نام خاص باشند، اینجا قرار دهید.';
+$lang['skiptrans'] = 'وقتی نام‌صفحه با عبارات منظم هم‌خوانی داشت، منوی ترجمه را نشان نده.';
+$lang['dropdown'] = 'استفاده از یک لیست کشویی برای نمایش ترجمه (توصیه شده برای بیشتر از ۵ زبان)';
+$lang['translateui'] = 'باید زبان رابط کاربر در زبان‌های خارجی فضای‌نام تغییر یابد، همچنین؟';
+$lang['redirectstart'] = 'باید صفحه‌ی آغازین به‌طور خودکار به زبانی که فضای‌نام توسط مرورگر کشف شده، تغییرمسیر کند؟';
+$lang['about'] = 'وارد کردن یک نام‌صفحه جایی که ';
+$lang['localabout'] = 'استفاده از نسخه‌های متمرکز شده‌ی درباره صفحه (به جای یک جهانی درباره صفحه)';
+$lang['checkage'] = 'هشدار درمورد ترجمه‌های احتمالا منسوخ شده.';
+$lang['display'] = 'انتخاب این‌که شما چه چیزی را می‌پسندید تا در انتخابگر زبان نمایش داده شود. توجه داشته‌باشید که استفاده از پرچم کشورها برای انتخابگر زبان توسط کارشناسان توصیه نمی‌شود.';
+$lang['copytrans'] = 'کپی‌کردن زبان اصلی متن داخل ویرایشگر وقتی که یک ترجمه جدید آغار می‌شود؟';
+$lang['show_path'] = 'نمایش مسیر در ترجمه‌ی پیدانشده‌ی صفحه‌‌ها؟';
diff --git a/platform/www/lib/plugins/translation/lang/fa/totranslate.txt b/platform/www/lib/plugins/translation/lang/fa/totranslate.txt
new file mode 100644
index 0000000..665eb5b
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/fa/totranslate.txt
@@ -0,0 +1 @@
+تعمیرم کن **این صفحه کامل ترجمه نشده، اکنون. لطفا برای کامل‌شدنش کمک کنید.**\\ //(بعد از پایان ترجمه این بند را از ترجمه حذف کنید)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/fr/lang.php b/platform/www/lib/plugins/translation/lang/fr/lang.php
new file mode 100644
index 0000000..d8a8b9d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/fr/lang.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Vincent Feltz <psycho@feltzv.fr>
+ * @author NicolasFriedli <nicolas@theologique.ch>
+ * @author Gilles-Philippe Morin <gilles.philippe.morin@gmail.com>
+ * @author Schplurtz le Déboulonné <schplurtz@laposte.net>
+ */
+$lang['translations'] = 'Traductions de cette page';
+$lang['outdated'] = 'Cette traduction est plus ancienne que <a href="%s" class="wikilink1">la page originale</a> et est peut-être dépassée.';
+$lang['old'] = 'dépassée';
+$lang['diff'] = 'Voir ce qui a <a href="%s" class="wikilink1">changé</a>.';
+$lang['transloaded'] = 'Le contenu de cette page en %s a été pré-chargé pour faciliter la traduction.<br/> Mais vous pouvez baser votre traduction sur les traductions existantes: %s';
+$lang['menu'] = 'traductions dépassées et manquantes';
+$lang['missing'] = 'Manquante!';
+$lang['current'] = 'à jour';
+$lang['path'] = 'Chemin';
diff --git a/platform/www/lib/plugins/translation/lang/fr/settings.php b/platform/www/lib/plugins/translation/lang/fr/settings.php
new file mode 100644
index 0000000..74869ce
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/fr/settings.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Guy Brand <gb@isis.u-strasbg.fr>
+ * @author Vincent Feltz <psycho@feltzv.fr>
+ * @author NicolasFriedli <nicolas@theologique.ch>
+ * @author Schplurtz le Déboulonné <schplurtz@laposte.net>
+ */
+$lang['translations'] = 'Liste des langues disponibles séparées par des espaces (codes ISO).';
+$lang['translationns'] = 'Si vous souhaitez ne traduire qu\'une certaine catégorie, indiquez-la ici.';
+$lang['skiptrans'] = 'Quand le nom de la page correspond à cette expression régulière, ne pas montrer le menu de traduction.';
+$lang['dropdown'] = 'Utiliser un menu déroulant pour afficher les traductions (recommandé pour plus de 5 langues).';
+$lang['translateui'] = 'Faut-il changer la langue de l\'interface utilisateur dans les catégories traduites ?';
+$lang['redirectstart'] = 'La page de départ devrait-elle rediriger vers une catégorie traduite en utilisant la détection de langue du navigateur ?';
+$lang['about'] = 'Entrez ici un nom de page où la fonctionnalité de traduction est expliquée aux utilisateurs. Elle sera accessible depuis le sélecteur de langue.';
+$lang['localabout'] = 'Utiliser des versions traduites de la page à propos (au lieu d\'une page à propos globale).';
+$lang['checkage'] = 'Avertir de la possibilité de traductions dépassées.';
+$lang['display'] = 'Sélectionnez ce que vous voudriez afficher dans le sélecteur de langue. Notez qu\'utiliser les drapeaux de pays pour la sélection de langue n\'est pas recommandé par les experts en ergonomie.';
+$lang['copytrans'] = 'Copier le texte en langue source dans l\'éditeur quand une nouvelle traduction est lancée ?';
+$lang['show_path'] = 'Montrer les chemins sur la page des traductions manquantes ?';
diff --git a/platform/www/lib/plugins/translation/lang/fr/totranslate.txt b/platform/www/lib/plugins/translation/lang/fr/totranslate.txt
new file mode 100644
index 0000000..3603d4e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/fr/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Cette page n'est pas encore traduite entièrement. Merci de terminer la traduction**\\ //(supprimez ce paragraphe une fois la traduction terminée)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/hr/lang.php b/platform/www/lib/plugins/translation/lang/hr/lang.php
new file mode 100644
index 0000000..80e9399
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/hr/lang.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Davor Turkalj <turki.bsc@gmail.com>
+ */
+$lang['translations'] = 'Prijevodi ove stranice';
+$lang['outdated'] = 'Prijevod ove stranice je stariji od <a href="%s" class="wikilink1">originalne stranice</a> i može biti zastario.';
+$lang['diff'] = 'Pogledajte što je <a href="%s" class="wikilink1">izmijenjeno</a>.';
+$lang['transloaded'] = 'Sadržaj ove stranice u jeziku %s je napunjeno radi lakšeg prevođenja.<br />Ali možete bazirati Vaš prijevod i prema slijedećim raspoloživim prijevodima: %s.';
+$lang['menu'] = 'zastarjeli i nedostajući prijevodi';
+$lang['missing'] = 'Nedostaje!';
+$lang['old'] = 'zastarjelo';
+$lang['current'] = 'ažuran';
+$lang['path'] = 'Staza';
diff --git a/platform/www/lib/plugins/translation/lang/hr/settings.php b/platform/www/lib/plugins/translation/lang/hr/settings.php
new file mode 100644
index 0000000..d1769cc
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/hr/settings.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Davor Turkalj <turki.bsc@gmail.com>
+ */
+$lang['translations'] = 'Razmacima odvojena lista podržanih jezika (ISO oznake).';
+$lang['translationns'] = 'Ako želite prijevode samo ispod određenog imenskog prostora, navedite ga ovdje.';
+$lang['skiptrans'] = 'Kada ime stranice odgovara ovom regularnom izrazu, ne prikazujte meni za prijevode.';
+$lang['dropdown'] = 'Koristi padajuću listu za prikaz prijevoda (preporučeno kada ima više od 5 jezika).';
+$lang['translateui'] = 'Da li da jezik korisničkog sučelja također bude prebačen u jezik stranog imenskog prostora ?';
+$lang['redirectstart'] = 'Da li da se početna strana automatski preusmjeri na imenski prostor koristeći detektirani jezik preglednika?';
+$lang['about'] = 'Unesi naziv stranice gdje je korisnicima pojašnjene mogućnosti prevođenja. Ona će biti povezana na izbornik jezika.';
+$lang['localabout'] = 'Koristi lokaliziranu inačicu "about" stranice (umjesto jedinstvene globalne)';
+$lang['checkage'] = 'Upozori o mogućem zastarjelom prijevodu.';
+$lang['display'] = 'Odaberite što želite da bude prikazano u izborniku jezika. Budite svjesni da korištenje zastava za odabir jezika nije preporučeno od strane eksperata.';
+$lang['copytrans'] = 'Kopirati originalni tekst u editor kada otvorite novi prijevod ?';
+$lang['show_path'] = 'Prikaži stazu do nedostajuće stranice s prijevodom?';
diff --git a/platform/www/lib/plugins/translation/lang/hr/totranslate.txt b/platform/www/lib/plugins/translation/lang/hr/totranslate.txt
new file mode 100644
index 0000000..b49e869
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/hr/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Ova stranica još nije prevedena u cijelosti. Molimo pomognite u njenom prijevodu.**\\ //(uklonite ovaj paragraf jednom kada je prevođenje završeno)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/hu/lang.php b/platform/www/lib/plugins/translation/lang/hu/lang.php
new file mode 100644
index 0000000..6a576ff
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/hu/lang.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Marina Vladi <deldadam@gmail.com>
+ */
+$lang['translations'] = 'Oldal fordításai';
+$lang['outdated'] = 'A fordítás régebbi, mint az <a href="%s" class="wikilink1">eredeti oldal</a>, ezért lehet, hogy már elavult.';
+$lang['diff'] = 'Módosítások <a href="%s" class="wikilink1">megtekintése</a>.';
+$lang['transloaded'] = 'Az oldal tartalmának %s nyelvi fordítását előre betöltöttem a könnyebb módosítás érdekében.<br />Ugyanakkor a fordítást elvégezhetjük a már létező %s fordítás alapján is.';
diff --git a/platform/www/lib/plugins/translation/lang/hu/settings.php b/platform/www/lib/plugins/translation/lang/hu/settings.php
new file mode 100644
index 0000000..5a27108
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/hu/settings.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Marina Vladi <deldadam@gmail.com>
+ */
+$lang['translations'] = 'Szóközzel elválasztott lista a nyelvi fordításokról (ISO-kódokkal).';
+$lang['translationns'] = 'Ha csak egy bizonyos névtér alatt lévő fordítást szeretnénk, tegyük ide.';
+$lang['skiptrans'] = 'Ha az oldal neve illeszkedik ehhez a reguláris kifejezéshez, ne jelenjen meg a fordítások menüje.';
+$lang['dropdown'] = 'Legördülő lista használata a fordításokhoz (5 nyelvnél több esetén javasolt).';
+$lang['translateui'] = 'Módosuljon a felhasználói felület nyelve is idegen nyelvi névterek alatt?';
+$lang['redirectstart'] = 'Átirányítsuk automatikusan a kezdőoldalt abba a nyelvi névtérbe, amely nyelv a böngészőben van beállítva?';
+$lang['about'] = 'Itt adhatjuk meg annak az oldalnak a nevét, amelyen a fordítási lehetőségeket ismertetjük a felhasználókkal. Erre fog hivatkozni a nyelvkiválasztó képernyőelem.';
+$lang['localabout'] = 'A névjegy oldal fordított változátanak használata (a globális névjegy oldal helyett).';
+$lang['checkage'] = 'Figyelmeztetés az esetlegesen elavult fordításokra.';
+$lang['display'] = 'Válasszuk ki, mi jelenjen meg a nyelvi kiválasztó képernyőelemében. Jegyezzük meg: az országzászlókat nem javasolják a használhatósági szakértők.';
+$lang['copytrans'] = 'Átmásoljuk az eredeti nyelvi szöveget a szövegszerkesztőbe új fordítás indításakor?';
diff --git a/platform/www/lib/plugins/translation/lang/hu/totranslate.txt b/platform/www/lib/plugins/translation/lang/hu/totranslate.txt
new file mode 100644
index 0000000..e072d8a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/hu/totranslate.txt
@@ -0,0 +1 @@
+JAVÍTANDÓ **Az oldal még nincs teljesen lefordítva. Kérjük, segítsen a befejezésében!**\\ //(Töröljük ezt a bekezdést a fordítás elkészültekor.)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/it/lang.php b/platform/www/lib/plugins/translation/lang/it/lang.php
new file mode 100644
index 0000000..23d3bd6
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/it/lang.php
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Torpedo <dgtorpedo@gmail.com>
+ */
+$lang['translations'] = 'Traduzioni di questa pagina';
+$lang['outdated'] = 'Questa traduzione è più vecchia di quella della <a href="%s" class="wikilink1">pagina originale</a> è potrebbe essere superata.';
+$lang['diff'] = 'Vedi cosa è <a href="%s" class="wikilink1">cambiato</a>.';
diff --git a/platform/www/lib/plugins/translation/lang/it/settings.php b/platform/www/lib/plugins/translation/lang/it/settings.php
new file mode 100644
index 0000000..775fbf7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/it/settings.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Diego Pierotto <ita.translations@tiscali.it>
+ * @author Sebastiano Pistore <olatusrooc@virgilio.it>
+ * @author Sebastiano Pistore <sebastiano.pistore.info@aol.com>
+ * @author OlatusRooc <olatusrooc@virgilio.it>
+ */
+$lang['translations'] = 'Elenco delle lingue di traduzione separati da spazi (codici ISO). Non includere la lingua predefinita';
+$lang['translationns'] = 'Scrivi qui solo se vuoi le traduzioni all\'interno di una certa categoria.';
+$lang['skiptrans'] = 'Quando i nomi delle pagine corrispondono a questa espressione regolare non mostrare il menu di traduzione.';
+$lang['dropdown'] = 'Utilizza un menu a tendina per visualizzare le traduzioni (consigliato quando si lavora con più di cinque lingue).';
+$lang['translateui'] = 'Vuoi che anche la lingua dell\'interfaccia utente sia modificata in categorie della stessa lingua?';
+$lang['about'] = 'Inserisci qui una pagina dove la funzione di traduzione viene spiegata agli utenti. Sarà collegata al selettore lingua.';
+$lang['localabout'] = 'Mostra le versioni localizzate della pagina About.';
+$lang['checkage'] = 'Avvisa della possibile presenza di traduzioni obsolete.';
+$lang['copytrans'] = 'Copia nell\'editor il testo in lingua originale quando viene iniziata una nuova traduzione?';
diff --git a/platform/www/lib/plugins/translation/lang/it/totranslate.txt b/platform/www/lib/plugins/translation/lang/it/totranslate.txt
new file mode 100644
index 0000000..e83c52a
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/it/totranslate.txt
@@ -0,0 +1 @@
+FIXME ** Questa pagina non è ancora completamente tradotta. Chi può potrebbe aiutarne il completamento. ** \\ // (Rimuovere questo paragrafo a lavoro completato) // \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/ja/lang.php b/platform/www/lib/plugins/translation/lang/ja/lang.php
new file mode 100644
index 0000000..b438bc6
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/ja/lang.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Hideaki SAWADA <chuno@live.jp>
+ */
+$lang['translations'] = 'このページの翻訳';
+$lang['outdated'] = 'この翻訳は<a href="%s" class="wikilink1">元のページ</a>よりも更新日が古く、内容が古い可能性があります。';
+$lang['diff'] = '<a href="%s" class="wikilink1">変更点</a>を参照して下さい。';
+$lang['transloaded'] = '翻訳し易くするために %s にあるこのページの翻訳内容を事前に読み込みました。<br />以下の既存の翻訳を翻訳の基にすることができます:%s。';
+$lang['menu'] = '古い翻訳と欠落している翻訳';
+$lang['missing'] = '欠落';
+$lang['old'] = '内容が古い';
+$lang['current'] = '最新';
+$lang['path'] = 'パス';
diff --git a/platform/www/lib/plugins/translation/lang/ja/settings.php b/platform/www/lib/plugins/translation/lang/ja/settings.php
new file mode 100644
index 0000000..b8c8899
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/ja/settings.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Hideaki SAWADA <chuno@live.jp>
+ */
+$lang['translations'] = '翻訳言語(ISOコード)のスペース区切り一覧';
+$lang['translationns'] = '特定の名前空間以下のみを翻訳したい場合、名前空間を記入する。';
+$lang['skiptrans'] = 'ページ名がこの正規表現と一致すると、翻訳メニューが表示されません。';
+$lang['dropdown'] = '翻訳を表示するためにドロップダウン一覧を使用する(5言語以上の場合推奨)。';
+$lang['translateui'] = 'ユーザーインターフェイスの言語も、名前空間の言語に切り替えるか?';
+$lang['redirectstart'] = 'ブラウザーの言語設定を利用して、スタートページを各言語の名前空間に自動的にリダイレクトするか?';
+$lang['about'] = '翻訳機能をユーザーに説明するページ名を入力して下さい。言語セレクタからリンクされます。';
+$lang['localabout'] = '(包括的な概要ページの代わりに)翻訳版の概要ページを使用する。';
+$lang['checkage'] = '古い翻訳について警告する。';
+$lang['display'] = '言語セレクタに何を表示するかを選択する。言語選択に国旗を使用することをユーザビリティ専門家は奨励しないので注意してください。';
+$lang['copytrans'] = '新しく翻訳を開始する時、エディタに元の言語の文章をコピーしますか?';
+$lang['show_path'] = '欠落している翻訳ページのパスを表示します。';
diff --git a/platform/www/lib/plugins/translation/lang/ja/totranslate.txt b/platform/www/lib/plugins/translation/lang/ja/totranslate.txt
new file mode 100644
index 0000000..05ac184
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/ja/totranslate.txt
@@ -0,0 +1 @@
+FIXME **このページはまだ完全には、翻訳されません。翻訳の完了を支援して下さい。**\\ //(翻訳が完了したらこの段落を削除して下さい)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/ko/lang.php b/platform/www/lib/plugins/translation/lang/ko/lang.php
new file mode 100644
index 0000000..d99d64d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/ko/lang.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Myeongjin <aranet100@gmail.com>
+ */
+$lang['translations'] = '이 문서의 번역';
+$lang['outdated'] = '이 번역은 <a href="%s" class="wikilink1">원래 문서</a>보다 오래되었고 오래된 번역일 수 있습니다.';
+$lang['diff'] = '무엇이 <a href="%s" class="wikilink1">바뀌었는지</a> 보세요.';
+$lang['transloaded'] = '%s에 있는 이 문서의 번역의 내용을 쉽게 번역하기 위해 미리 불러왔습니다.<br />하지만 다음 기존 번역에 당신의 번역을 바탕으로 할 수 있습니다: %s.';
+$lang['menu'] = '오래되었고 없는 번역';
+$lang['missing'] = '없음!';
+$lang['old'] = '오래됨';
+$lang['current'] = '최신';
+$lang['path'] = '경로';
diff --git a/platform/www/lib/plugins/translation/lang/ko/settings.php b/platform/www/lib/plugins/translation/lang/ko/settings.php
new file mode 100644
index 0000000..ccf6d1c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/ko/settings.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Myeongjin <aranet100@gmail.com>
+ */
+$lang['translations'] = '번역 언어의 공백으로 구분한 목록 (ISO 코드).';
+$lang['translationns'] = '특정 이름공간에 따라 번역을 원하면, 여기에 넣으세요.';
+$lang['skiptrans'] = '문서 이름이 정규 표현식과 일치하면, 번역 메뉴를 보여주지 마세요.';
+$lang['dropdown'] = '번역을 표시할 드롭다운 목록을 사용합니다. (5개 이상의 언어에 권장)';
+$lang['translateui'] = '사용자 인터페이스의 언어도 외국어 이름공간으로 전환해야 합니까?';
+$lang['redirectstart'] = '시작 문서가 자동으로 브라우저 언어 감지를 사용해 언어 이름공간으로 넘겨줘야 합니까?';
+$lang['about'] = '사용자에게 설명할 번역 기능이 어디에 있는지 여기에 문서 이름을 입력하세요.';
+$lang['localabout'] = '(하나의 전역 소개 문서 대신) 소개 문서의 지역화된 버전을 사용합니다.';
+$lang['checkage'] = '가능하면 오래된 번역에 대해 경고합니다.';
+$lang['display'] = '언어 선택기에 보여주고 싶은 것을 선택하세요. 언어 선택에 국기를 사용하는 것은 사용성 전문가에게 권장하지 않음을 참고하세요.';
+$lang['copytrans'] = '새 번역을 시작할 때 편집기에 원래 언어 문장을 복사하겠습니까?';
+$lang['show_path'] = '없는 번역 문서에서의 경로를 보여줄까요?';
diff --git a/platform/www/lib/plugins/translation/lang/ko/totranslate.txt b/platform/www/lib/plugins/translation/lang/ko/totranslate.txt
new file mode 100644
index 0000000..9a19833
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/ko/totranslate.txt
@@ -0,0 +1 @@
+FIXME **이 문서는 아직 완전히 번역되지 않았습니다. 번역을 완료하는 데 도와주세요.**\\ //(번역을 마치면 이 단락을 지우세요)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/langnames.txt b/platform/www/lib/plugins/translation/lang/langnames.txt
new file mode 100644
index 0000000..90992ee
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/langnames.txt
@@ -0,0 +1,188 @@
+# Native language names
+# extracted from http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+
+aa Afaraf
+ab Аҧсуа
+ae Avesta
+af Afrikaans
+ak Akan
+am አማርኛ
+an Aragonés
+ar |العربية
+as অসমীয়া
+av Авар мацӀ
+ay Aymar aru
+az Azərbaycan dili
+ba Башҡорт теле
+be Беларуская
+bg Български език
+bh भोजपुरी
+bi Bislama
+bm Bamanankan
+bn বাংলা
+bo བོད་ཡིག
+br Brezhoneg
+bs Bosanski Jezik
+ca Català
+ce Нохчийн Мотт
+ch Chamoru
+co Corsu
+cr ᓀᐦᐃᔭᐍᐏᐣ
+cs Česky
+cu Ѩзыкъ Словѣньскъ
+cv Чӑваш Чӗлхи
+cy Cymraeg
+da Dansk
+de Deutsch
+dv ދިވެހި
+dz རྫོང་ཁ
+ee Eʋegbe
+el Ελληνικά
+en English
+eo Esperanto
+es Español
+et Eesti
+eu Euskara
+fa فارسی
+ff Fulfulde
+fi Suomi
+fj Vosa Vakaviti
+fo Føroyskt
+fr Français
+fy Frysk
+ga Gaeilge
+gd Gaelic
+gl Galego
+gn Avañe'ẽ
+gu ગુજરાતી
+gv Gaelg, Gailck
+ha هَوُسَ
+he עברית
+hi हिन्दी, हिंदी
+ho Hiri Motu
+hr Hrvatski
+ht Kreyòl Ayisyen
+hu Magyar
+hy Հայերեն
+hz Otjiherero
+ia Interlingua
+id Bahasa Indonesia
+ie Interlingue
+ig Igbo
+ii ꆇꉙ
+ik Iñupiaq
+io Ido
+is Íslenska
+it Italiano
+iu ᐃᓄᒃᑎᑐᑦ
+ja 日本語
+jv Basa Jawa
+ka ქართული
+kg KiKongo
+ki Gĩkũyũ
+kj Kuanyama
+kk Қазақ тілі
+kl kalaallisut
+km ភាសាខ្មែរ
+kn ಕನ್ನಡ
+ko 한국어
+kr Kanuri
+ks कश्मीरी}}
+ku Kurdî
+kv Коми Кыв
+kw Kernewek
+ky Кыргыз Тили
+la Latine
+lb Lëtzebuergesch
+lg Luganda
+li Limburgs
+ln Lingála
+lo ພາສາລາວ
+lt Lietuvių Kalba
+lv Latviešu Valoda
+mg Malagasy Fiteny
+mh Kajin M̧ajeļ
+mi Te Reo Māori
+mk Македонски Јазик
+ml മലയാളം
+mn Монгол
+mr मराठी
+ms بهاس ملايو
+mt Malti
+my ဗမာစာ
+na Ekakairũ Naoero
+nb Norsk bokmål
+nd isiNdebele
+ne नेपाली
+ng Owambo
+nl Nederlands
+nn Norsk nynorsk
+no Norsk
+nr IsiNdebele
+nv Diné bizaad
+ny ChiCheŵa
+oc Occitan
+oj ᐊᓂᔑᓈᐯᒧᐎᓐ
+om Afaan Oromoo
+or ଓଡ଼ିଆ
+os Ирон æвзаг
+pa ਪੰਜਾਬੀ,
+pi पाऴि
+pl Polski
+ps پښتو
+pt Português
+pt-br Português
+qu Runa Simi
+rm Rumantsch Grischun
+rn KiRundi
+ro Română
+ru Русский
+rw Ikinyarwanda
+sa संस्कृतम्
+sc Sardu
+sd सिन्धी}}
+se Davvisámegiella
+sg Yângâ Tî Sängö
+si සිංහල
+sk Slovenčina
+sl Slovenščina
+sm Gagana fa'a Samoa
+sn ChiShona
+so Soomaaliga
+sq Shqip
+sr Српски Језик
+ss SiSwati
+st Sesotho
+su Basa Sunda
+sv Svenska
+sw Kiswahili
+ta தமிழ்
+te తెలుగు
+tg Тоҷикӣ
+th ไทย
+ti ትግርኛ
+tk Türkmen
+tl Wikang Tagalog
+tn Setswana
+to Faka Tonga
+tr Türkçe
+ts Xitsonga
+tt Татарча
+tw Twi
+ty Reo Mā`ohi
+ug Uyƣurqə
+uk Українська
+ur اردو
+uz O'zbek
+ve Tshivenḓa
+vi Tiếng Việt
+vo Volapük
+wa Walon
+wo Wollof
+xh IsiXhosa
+yi ייִדיש
+yo Yorùbá
+za Saɯ cueŋƅ
+zh 中文
+zh-tw 繁體中文
+zu IsiZulu
diff --git a/platform/www/lib/plugins/translation/lang/lv/lang.php b/platform/www/lib/plugins/translation/lang/lv/lang.php
new file mode 100644
index 0000000..af85894
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/lv/lang.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Aivars Miška <allefm@gmail.com>
+ */
+$lang['translations'] = 'Citās valodās';
+$lang['outdated'] = 'Šis tulkojums ir vecāks par <a href="%s" class="wikilink1">oriģinālo lapu</a> un varbūt ir novecojis.';
+$lang['diff'] = 'Redzēt, ka ir <a href="%s" class="wikilink1">mainījies</a>.';
+$lang['transloaded'] = 'Vieglākai tulkošanai ir ielādēts lapas saturs no %s .<br />Bet varat balstīties arī uz šādiem tulkojumiem: %s.';
diff --git a/platform/www/lib/plugins/translation/lang/lv/settings.php b/platform/www/lib/plugins/translation/lang/lv/settings.php
new file mode 100644
index 0000000..1b61b46
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/lv/settings.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Aivars Miška <allefm@gmail.com>
+ * @author Aivars Miška <allefm@gmail.com>
+ */
+$lang['translations'] = 'Ar atstarpēm atdalīts tulkojumu valodu saraksts (ISO kodi). Izņemot noklusēto valodu.';
+$lang['translationns'] = 'Ja tulkojumus vajag tikai noteiktā nodaļā, ieraksti to šeit.';
+$lang['skiptrans'] = 'Ja lapas nosaukums atbilst regulārajai izteiksmei, tulkošanas izvēlni nerādīt.';
+$lang['dropdown'] = 'Lietot izkrītošo izvēlni tulkojumu parādīšanai (ieteikt, ja ir vairāk par 5 valodām). ';
+$lang['translateui'] = 'Vai svešvalodu nodaļās jāpārslēdz arī lietotāja sakarnes valoda?';
+$lang['redirectstart'] = 'Vai sākuma lapai automātiski jāpārslēdzas atkarībā no pārlūkprogrammas noteiktās valodas?';
+$lang['about'] = 'Ieraksti šeit lapu, kurā lietotājiem izskaidrotas tulkošas iespējas. Tā tiks piesaistīta valodu izvēlei.';
+$lang['localabout'] = 'Lietot "par" lapas lokalizēto versiju, nevis globālo "par" lapu.';
+$lang['checkage'] = 'Brīdināt pa varbūt novecojušiem tulkojumiem. ';
+$lang['display'] = 'Norādiet, ko lietot valodas izvēlei. Ņemiet vērā, ka valodām izmantot valstu karogus neiesaka.';
+$lang['copytrans'] = 'Sākot tulkojumu, iekopēt redaktorā oriģināltekstu?';
diff --git a/platform/www/lib/plugins/translation/lang/lv/totranslate.txt b/platform/www/lib/plugins/translation/lang/lv/totranslate.txt
new file mode 100644
index 0000000..c46c14e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/lv/totranslate.txt
@@ -0,0 +1 @@
+IZLABO **Lapa nav pilnībā pārtulkota. Lūdzu palīdzi pabeigt tulkojumu!** \\ //(Izdzēs šo rindkopu, kad tulkojums pabeigts!)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/nl/lang.php b/platform/www/lib/plugins/translation/lang/nl/lang.php
new file mode 100644
index 0000000..1864f07
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/nl/lang.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Gerrit Uitslag <klapinklapin@gmail.com>
+ * @author Marcel Bachus <marcel.bachus@ziggo.nl>
+ */
+$lang['translations'] = 'Vertaling van deze pagina';
+$lang['outdated'] = 'Deze vertaling is ouder dan de <a href="%s" class="wikilink1">originele pagina</a> en kan verouderd zijn.';
+$lang['diff'] = 'Kijk wat er is <a href="%s" class="wikilink1">veranderd</a>.';
+$lang['transloaded'] = 'De inhoud van vertaling van deze pagina in %s is al geladen om vertalen makkelijker te maken.<br />Maar je kunt je vertaling ook baseren op één van de volgende bestaande vertalingen: %s.';
+$lang['menu'] = 'verouderde of missende vertaling';
+$lang['missing'] = 'Niet gevonden!';
+$lang['old'] = 'verouderd';
+$lang['current'] = 'laatste stand van zaken';
+$lang['path'] = 'Pad';
diff --git a/platform/www/lib/plugins/translation/lang/nl/settings.php b/platform/www/lib/plugins/translation/lang/nl/settings.php
new file mode 100644
index 0000000..88dbe98
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/nl/settings.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Gerrit <klapinklapin@gmail.com>
+ * @author Gerrit Uitslag <klapinklapin@gmail.com>
+ * @author Marcel Bachus <marcel.bachus@ziggo.nl>
+ */
+$lang['translations'] = 'Spatiegescheiden lijst van vertalingen (ISO codes).';
+$lang['translationns'] = 'Als je alleen vertalingen in een bepaalde namespace wenst, plaatst die dan hier.';
+$lang['skiptrans'] = 'Wanneer een paginanaam overeenstemt met deze reguliere expressie, wordt het vertaalmenu niet getoond.';
+$lang['dropdown'] = 'Gebruik een dropdownlijst om vertalingen weer te geven (aanbevolen bij meer dan 5 talen).';
+$lang['translateui'] = 'Moet de taal van de gebruikersinterface ook veranderen naar de taal van vertaalde namespace?';
+$lang['redirectstart'] = 'Moet de startpagina automatisch doorverwijzen naar de namespace van de taal die de taaldetectie van de browser doorgeeft?';
+$lang['about'] = 'Geef een paginanaam waar de vertaalfunctie wordt uitgelegd voor je gebruikers. Het zal worden gelinkt vanuit de talenkiezer.';
+$lang['localabout'] = 'Gebruik vertaalde versies van bovengenoemde vertalingsuitlegpagina (in plaats van één globale uitlegpagina).';
+$lang['checkage'] = 'Waarschuw voor mogelijk gedateerde vertalingen.';
+$lang['display'] = 'Selecteer wat je wil zien in de talenkiezer. Let op dat het gebruik van landenvlaggen in de talenkiezer niet altijd gebruiksvriendelijkheid is.';
+$lang['copytrans'] = 'De tekst in de oorspronkelijke taal naar het bewerkvenster kopiëren als er een nieuwe vertaling wordt begonnen.';
+$lang['show_path'] = 'Toon het pad naar de missende vertalings pagina?';
diff --git a/platform/www/lib/plugins/translation/lang/nl/totranslate.txt b/platform/www/lib/plugins/translation/lang/nl/totranslate.txt
new file mode 100644
index 0000000..d5f8cee
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/nl/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Deze pagina is nog niet volledig vertaald. Help alsjeblieft de vertaling compleet te maken.**\\ //(verwijder deze paragraaf als de vertaling is voltooid)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/pt-br/lang.php b/platform/www/lib/plugins/translation/lang/pt-br/lang.php
new file mode 100644
index 0000000..c6fa3f7
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/pt-br/lang.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Felipe Castro <fefcas@gmail.com>
+ * @author Edney Rossi <edneyrossi@gmail.com>
+ */
+$lang['translations'] = 'Traduções desta página';
+$lang['outdated'] = 'Esta tradução é mais antiga que a <a href="%s" class="wikilink1"> página original </a> e pode estar desatualizada.';
+$lang['diff'] = 'Veja o que foi <a href="%s" class="wikilink1">mudado</a>.';
+$lang['transloaded'] = 'O conteúdo da tradução desta página em %s foi pré-carregado para facilitar o trabalho.< br/>Mas você pode basear sua tradução nas seguintes traduções existentes: %s.';
+$lang['menu'] = 'traduções desatualizadas e inexistentes';
+$lang['missing'] = 'Inexistente!';
+$lang['old'] = 'desatualizado';
+$lang['current'] = 'atualizada';
+$lang['path'] = 'Caminho';
diff --git a/platform/www/lib/plugins/translation/lang/pt-br/settings.php b/platform/www/lib/plugins/translation/lang/pt-br/settings.php
new file mode 100644
index 0000000..e053e13
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/pt-br/settings.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Paulino Michelazzo <paulino@michelazzo.com.br>
+ * @author Felipe Castro <fefcas@gmail.com>
+ * @author Edney Rossi <edneyrossi@gmail.com>
+ */
+$lang['translations'] = 'Lista de idiomas separados por espaço (códigos ISO).';
+$lang['translationns'] = 'Se você deseja traduções apenas para certo idioma, coloque-o aqui.';
+$lang['skiptrans'] = 'Quando o nome-de-página estiver de acordo com esta expressão regular, não mostre o menu de tradução.';
+$lang['dropdown'] = 'Usar listagem desdobrada para mostrar as traduções (recomendado para mais que 5 línguas).';
+$lang['translateui'] = 'A interface do usuário deve ser trocada para o idioma, também?';
+$lang['redirectstart'] = 'A página inicial deve redirecionar automaticamente para o "namespace" da língua usando a detecção de idiomas no navegador?';
+$lang['about'] = 'Digite um nome de página aqui onde o recurso de tradução é explicado para seus usuários. Ele será vinculado a partir do seletor de idioma.';
+$lang['localabout'] = 'Usar versões localizadas da página "a respeito de" (em vez de uma página global "a respeito de").';
+$lang['checkage'] = 'Avisar sobre possíveis traduções desatualizadas.';
+$lang['display'] = 'Selecionar o que você gostaria de mostrar no seletor de línguas. Note que usar bandeirinhas de países para selecionar línguas não é recomendado por especialistas em usabilidade.';
+$lang['copytrans'] = 'Copiar o texto da língua original no editor quando começar uma nova tradução?';
+$lang['show_path'] = 'Mostrar o caminho na página com tradução inexistente?';
diff --git a/platform/www/lib/plugins/translation/lang/pt-br/totranslate.txt b/platform/www/lib/plugins/translation/lang/pt-br/totranslate.txt
new file mode 100644
index 0000000..5329cda
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/pt-br/totranslate.txt
@@ -0,0 +1 @@
+FIXME ** Esta página não está completamente traduzida ainda. Por favor ajude a completar sua tradução.**\\ //(remova este parágrafo assim que a tradução tenha terminado)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/pt/lang.php b/platform/www/lib/plugins/translation/lang/pt/lang.php
new file mode 100644
index 0000000..a17ab35
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/pt/lang.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author André Neves <drakferion@gmail.com>
+ * @author Alfredo Silva <alfredo.silva@sky.com>
+ */
+$lang['translations'] = 'Traduções para esta página';
+$lang['outdated'] = 'Esta tradução é mais antiga do que a <a href="%s" class="wikilink1">página original</a> e poderá estar desatualizada.';
+$lang['diff'] = 'Veja o que foi <a href="%s" class="wikilink1">alterado</a>.';
diff --git a/platform/www/lib/plugins/translation/lang/pt/settings.php b/platform/www/lib/plugins/translation/lang/pt/settings.php
new file mode 100644
index 0000000..4cdd114
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/pt/settings.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author André Neves <drakferion@gmail.com>
+ * @author Alfredo Silva <alfredo.silva@sky.com>
+ */
+$lang['translations'] = 'Lista de idiomas de tradução (códigos ISO) separada por espaço.';
+$lang['translationns'] = 'Se pretender apenas as traduções abaixo de um determinado espaço de nome, coloque-as aqui.';
+$lang['skiptrans'] = 'Quando o nome da página corresponder com esta expressão regular, não mostrar o menu de tradução.';
+$lang['dropdown'] = 'Utilizar uma lista de menu para exibir as traduções (recomendado para mais de 5 idiomas).';
+$lang['translateui'] = 'O idioma da interface do utilizador também deverá ser alterado nos espaços de nome do idioma estrangeiro?';
+$lang['redirectstart'] = 'A página inicial deve redirecionar automaticamente para um espaço de nome do idioma utilizando a deteção de idioma do navegador?';
+$lang['about'] = 'Insira aqui um nome de página onde a funcionalidade de tradução é explicada aos seus utilizadores. O seletor de língua terá uma ligação para lá.';
+$lang['localabout'] = 'Utilizar versões localizadas da página sobre (em vez de uma página global sobre).';
+$lang['checkage'] = 'Avisar sobre as possíveis traduções desatualizadas.';
+$lang['display'] = 'Selecione o que gostaria de ver mostrado no seletor de linguagem. Note que usar bandeiras de países para seleção de linguagem não é recomendado por peritos de usabilidade.';
+$lang['copytrans'] = 'Copiar o texto do idioma original no editor quando iniciar uma nova tradução?';
diff --git a/platform/www/lib/plugins/translation/lang/ru/lang.php b/platform/www/lib/plugins/translation/lang/ru/lang.php
new file mode 100644
index 0000000..dc85abc
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/ru/lang.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Aleksandr Selivanov <alexgearbox@gmail.com>
+ * @author Vasilyy Balyasnyy <v.balyasnyy@gmail.com>
+ * @author Anotheroneuser <w20151222@ya.ru>
+ */
+$lang['translations'] = 'Перевод этой страницы';
+$lang['outdated'] = 'Этот перевод старее, чем <a href="%s" class="wikilink1">оригинальная страница</a>, и может быть неактуальным.';
+$lang['diff'] = 'Смотрите, что <a href="%s" class="wikilink1">было изменено</a>.';
+$lang['transloaded'] = 'Содержание перевода этой страницы в %s было предварительно загружено для упрощения перевода.<br />Но вы можете переводить на основе следующего существующего перевода: %s.';
+$lang['menu'] = 'Устаревшие или отсутствующие переводы';
+$lang['missing'] = 'Отсутствует! ';
+$lang['old'] = 'устарело';
+$lang['current'] = 'обновить (привести в актуальное состояние)';
+$lang['path'] = 'Путь';
diff --git a/platform/www/lib/plugins/translation/lang/ru/settings.php b/platform/www/lib/plugins/translation/lang/ru/settings.php
new file mode 100644
index 0000000..a974b68
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/ru/settings.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Oleksiy Zagorskyi <zalex_ua@i.ua>
+ * @author Aleksandr Selivanov <alexgearbox@gmail.com>
+ * @author Anotheroneuser <w20151222@ya.ru>
+ */
+$lang['translations'] = 'Список поддерживаемых языков перевода (двухсимвольные коды ISO). Разделите значения пробелами.';
+$lang['translationns'] = 'Если вы хотите перевести только определённое пространство имён, тогда впишите здесь его имя.';
+$lang['skiptrans'] = 'Если имя страницы соответствует этому регулярному выражению, тогда не отображать меню перевода.';
+$lang['dropdown'] = 'Использовать выпадающий список для отображения доступных переводов (рекомендуется, если более 5 переводов)';
+$lang['translateui'] = 'Должен ли язык интерфейса пользователя также переключаться согласно языку пространства имён?';
+$lang['redirectstart'] = 'Должна ли стартовая страница автоматически перенаправляться на пространство имён языка, используя автоопределение языка браузера?';
+$lang['about'] = 'Введите здесь имя страницы, на которой будут разъяснены функции перевода для ваших пользователей. Она будет связана с выбором языка.';
+$lang['localabout'] = 'Использовать локализованную версию страницы разъяснений (вместо одной глобальной страницы разъяснений).';
+$lang['checkage'] = 'Отображать предупреждение о возможной неактуальности перевода?';
+$lang['display'] = 'Выберите, что бы вы хотели видеть в поле выбора языков. Имейте в виду, что использование изображения государственного флага в поле выбора языков не было рекомендовано экспертами в области потребительского удобства. ';
+$lang['copytrans'] = 'Копировать текст оригинала в окно редактирования при создании нового перевода?';
+$lang['show_path'] = 'Показывать путь на непереведённых страницах? ';
diff --git a/platform/www/lib/plugins/translation/lang/ru/totranslate.txt b/platform/www/lib/plugins/translation/lang/ru/totranslate.txt
new file mode 100644
index 0000000..b34588e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/ru/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Эта страница пока что не переведена полностью. Пожалуйста, помогите завершить перевод.**\\ //(Сотрите это сообщение по окончании перевода.)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/sl/lang.php b/platform/www/lib/plugins/translation/lang/sl/lang.php
new file mode 100644
index 0000000..be8a195
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/sl/lang.php
@@ -0,0 +1,9 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ */
+$lang['translations'] = 'Prevod trenutne strani';
+$lang['outdated'] = 'Prevod je starejši od <a href="%s" class="wikilink1">izvorne strani</a> in je zato lahko zastarel.';
+$lang['diff'] = 'Oglejte si <a href="%s" class="wikilink1">spremembe</a>.';
diff --git a/platform/www/lib/plugins/translation/lang/sl/settings.php b/platform/www/lib/plugins/translation/lang/sl/settings.php
new file mode 100644
index 0000000..6bdcff4
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/sl/settings.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Matej Urbančič <mateju@svn.gnome.org>
+ */
+$lang['translations'] = 'Space separated list of translation languages (ISO codes).';
+$lang['translationns'] = 'If you only want translations below a certain namespace, put it here.';
+$lang['skiptrans'] = 'When the pagename matches this regular expression, don\'t show the translation menu.';
+$lang['dropdown'] = 'Use a dropdown list to display the translations (recommended for more than 5 languages).';
+$lang['translateui'] = 'Should the language of the user interface be switched in foreign language namespaces, too?';
+$lang['redirectstart'] = 'Should the start page automatically redirect into a language namespace using browser language detection?';
+$lang['about'] = 'Enter a pagename here where the translation feature is explained for your users. It will be linked from the language selector.';
+$lang['localabout'] = 'Uporabi prevedeno različico strani o vstavku (namesto splošne strani).';
+$lang['checkage'] = 'Opozori o zastarelem prevodu.';
+$lang['display'] = 'Izbor možnosti za prikaz jezika v izbirniku jezika. Izbor zastave jezika v izbiri ni priporočen.';
diff --git a/platform/www/lib/plugins/translation/lang/sv/lang.php b/platform/www/lib/plugins/translation/lang/sv/lang.php
new file mode 100644
index 0000000..6f40f58
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/sv/lang.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Tor Härnqvist <tor@harnqvist.se>
+ */
+$lang['translations'] = 'Översättningar av denna sida';
+$lang['outdated'] = 'Denna översättning är äldre än <a href="%s" class="wikilink1">orginalsidan</a> och kan vara daterad.';
+$lang['diff'] = 'Se vad som har <a href="%s" class="wikilink1">ändrats</a>.';
+$lang['transloaded'] = 'Innehållet på denna sidas översättning på %s har blivit förinläst för enklare översättning.<br />Du kan dock basera din översättning på följande redan existerande översättningar: %s.';
+$lang['menu'] = 'daterade och saknade översättningar';
+$lang['missing'] = 'Saknas!';
+$lang['old'] = 'daterad';
+$lang['current'] = 'uppdaterad';
+$lang['path'] = 'Sökväg';
diff --git a/platform/www/lib/plugins/translation/lang/sv/settings.php b/platform/www/lib/plugins/translation/lang/sv/settings.php
new file mode 100644
index 0000000..4cd67f4
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/sv/settings.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Tor Härnqvist <tor@harnqvist.se>
+ */
+$lang['translations'] = 'Blankstegsseparerad lista över översatta språk (ISO-koder).';
+$lang['translationns'] = 'Om du bara önskar översättningar under en specifik namnrymd, placera den här.';
+$lang['skiptrans'] = 'När sidnamnet matchar detta reguljära uttryck, visa ej översättningsmenyn.';
+$lang['dropdown'] = 'Använd en rullista för att visa översättningarna (rekommenderat till fem eller fler språk).';
+$lang['translateui'] = 'Skall språket för användargränssnittet växlas även för namnrymder på främmande språk?';
+$lang['redirectstart'] = 'Skall startsidan automatiskt omdirigeras till en språkspecifik namnrymd baserat på webbläsarens språkdetektion?';
+$lang['about'] = 'Fyll i ett sidnamn här där översättningsfunktionen förklaras för dina användare. Sidan kommer att länkas från språkväljaren.';
+$lang['localabout'] = 'Använd översatt version av "Om"-sidan (istället för en global "Om"-sida).';
+$lang['checkage'] = 'Varna för möjligt daterade översättningar.';
+$lang['display'] = 'Ange vad du önskar visas i menyn för språkval. Vänligen notera att nationsfanor för språkval ej är rekommenderat.';
+$lang['copytrans'] = 'Kopiera texten på originalspråket till textredigeraren när en ny översättning inleds?';
+$lang['show_path'] = 'Visa sökväg på sidan för den saknade översättningen?';
diff --git a/platform/www/lib/plugins/translation/lang/sv/totranslate.txt b/platform/www/lib/plugins/translation/lang/sv/totranslate.txt
new file mode 100644
index 0000000..d4f7a53
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/sv/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Denna sida är inte helt översatt än. Var god hjälp till att avsluta översättningen.**\\ //(ta bort detta stycke när översättningen är färdig)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/tr/lang.php b/platform/www/lib/plugins/translation/lang/tr/lang.php
new file mode 100644
index 0000000..6f8f617
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/tr/lang.php
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author İlker R. Kapaç <irifat@gmail.com>
+ */
+$lang['translations'] = 'Bu sayfanın çevirileri';
+$lang['outdated'] = 'Bu çeviri <a href="%s" class="wikilink1">orjinal sayfadan</a> daha eski tarihli. Dolayısıyla güncel olmayabilir.';
+$lang['diff'] = 'Nelerin değiştiğini görmek için <a href="%s" class="wikilink1">tıklayın</a>.';
diff --git a/platform/www/lib/plugins/translation/lang/tr/settings.php b/platform/www/lib/plugins/translation/lang/tr/settings.php
new file mode 100644
index 0000000..bad9dbc
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/tr/settings.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author İlker R. Kapaç <irifat@gmail.com>
+ */
+$lang['translations'] = 'Tercüme dillerinin listesi. (boşluk ile ayrılmış, ISO kodları)';
+$lang['translationns'] = 'Eğer tercümelerin bir isim alanın (namespace) altında olmasını istiyorsanız, buraya yazın.';
+$lang['skiptrans'] = 'İsim alanı (Namespace) buradaki tanıma uyduğunda, tercüme arayüzünü gösterme.';
+$lang['dropdown'] = 'Dilleri listelemek için açılır arayüz kullan. (5\'ten fazla dil olduğunda kullanılması önerilir)';
+$lang['localabout'] = 'Bir tane genel "hakkında" sayfası kullanmak yerine, yerelleştirilmiş "hakkında" sayfaları kullan. ';
+$lang['checkage'] = 'Eski tarihli tercümeler hakkında uyarı göster.';
+$lang['display'] = 'Dil seçiminde görünmesini istediklerinizi seçin. Lütfen unutmayın, dil seçiminde ülke bayrağı kullanmak, erişilebilirlik uzmanları tarafından tavsiye edilmez.';
+$lang['copytrans'] = 'Yeni tercümeye başlarken orjinal dildeki metin, düzenleme ekranına kopyalansın mı?';
diff --git a/platform/www/lib/plugins/translation/lang/tr/totranslate.txt b/platform/www/lib/plugins/translation/lang/tr/totranslate.txt
new file mode 100644
index 0000000..e281874
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/tr/totranslate.txt
@@ -0,0 +1 @@
+FIXME **Bu sayfanın çevirisi henüz tamamlanmadı. Lütfen çevirinin tamamlanmasına yardımcı olun.**\\ //(Çeviri tamamlandığında bu paragrafı silin)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/uk/lang.php b/platform/www/lib/plugins/translation/lang/uk/lang.php
new file mode 100644
index 0000000..8be6648
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/uk/lang.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Олексій <alexey.furashev@gmail.com>
+ * @author Vitaly <vitaly.balashov@smuzzy.com.ua>
+ */
+$lang['translations'] = 'Переклад цієї сторінки';
+$lang['outdated'] = 'Цей переклад старіший ніж <a href="%s" class="wikilink1">оригінальна сторінка</a> і може бути не актуальним.';
+$lang['diff'] = 'Дивіться що <a href="%s" class="wikilink1">було змінено</a>.';
+$lang['transloaded'] = 'Зміст перекладу цієї сторінки %s був попередньо завантажений для зручності перекладу. <br /> Також можете використовувати наступні переклади: %s.';
+$lang['menu'] = 'застарілі та відсутні переклади';
+$lang['missing'] = 'Відсутній!';
+$lang['old'] = 'застарілий';
+$lang['current'] = 'поточний';
+$lang['path'] = 'Шлях';
diff --git a/platform/www/lib/plugins/translation/lang/uk/settings.php b/platform/www/lib/plugins/translation/lang/uk/settings.php
new file mode 100644
index 0000000..51ec6d4
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/uk/settings.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * Ukrainian language file
+ *
+ * @author Олексій <alexey.furashev@gmail.com>
+ * @author Vitaly <vitaly.balashov@smuzzy.com.ua>
+ * @author Oleksiy Zagorskyi <zalex_ua@i.ua>
+ */
+$lang['translations'] = 'Список підтримуваних мов перекладу (двохсимвольні коди ISO). Розділіть значення комами або пробілами.';
+$lang['translationns'] = 'Якщо ви хочете перекласти тільки визначений Простір імен, тоді впишіть тут його ім\'я.';
+$lang['skiptrans'] = 'Якщо ім\'я сторінки відповідає цьому регулярному виразу, тоді не відображувати меню перекладів.';
+$lang['dropdown'] = 'Використовувати випадаючий список для відображення доступних перекладів (рекомендується, якщо більше 5 перекладів)';
+$lang['translateui'] = 'Чи повинна мова інтерфейсу користувача також перемикатись відповідно до мови Простору імен?';
+$lang['redirectstart'] = 'Чи повинна стартова сторінка автоматично перенаправлятись на Простір імен мови, використовуючи детектекцію мови оглядача?';
+$lang['about'] = 'Введіть тут ім\'я сторінки, на якій буде роз\'яснено функції перекладу для ваших користувачів. Вона буде пов\'язана з вибором мови.';
+$lang['localabout'] = 'Використовувати локалізовану версію сторінки роз\'яснень (замість однієї глобальної сторінки роз\'яснень).';
+$lang['checkage'] = 'Відображувати попередження про можливу не актуальність перекладу сторінок?';
+$lang['display'] = 'Оберіть що б ви хотіли відображувати в перемикачі мов. Примітка: використовувати прапор країни для перемикача мов не рекомендується експертами по зручності використання інтерфейсу.';
+$lang['copytrans'] = 'Зкопіювати текст мовою оригіналу до редактора на початку нового перекладу?';
+$lang['show_path'] = 'Показати шлях на відсутній сторінці перекладу?';
diff --git a/platform/www/lib/plugins/translation/lang/uk/totranslate.txt b/platform/www/lib/plugins/translation/lang/uk/totranslate.txt
new file mode 100644
index 0000000..ee2d980
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/uk/totranslate.txt
@@ -0,0 +1 @@
+FIXME ** Ця сторінка ще не повністю переведена. Будь-ласка, допоможіть завершити переклад. ** \\ //(видаліть цей абзац після завершення перекладу)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/lang/zh-tw/lang.php b/platform/www/lib/plugins/translation/lang/zh-tw/lang.php
new file mode 100644
index 0000000..7b9f694
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/zh-tw/lang.php
@@ -0,0 +1,6 @@
+<?php
+
+$lang['translations'] = '本頁之翻譯';
+$lang['outdated'] = '這份翻譯較<a href="%s" class="wikilink1">原始頁面</a>舊,可能已過時。';
+$lang['diff'] = '檢視<a href="%s" class="wikilink1">變更</a>。';
+
diff --git a/platform/www/lib/plugins/translation/lang/zh-tw/settings.php b/platform/www/lib/plugins/translation/lang/zh-tw/settings.php
new file mode 100644
index 0000000..7cc76dc
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/zh-tw/settings.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Traditional Chinese language file
+ */
+
+$lang['translations'] = '空白分隔的翻譯語言列表 (ISO 碼)。不填預設語言將使用根命名空間。';
+$lang['translationns'] = '如果你只想翻譯某些命名空間,請寫在這裡。';
+$lang['skiptrans'] = '頁面名稱若符合此正規式就不顯示翻譯選單。';
+$lang['dropdown'] = '用下拉式選單顯示 (多於 5 個語言時建議使用)。';
+$lang['translateui'] = '在外語的命名空間時,也轉換使用者介面嗎?';
+$lang['redirectstart'] = '要偵測覽器語言,把開始頁面重新導向到語言的命名空間嗎?';
+$lang['about'] = '用於解釋翻譯機制的頁面名稱,它的連結會出現在語言選單。';
+$lang['localabout'] = '解釋頁使用翻譯版本 (而非一個通用頁)。';
+$lang['checkage'] = '警告可能過時的翻譯。';
+$lang['display'] = '選擇你希望在語言選單中顯示的項目。注意國旗 (flag) 選項是不被易用性專家建議的。';
+
diff --git a/platform/www/lib/plugins/translation/lang/zh/lang.php b/platform/www/lib/plugins/translation/lang/zh/lang.php
new file mode 100644
index 0000000..4aaa2d3
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/zh/lang.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author hfl <huangfeilong@gmail.com>
+ * @author oott123 <ip.192.168.1.1@qq.com>
+ * @author kuma <kuma000@qq.com>
+ */
+$lang['translations'] = '本页面的其他翻译';
+$lang['outdated'] = '翻译跟<a href="%s" class="wikilink1">原始页面</a>比较起来显得有些陈旧,所以可能失效。';
+$lang['diff'] = '查看<a href="%s" class="wikilink1">更新</a>';
+$lang['transloaded'] = '此页面的 %s 已经由 easy translation 预翻译。<br />但你可以以以下现存的语言为基础翻译你的版本。%s';
+$lang['menu'] = '过时的和缺失的翻译';
+$lang['missing'] = '缺失!';
+$lang['old'] = '过时';
+$lang['current'] = '最新的';
+$lang['path'] = '路径';
diff --git a/platform/www/lib/plugins/translation/lang/zh/settings.php b/platform/www/lib/plugins/translation/lang/zh/settings.php
new file mode 100644
index 0000000..6ca6666
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/zh/settings.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author ZDYX <zhangduyixiong@gmail.com>
+ * @author oott123 <ip.192.168.1.1@qq.com>
+ * @author kuma <kuma000@qq.com>
+ */
+$lang['translations'] = '使用空格分隔的翻译语言列表(ISO 码)。请勿填入默认语言。';
+$lang['translationns'] = '如果您只希望本插件作用于某个特定的名称空间,请在这里写上其名称。';
+$lang['skiptrans'] = '当页面名称与此正则匹配时,不要显示翻译菜单。';
+$lang['dropdown'] = '使用下拉列表显示翻译语言(5+语言时建议启用)';
+$lang['translateui'] = '整个用户界面也跟随某个页面的翻译语言而改变吗?';
+$lang['redirectstart'] = '首页是否根据浏览器语言自动切换到相应语言?';
+$lang['about'] = '请在此输入向用户解释翻译功能的页面的名称空间。它的链接将出现在语言选择器上。';
+$lang['localabout'] = '使用本地化的关于页面(而不是一个全局关于页面)。';
+$lang['checkage'] = '警告:可能过时了的翻译。';
+$lang['display'] = '选择你想在选择器中显示什么。注意可用性专家并不推荐使用国旗选择语言。';
+$lang['copytrans'] = '开始新翻译的时候在编辑器中复制原始语言版本?';
+$lang['show_path'] = '缺失的翻译页面上显示路径?';
diff --git a/platform/www/lib/plugins/translation/lang/zh/totranslate.txt b/platform/www/lib/plugins/translation/lang/zh/totranslate.txt
new file mode 100644
index 0000000..aaa32f9
--- /dev/null
+++ b/platform/www/lib/plugins/translation/lang/zh/totranslate.txt
@@ -0,0 +1 @@
+等待修复 **此页面没有被翻译完全。请帮助翻译本页。**\\ //(当全文翻译完时请移除这个段落。)// \ No newline at end of file
diff --git a/platform/www/lib/plugins/translation/manager.dat b/platform/www/lib/plugins/translation/manager.dat
new file mode 100644
index 0000000..29afe7c
--- /dev/null
+++ b/platform/www/lib/plugins/translation/manager.dat
@@ -0,0 +1,2 @@
+downloadurl=https://github.com/splitbrain/dokuwiki-plugin-translation/zipball/master
+installed=Wed, 23 Jan 2019 14:38:38 -0300
diff --git a/platform/www/lib/plugins/translation/plugin.info.txt b/platform/www/lib/plugins/translation/plugin.info.txt
new file mode 100644
index 0000000..9424ec9
--- /dev/null
+++ b/platform/www/lib/plugins/translation/plugin.info.txt
@@ -0,0 +1,8 @@
+# General Plugin Info do not edit
+base translation
+author Andreas Gohr
+email andi@splitbrain.org
+date 2018-08-17
+name Translation Plugin
+desc Supports the easy setup of a multi-language wiki.
+url http://www.dokuwiki.org/plugin:translation
diff --git a/platform/www/lib/plugins/translation/print.css b/platform/www/lib/plugins/translation/print.css
new file mode 100644
index 0000000..c2fd328
--- /dev/null
+++ b/platform/www/lib/plugins/translation/print.css
@@ -0,0 +1 @@
+.dokuwiki div.plugin_translation { display: none }
diff --git a/platform/www/lib/plugins/translation/script.js b/platform/www/lib/plugins/translation/script.js
new file mode 100644
index 0000000..819b80e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/script.js
@@ -0,0 +1,20 @@
+/**
+ * Remove go button from translation dropdown
+ */
+jQuery(function(){
+ var $frm = jQuery('#translation__dropdown');
+ if(!$frm.length) return;
+ $frm.find('input[name=go]').hide();
+ $frm.find('select[name=id]').change(function(){
+ var id = jQuery(this).val();
+ // this should hopefully detect rewriting good enough:
+ var action = $frm.attr('action');
+ if(action.substr(action.length-1) == '/'){
+ var link = action + id;
+ }else{
+ var link = action + '?id=' + id;
+ }
+
+ window.location.href= link;
+ });
+});
diff --git a/platform/www/lib/plugins/translation/style.css b/platform/www/lib/plugins/translation/style.css
new file mode 100644
index 0000000..4ace94d
--- /dev/null
+++ b/platform/www/lib/plugins/translation/style.css
@@ -0,0 +1,114 @@
+.dokuwiki div.plugin_translation {
+ font-size: 95%;
+ padding-right: 0.1em;
+ margin : 0.0em 0 0.3em 0;
+}
+
+/* List */
+
+.dokuwiki div.plugin_translation ul {
+ padding: 0;
+ margin: 0;
+ display: inline-block;
+}
+.dokuwiki div.plugin_translation ul li {
+ float: left;
+ list-style-type: none;
+ padding: 0;
+ margin: 0.2em 0 0 0;
+}
+.dokuwiki div.plugin_translation ul li img {
+ margin: -0.1em 0.2em;
+}
+
+#dokuwiki__footer .plugin_translation ul li a.wikilink1:link,
+#dokuwiki__footer .plugin_translation ul li a.wikilink1:active,
+#dokuwiki__footer .plugin_translation ul li a.wikilink1:visited {
+ background-color: #fff;
+ color: #505050;
+ text-decoration:none;
+ padding: 0.1em 0.4em;
+ margin: 0.1em 0.2em;
+ border: none !important;
+}
+
+#dokuwiki__footer .plugin_translation ul li a.wikilink1:hover {
+ background-color: #eee;
+}
+
+
+#dokuwiki__footer .plugin_translation ul li a.wikilink2:link,
+#dokuwiki__footer .plugin_translation ul li a.wikilink2:hover,
+#dokuwiki__footer .plugin_translation ul li a.wikilink2:active,
+#dokuwiki__footer .plugin_translation ul li a.wikilink2:visited {
+ background-color: #fff;
+ color: #ccc;
+ text-decoration:none;
+ padding: 0.1em 0.4em;
+ margin: 0.1em 0.2em;
+ border: none !important;
+}
+
+#dokuwiki__footer .plugin_translation ul li a.wikilink1.cur:link,
+#dokuwiki__footer .plugin_translation ul li a.wikilink1.cur:hover,
+#dokuwiki__footer .plugin_translation ul li a.wikilink1.cur:active,
+#dokuwiki__footer .plugin_translation ul li a.wikilink1.cur:visited,
+#dokuwiki__footer .plugin_translation ul li a.wikilink2.cur:link,
+#dokuwiki__footer .plugin_translation ul li a.wikilink2.cur:hover,
+#dokuwiki__footer .plugin_translation ul li a.wikilink2.cur:active,
+#dokuwiki__footer .plugin_translation ul li a.wikilink2.cur:visited {
+ background-color: #505050 !important;
+ color: #fff !important;
+ border-radius: 3px;
+
+}
+
+
+/* Dropdown */
+
+.dokuwiki div.plugin_translation select,
+.dokuwiki div.plugin_translation input {
+ border: none;
+ background-color: #ccc;
+}
+
+.dokuwiki div.plugin_translation option.flag {
+ padding-left: 18px;
+ background-repeat: no-repeat;
+ background-position: left center;
+}
+
+.dokuwiki div.plugin_translation select.wikilink1,
+.dokuwiki div.plugin_translation option.wikilink1 {
+ color: #000080;
+ text-align: center;
+}
+
+.dokuwiki div.plugin_translation select.wikilink2,
+.dokuwiki div.plugin_translation option.wikilink2 {
+ color: #808080;
+ text-align: center;
+}
+
+/* flags for non-existing pages */
+.dokuwiki div.plugin_translation img.wikilink2,
+.dokuwiki div.plugin_translation .wikilink2 img {
+ opacity: 0.5;
+}
+
+table#outdated_translations td {
+ padding-left: 3px;
+ padding-right: 3px;
+}
+
+table#outdated_translations td.missing {
+ background-color: #ff6666;
+}
+
+table#outdated_translations td.outdated {
+ background-color: #ffff66;
+}
+
+table#outdated_translations td.current {
+ background-color: #00CC00;
+}
diff --git a/platform/www/lib/plugins/translation/syntax/notrans.php b/platform/www/lib/plugins/translation/syntax/notrans.php
new file mode 100644
index 0000000..0d04671
--- /dev/null
+++ b/platform/www/lib/plugins/translation/syntax/notrans.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Translation Plugin: Simple multilanguage plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Class syntax_plugin_translation_notrans
+ */
+class syntax_plugin_translation_notrans extends DokuWiki_Syntax_Plugin {
+
+ /**
+ * for th helper plugin
+ * @var helper_plugin_translation
+ */
+ var $hlp = null;
+
+ /**
+ * Constructor. Load helper plugin
+ */
+ function __construct(){
+ $this->hlp = plugin_load('helper', 'translation');
+ }
+
+ /**
+ * What kind of syntax are we?
+ */
+ function getType(){
+ return 'substition';
+ }
+
+ /**
+ * Where to sort in?
+ */
+ function getSort(){
+ return 155;
+ }
+
+ /**
+ * Connect pattern to lexer
+ *
+ * @param string $mode
+ */
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern('~~NOTRANS~~',$mode,'plugin_translation_notrans');
+ }
+
+ /**
+ * Handler to prepare matched data for the rendering process
+ *
+ * @param string $match The text matched by the patterns
+ * @param int $state The lexer state for the match
+ * @param int $pos The character position of the matched text
+ * @param Doku_Handler $handler The Doku_Handler object
+ * @return bool|array Return an array with all data you want to use in render, false don't add an instruction
+ */
+ function handle($match, $state, $pos, Doku_Handler $handler){
+ return array('notrans');
+ }
+
+ /**
+ * Create output
+ *
+ * @param string $format
+ * @param Doku_Renderer $renderer
+ * @param array $data
+ * @return bool
+ */
+ function render($format, Doku_Renderer $renderer, $data) {
+ // store info in metadata
+ if($format == 'metadata'){
+ /** @var Doku_Renderer_metadata $renderer */
+ $renderer->meta['plugin']['translation']['notrans'] = true;
+ }
+ return false;
+ }
+
+ // for backward compatibility
+ /**
+ * @return string
+ */
+ function _showTranslations(){
+ return $this->hlp->showTranslations();
+ }
+
+}
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/translation/syntax/trans.php b/platform/www/lib/plugins/translation/syntax/trans.php
new file mode 100644
index 0000000..152276e
--- /dev/null
+++ b/platform/www/lib/plugins/translation/syntax/trans.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Translation Plugin: Simple multilanguage plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Class syntax_plugin_translation_trans
+ */
+class syntax_plugin_translation_trans extends DokuWiki_Syntax_Plugin {
+ /**
+ * What kind of syntax are we?
+ */
+ function getType() {
+ return 'substition';
+ }
+
+ /**
+ * Where to sort in?
+ */
+ function getSort() {
+ return 155;
+ }
+
+ /**
+ * Connect pattern to lexer
+ *
+ * @param string $mode
+ */
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern('~~TRANS~~', $mode, 'plugin_translation_trans');
+ }
+
+ /**
+ * Handler to prepare matched data for the rendering process
+ *
+ * @param string $match The text matched by the patterns
+ * @param int $state The lexer state for the match
+ * @param int $pos The character position of the matched text
+ * @param Doku_Handler $handler The Doku_Handler object
+ * @return bool|array Return an array with all data you want to use in render, false don't add an instruction
+ */
+ function handle($match, $state, $pos, Doku_Handler $handler) {
+ return array();
+ }
+
+ /**
+ * Handles the actual output creation.
+ *
+ * @param string $format output format being rendered
+ * @param Doku_Renderer $renderer the current renderer object
+ * @param array $data data created by handler()
+ * @return boolean rendered correctly? (however, returned value is not used at the moment)
+ */
+ function render($format, Doku_Renderer $renderer, $data) {
+ if($format != 'xhtml') return false;
+ // disable caching
+ $renderer->nocache();
+
+ /** @var helper_plugin_translation $hlp */
+ $hlp = plugin_load('helper', 'translation');
+ $renderer->doc .= $hlp->showTranslations();
+ return true;
+ }
+
+}
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/platform/www/lib/plugins/usermanager/admin.php b/platform/www/lib/plugins/usermanager/admin.php
new file mode 100644
index 0000000..4234671
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/admin.php
@@ -0,0 +1,1235 @@
+<?php
+/*
+ * User Manager
+ *
+ * Dokuwiki Admin Plugin
+ *
+ * This version of the user manager has been modified to only work with
+ * objectified version of auth system
+ *
+ * @author neolao <neolao@neolao.com>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+
+/**
+ * All DokuWiki plugins to extend the admin function
+ * need to inherit from this class
+ */
+class admin_plugin_usermanager extends DokuWiki_Admin_Plugin
+{
+ const IMAGE_DIR = DOKU_BASE.'lib/plugins/usermanager/images/';
+
+ protected $auth = null; // auth object
+ protected $users_total = 0; // number of registered users
+ protected $filter = array(); // user selection filter(s)
+ protected $start = 0; // index of first user to be displayed
+ protected $last = 0; // index of the last user to be displayed
+ protected $pagesize = 20; // number of users to list on one page
+ protected $edit_user = ''; // set to user selected for editing
+ protected $edit_userdata = array();
+ protected $disabled = ''; // if disabled set to explanatory string
+ protected $import_failures = array();
+ protected $lastdisabled = false; // set to true if last user is unknown and last button is hence buggy
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ /** @var DokuWiki_Auth_Plugin $auth */
+ global $auth;
+
+ $this->setupLocale();
+
+ if (!isset($auth)) {
+ $this->disabled = $this->lang['noauth'];
+ } elseif (!$auth->canDo('getUsers')) {
+ $this->disabled = $this->lang['nosupport'];
+ } else {
+ // we're good to go
+ $this->auth = & $auth;
+ }
+
+ // attempt to retrieve any import failures from the session
+ if (!empty($_SESSION['import_failures'])) {
+ $this->import_failures = $_SESSION['import_failures'];
+ }
+ }
+
+ /**
+ * Return prompt for admin menu
+ *
+ * @param string $language
+ * @return string
+ */
+ public function getMenuText($language)
+ {
+
+ if (!is_null($this->auth))
+ return parent::getMenuText($language);
+
+ return $this->getLang('menu').' '.$this->disabled;
+ }
+
+ /**
+ * return sort order for position in admin menu
+ *
+ * @return int
+ */
+ public function getMenuSort()
+ {
+ return 2;
+ }
+
+ /**
+ * @return int current start value for pageination
+ */
+ public function getStart()
+ {
+ return $this->start;
+ }
+
+ /**
+ * @return int number of users per page
+ */
+ public function getPagesize()
+ {
+ return $this->pagesize;
+ }
+
+ /**
+ * @param boolean $lastdisabled
+ */
+ public function setLastdisabled($lastdisabled)
+ {
+ $this->lastdisabled = $lastdisabled;
+ }
+
+ /**
+ * Handle user request
+ *
+ * @return bool
+ */
+ public function handle()
+ {
+ global $INPUT;
+ if (is_null($this->auth)) return false;
+
+ // extract the command and any specific parameters
+ // submit button name is of the form - fn[cmd][param(s)]
+ $fn = $INPUT->param('fn');
+
+ if (is_array($fn)) {
+ $cmd = key($fn);
+ $param = is_array($fn[$cmd]) ? key($fn[$cmd]) : null;
+ } else {
+ $cmd = $fn;
+ $param = null;
+ }
+
+ if ($cmd != "search") {
+ $this->start = $INPUT->int('start', 0);
+ $this->filter = $this->retrieveFilter();
+ }
+
+ switch ($cmd) {
+ case "add":
+ $this->addUser();
+ break;
+ case "delete":
+ $this->deleteUser();
+ break;
+ case "modify":
+ $this->modifyUser();
+ break;
+ case "edit":
+ $this->editUser($param);
+ break;
+ case "search":
+ $this->setFilter($param);
+ $this->start = 0;
+ break;
+ case "export":
+ $this->exportCSV();
+ break;
+ case "import":
+ $this->importCSV();
+ break;
+ case "importfails":
+ $this->downloadImportFailures();
+ break;
+ }
+
+ $this->users_total = $this->auth->canDo('getUserCount') ? $this->auth->getUserCount($this->filter) : -1;
+
+ // page handling
+ switch ($cmd) {
+ case 'start':
+ $this->start = 0;
+ break;
+ case 'prev':
+ $this->start -= $this->pagesize;
+ break;
+ case 'next':
+ $this->start += $this->pagesize;
+ break;
+ case 'last':
+ $this->start = $this->users_total;
+ break;
+ }
+ $this->validatePagination();
+ return true;
+ }
+
+ /**
+ * Output appropriate html
+ *
+ * @return bool
+ */
+ public function html()
+ {
+ global $ID;
+
+ if (is_null($this->auth)) {
+ print $this->lang['badauth'];
+ return false;
+ }
+
+ $user_list = $this->auth->retrieveUsers($this->start, $this->pagesize, $this->filter);
+
+ $page_buttons = $this->pagination();
+ $delete_disable = $this->auth->canDo('delUser') ? '' : 'disabled="disabled"';
+
+ $editable = $this->auth->canDo('UserMod');
+ $export_label = empty($this->filter) ? $this->lang['export_all'] : $this->lang['export_filtered'];
+
+ print $this->locale_xhtml('intro');
+ print $this->locale_xhtml('list');
+
+ ptln("<div id=\"user__manager\">");
+ ptln("<div class=\"level2\">");
+
+ if ($this->users_total > 0) {
+ ptln(
+ "<p>" . sprintf(
+ $this->lang['summary'],
+ $this->start + 1,
+ $this->last,
+ $this->users_total,
+ $this->auth->getUserCount()
+ ) . "</p>"
+ );
+ } else {
+ if ($this->users_total < 0) {
+ $allUserTotal = 0;
+ } else {
+ $allUserTotal = $this->auth->getUserCount();
+ }
+ ptln("<p>".sprintf($this->lang['nonefound'], $allUserTotal)."</p>");
+ }
+ ptln("<form action=\"".wl($ID)."\" method=\"post\">");
+ formSecurityToken();
+ ptln(" <div class=\"table\">");
+ ptln(" <table class=\"inline\">");
+ ptln(" <thead>");
+ ptln(" <tr>");
+ ptln(" <th>&#160;</th>
+ <th>".$this->lang["user_id"]."</th>
+ <th>".$this->lang["user_name"]."</th>
+ <th>".$this->lang["user_mail"]."</th>
+ <th>".$this->lang["user_groups"]."</th>");
+ ptln(" </tr>");
+
+ ptln(" <tr>");
+ ptln(" <td class=\"rightalign\"><input type=\"image\" src=\"".
+ self::IMAGE_DIR."search.png\" name=\"fn[search][new]\" title=\"".
+ $this->lang['search_prompt']."\" alt=\"".$this->lang['search']."\" class=\"button\" /></td>");
+ ptln(" <td><input type=\"text\" name=\"userid\" class=\"edit\" value=\"".
+ $this->htmlFilter('user')."\" /></td>");
+ ptln(" <td><input type=\"text\" name=\"username\" class=\"edit\" value=\"".
+ $this->htmlFilter('name')."\" /></td>");
+ ptln(" <td><input type=\"text\" name=\"usermail\" class=\"edit\" value=\"".
+ $this->htmlFilter('mail')."\" /></td>");
+ ptln(" <td><input type=\"text\" name=\"usergroups\" class=\"edit\" value=\"".
+ $this->htmlFilter('grps')."\" /></td>");
+ ptln(" </tr>");
+ ptln(" </thead>");
+
+ if ($this->users_total) {
+ ptln(" <tbody>");
+ foreach ($user_list as $user => $userinfo) {
+ extract($userinfo);
+ /**
+ * @var string $name
+ * @var string $pass
+ * @var string $mail
+ * @var array $grps
+ */
+ $groups = join(', ', $grps);
+ ptln(" <tr class=\"user_info\">");
+ ptln(" <td class=\"centeralign\"><input type=\"checkbox\" name=\"delete[".hsc($user).
+ "]\" ".$delete_disable." /></td>");
+ if ($editable) {
+ ptln(" <td><a href=\"".wl($ID, array('fn[edit]['.$user.']' => 1,
+ 'do' => 'admin',
+ 'page' => 'usermanager',
+ 'sectok' => getSecurityToken())).
+ "\" title=\"".$this->lang['edit_prompt']."\">".hsc($user)."</a></td>");
+ } else {
+ ptln(" <td>".hsc($user)."</td>");
+ }
+ ptln(" <td>".hsc($name)."</td><td>".hsc($mail)."</td><td>".hsc($groups)."</td>");
+ ptln(" </tr>");
+ }
+ ptln(" </tbody>");
+ }
+
+ ptln(" <tbody>");
+ ptln(" <tr><td colspan=\"5\" class=\"centeralign\">");
+ ptln(" <span class=\"medialeft\">");
+ ptln(" <button type=\"submit\" name=\"fn[delete]\" id=\"usrmgr__del\" ".$delete_disable.">".
+ $this->lang['delete_selected']."</button>");
+ ptln(" </span>");
+ ptln(" <span class=\"mediaright\">");
+ ptln(" <button type=\"submit\" name=\"fn[start]\" ".$page_buttons['start'].">".
+ $this->lang['start']."</button>");
+ ptln(" <button type=\"submit\" name=\"fn[prev]\" ".$page_buttons['prev'].">".
+ $this->lang['prev']."</button>");
+ ptln(" <button type=\"submit\" name=\"fn[next]\" ".$page_buttons['next'].">".
+ $this->lang['next']."</button>");
+ ptln(" <button type=\"submit\" name=\"fn[last]\" ".$page_buttons['last'].">".
+ $this->lang['last']."</button>");
+ ptln(" </span>");
+ if (!empty($this->filter)) {
+ ptln(" <button type=\"submit\" name=\"fn[search][clear]\">".$this->lang['clear']."</button>");
+ }
+ ptln(" <button type=\"submit\" name=\"fn[export]\">".$export_label."</button>");
+ ptln(" <input type=\"hidden\" name=\"do\" value=\"admin\" />");
+ ptln(" <input type=\"hidden\" name=\"page\" value=\"usermanager\" />");
+
+ $this->htmlFilterSettings(2);
+
+ ptln(" </td></tr>");
+ ptln(" </tbody>");
+ ptln(" </table>");
+ ptln(" </div>");
+
+ ptln("</form>");
+ ptln("</div>");
+
+ $style = $this->edit_user ? " class=\"edit_user\"" : "";
+
+ if ($this->auth->canDo('addUser')) {
+ ptln("<div".$style.">");
+ print $this->locale_xhtml('add');
+ ptln(" <div class=\"level2\">");
+
+ $this->htmlUserForm('add', null, array(), 4);
+
+ ptln(" </div>");
+ ptln("</div>");
+ }
+
+ if ($this->edit_user && $this->auth->canDo('UserMod')) {
+ ptln("<div".$style." id=\"scroll__here\">");
+ print $this->locale_xhtml('edit');
+ ptln(" <div class=\"level2\">");
+
+ $this->htmlUserForm('modify', $this->edit_user, $this->edit_userdata, 4);
+
+ ptln(" </div>");
+ ptln("</div>");
+ }
+
+ if ($this->auth->canDo('addUser')) {
+ $this->htmlImportForm();
+ }
+ ptln("</div>");
+ return true;
+ }
+
+ /**
+ * User Manager is only available if the auth backend supports it
+ *
+ * @inheritdoc
+ * @return bool
+ */
+ public function isAccessibleByCurrentUser()
+ {
+ /** @var DokuWiki_Auth_Plugin $auth */
+ global $auth;
+ if(!$auth || !$auth->canDo('getUsers') ) {
+ return false;
+ }
+
+ return parent::isAccessibleByCurrentUser();
+ }
+
+
+ /**
+ * Display form to add or modify a user
+ *
+ * @param string $cmd 'add' or 'modify'
+ * @param string $user id of user
+ * @param array $userdata array with name, mail, pass and grps
+ * @param int $indent
+ */
+ protected function htmlUserForm($cmd, $user = '', $userdata = array(), $indent = 0)
+ {
+ global $conf;
+ global $ID;
+ global $lang;
+
+ $name = $mail = $groups = '';
+ $notes = array();
+
+ if ($user) {
+ extract($userdata);
+ if (!empty($grps)) $groups = join(',', $grps);
+ } else {
+ $notes[] = sprintf($this->lang['note_group'], $conf['defaultgroup']);
+ }
+
+ ptln("<form action=\"".wl($ID)."\" method=\"post\">", $indent);
+ formSecurityToken();
+ ptln(" <div class=\"table\">", $indent);
+ ptln(" <table class=\"inline\">", $indent);
+ ptln(" <thead>", $indent);
+ ptln(" <tr><th>".$this->lang["field"]."</th><th>".$this->lang["value"]."</th></tr>", $indent);
+ ptln(" </thead>", $indent);
+ ptln(" <tbody>", $indent);
+
+ $this->htmlInputField(
+ $cmd . "_userid",
+ "userid",
+ $this->lang["user_id"],
+ $user,
+ $this->auth->canDo("modLogin"),
+ true,
+ $indent + 6
+ );
+ $this->htmlInputField(
+ $cmd . "_userpass",
+ "userpass",
+ $this->lang["user_pass"],
+ "",
+ $this->auth->canDo("modPass"),
+ false,
+ $indent + 6
+ );
+ $this->htmlInputField(
+ $cmd . "_userpass2",
+ "userpass2",
+ $lang["passchk"],
+ "",
+ $this->auth->canDo("modPass"),
+ false,
+ $indent + 6
+ );
+ $this->htmlInputField(
+ $cmd . "_username",
+ "username",
+ $this->lang["user_name"],
+ $name,
+ $this->auth->canDo("modName"),
+ true,
+ $indent + 6
+ );
+ $this->htmlInputField(
+ $cmd . "_usermail",
+ "usermail",
+ $this->lang["user_mail"],
+ $mail,
+ $this->auth->canDo("modMail"),
+ true,
+ $indent + 6
+ );
+ $this->htmlInputField(
+ $cmd . "_usergroups",
+ "usergroups",
+ $this->lang["user_groups"],
+ $groups,
+ $this->auth->canDo("modGroups"),
+ false,
+ $indent + 6
+ );
+
+ if ($this->auth->canDo("modPass")) {
+ if ($cmd == 'add') {
+ $notes[] = $this->lang['note_pass'];
+ }
+ if ($user) {
+ $notes[] = $this->lang['note_notify'];
+ }
+
+ ptln("<tr><td><label for=\"".$cmd."_usernotify\" >".
+ $this->lang["user_notify"].": </label></td>
+ <td><input type=\"checkbox\" id=\"".$cmd."_usernotify\" name=\"usernotify\" value=\"1\" />
+ </td></tr>", $indent);
+ }
+
+ ptln(" </tbody>", $indent);
+ ptln(" <tbody>", $indent);
+ ptln(" <tr>", $indent);
+ ptln(" <td colspan=\"2\">", $indent);
+ ptln(" <input type=\"hidden\" name=\"do\" value=\"admin\" />", $indent);
+ ptln(" <input type=\"hidden\" name=\"page\" value=\"usermanager\" />", $indent);
+
+ // save current $user, we need this to access details if the name is changed
+ if ($user)
+ ptln(" <input type=\"hidden\" name=\"userid_old\" value=\"".hsc($user)."\" />", $indent);
+
+ $this->htmlFilterSettings($indent+10);
+
+ ptln(" <button type=\"submit\" name=\"fn[".$cmd."]\">".$this->lang[$cmd]."</button>", $indent);
+ ptln(" </td>", $indent);
+ ptln(" </tr>", $indent);
+ ptln(" </tbody>", $indent);
+ ptln(" </table>", $indent);
+
+ if ($notes) {
+ ptln(" <ul class=\"notes\">");
+ foreach ($notes as $note) {
+ ptln(" <li><span class=\"li\">".$note."</li>", $indent);
+ }
+ ptln(" </ul>");
+ }
+ ptln(" </div>", $indent);
+ ptln("</form>", $indent);
+ }
+
+ /**
+ * Prints a inputfield
+ *
+ * @param string $id
+ * @param string $name
+ * @param string $label
+ * @param string $value
+ * @param bool $cando whether auth backend is capable to do this action
+ * @param bool $required is this field required?
+ * @param int $indent
+ */
+ protected function htmlInputField($id, $name, $label, $value, $cando, $required, $indent = 0)
+ {
+ $class = $cando ? '' : ' class="disabled"';
+ echo str_pad('', $indent);
+
+ if ($name == 'userpass' || $name == 'userpass2') {
+ $fieldtype = 'password';
+ $autocomp = 'autocomplete="off"';
+ } elseif ($name == 'usermail') {
+ $fieldtype = 'email';
+ $autocomp = '';
+ } else {
+ $fieldtype = 'text';
+ $autocomp = '';
+ }
+ $value = hsc($value);
+
+ echo "<tr $class>";
+ echo "<td><label for=\"$id\" >$label: </label></td>";
+ echo "<td>";
+ if ($cando) {
+ $req = '';
+ if ($required) $req = 'required="required"';
+ echo "<input type=\"$fieldtype\" id=\"$id\" name=\"$name\"
+ value=\"$value\" class=\"edit\" $autocomp $req />";
+ } else {
+ echo "<input type=\"hidden\" name=\"$name\" value=\"$value\" />";
+ echo "<input type=\"$fieldtype\" id=\"$id\" name=\"$name\"
+ value=\"$value\" class=\"edit disabled\" disabled=\"disabled\" />";
+ }
+ echo "</td>";
+ echo "</tr>";
+ }
+
+ /**
+ * Returns htmlescaped filter value
+ *
+ * @param string $key name of search field
+ * @return string html escaped value
+ */
+ protected function htmlFilter($key)
+ {
+ if (empty($this->filter)) return '';
+ return (isset($this->filter[$key]) ? hsc($this->filter[$key]) : '');
+ }
+
+ /**
+ * Print hidden inputs with the current filter values
+ *
+ * @param int $indent
+ */
+ protected function htmlFilterSettings($indent = 0)
+ {
+
+ ptln("<input type=\"hidden\" name=\"start\" value=\"".$this->start."\" />", $indent);
+
+ foreach ($this->filter as $key => $filter) {
+ ptln("<input type=\"hidden\" name=\"filter[".$key."]\" value=\"".hsc($filter)."\" />", $indent);
+ }
+ }
+
+ /**
+ * Print import form and summary of previous import
+ *
+ * @param int $indent
+ */
+ protected function htmlImportForm($indent = 0)
+ {
+ global $ID;
+
+ $failure_download_link = wl($ID, array('do'=>'admin','page'=>'usermanager','fn[importfails]'=>1));
+
+ ptln('<div class="level2 import_users">', $indent);
+ print $this->locale_xhtml('import');
+ ptln(' <form action="'.wl($ID).'" method="post" enctype="multipart/form-data">', $indent);
+ formSecurityToken();
+ ptln(' <label>'.$this->lang['import_userlistcsv'].'<input type="file" name="import" /></label>', $indent);
+ ptln(' <button type="submit" name="fn[import]">'.$this->lang['import'].'</button>', $indent);
+ ptln(' <input type="hidden" name="do" value="admin" />', $indent);
+ ptln(' <input type="hidden" name="page" value="usermanager" />', $indent);
+
+ $this->htmlFilterSettings($indent+4);
+ ptln(' </form>', $indent);
+ ptln('</div>');
+
+ // list failures from the previous import
+ if ($this->import_failures) {
+ $digits = strlen(count($this->import_failures));
+ ptln('<div class="level3 import_failures">', $indent);
+ ptln(' <h3>'.$this->lang['import_header'].'</h3>');
+ ptln(' <table class="import_failures">', $indent);
+ ptln(' <thead>', $indent);
+ ptln(' <tr>', $indent);
+ ptln(' <th class="line">'.$this->lang['line'].'</th>', $indent);
+ ptln(' <th class="error">'.$this->lang['error'].'</th>', $indent);
+ ptln(' <th class="userid">'.$this->lang['user_id'].'</th>', $indent);
+ ptln(' <th class="username">'.$this->lang['user_name'].'</th>', $indent);
+ ptln(' <th class="usermail">'.$this->lang['user_mail'].'</th>', $indent);
+ ptln(' <th class="usergroups">'.$this->lang['user_groups'].'</th>', $indent);
+ ptln(' </tr>', $indent);
+ ptln(' </thead>', $indent);
+ ptln(' <tbody>', $indent);
+ foreach ($this->import_failures as $line => $failure) {
+ ptln(' <tr>', $indent);
+ ptln(' <td class="lineno"> '.sprintf('%0'.$digits.'d', $line).' </td>', $indent);
+ ptln(' <td class="error">' .$failure['error'].' </td>', $indent);
+ ptln(' <td class="field userid"> '.hsc($failure['user'][0]).' </td>', $indent);
+ ptln(' <td class="field username"> '.hsc($failure['user'][2]).' </td>', $indent);
+ ptln(' <td class="field usermail"> '.hsc($failure['user'][3]).' </td>', $indent);
+ ptln(' <td class="field usergroups"> '.hsc($failure['user'][4]).' </td>', $indent);
+ ptln(' </tr>', $indent);
+ }
+ ptln(' </tbody>', $indent);
+ ptln(' </table>', $indent);
+ ptln(' <p><a href="'.$failure_download_link.'">'.$this->lang['import_downloadfailures'].'</a></p>');
+ ptln('</div>');
+ }
+ }
+
+ /**
+ * Add an user to auth backend
+ *
+ * @return bool whether succesful
+ */
+ protected function addUser()
+ {
+ global $INPUT;
+ if (!checkSecurityToken()) return false;
+ if (!$this->auth->canDo('addUser')) return false;
+
+ list($user,$pass,$name,$mail,$grps,$passconfirm) = $this->retrieveUser();
+ if (empty($user)) return false;
+
+ if ($this->auth->canDo('modPass')) {
+ if (empty($pass)) {
+ if ($INPUT->has('usernotify')) {
+ $pass = auth_pwgen($user);
+ } else {
+ msg($this->lang['add_fail'], -1);
+ msg($this->lang['addUser_error_missing_pass'], -1);
+ return false;
+ }
+ } else {
+ if (!$this->verifyPassword($pass, $passconfirm)) {
+ msg($this->lang['add_fail'], -1);
+ msg($this->lang['addUser_error_pass_not_identical'], -1);
+ return false;
+ }
+ }
+ } else {
+ if (!empty($pass)) {
+ msg($this->lang['add_fail'], -1);
+ msg($this->lang['addUser_error_modPass_disabled'], -1);
+ return false;
+ }
+ }
+
+ if ($this->auth->canDo('modName')) {
+ if (empty($name)) {
+ msg($this->lang['add_fail'], -1);
+ msg($this->lang['addUser_error_name_missing'], -1);
+ return false;
+ }
+ } else {
+ if (!empty($name)) {
+ msg($this->lang['add_fail'], -1);
+ msg($this->lang['addUser_error_modName_disabled'], -1);
+ return false;
+ }
+ }
+
+ if ($this->auth->canDo('modMail')) {
+ if (empty($mail)) {
+ msg($this->lang['add_fail'], -1);
+ msg($this->lang['addUser_error_mail_missing'], -1);
+ return false;
+ }
+ } else {
+ if (!empty($mail)) {
+ msg($this->lang['add_fail'], -1);
+ msg($this->lang['addUser_error_modMail_disabled'], -1);
+ return false;
+ }
+ }
+
+ if ($ok = $this->auth->triggerUserMod('create', array($user, $pass, $name, $mail, $grps))) {
+ msg($this->lang['add_ok'], 1);
+
+ if ($INPUT->has('usernotify') && $pass) {
+ $this->notifyUser($user, $pass);
+ }
+ } else {
+ msg($this->lang['add_fail'], -1);
+ msg($this->lang['addUser_error_create_event_failed'], -1);
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Delete user from auth backend
+ *
+ * @return bool whether succesful
+ */
+ protected function deleteUser()
+ {
+ global $conf, $INPUT;
+
+ if (!checkSecurityToken()) return false;
+ if (!$this->auth->canDo('delUser')) return false;
+
+ $selected = $INPUT->arr('delete');
+ if (empty($selected)) return false;
+ $selected = array_keys($selected);
+
+ if (in_array($_SERVER['REMOTE_USER'], $selected)) {
+ msg("You can't delete yourself!", -1);
+ return false;
+ }
+
+ $count = $this->auth->triggerUserMod('delete', array($selected));
+ if ($count == count($selected)) {
+ $text = str_replace('%d', $count, $this->lang['delete_ok']);
+ msg("$text.", 1);
+ } else {
+ $part1 = str_replace('%d', $count, $this->lang['delete_ok']);
+ $part2 = str_replace('%d', (count($selected)-$count), $this->lang['delete_fail']);
+ msg("$part1, $part2", -1);
+ }
+
+ // invalidate all sessions
+ io_saveFile($conf['cachedir'].'/sessionpurge', time());
+
+ return true;
+ }
+
+ /**
+ * Edit user (a user has been selected for editing)
+ *
+ * @param string $param id of the user
+ * @return bool whether succesful
+ */
+ protected function editUser($param)
+ {
+ if (!checkSecurityToken()) return false;
+ if (!$this->auth->canDo('UserMod')) return false;
+ $user = $this->auth->cleanUser(preg_replace('/.*[:\/]/', '', $param));
+ $userdata = $this->auth->getUserData($user);
+
+ // no user found?
+ if (!$userdata) {
+ msg($this->lang['edit_usermissing'], -1);
+ return false;
+ }
+
+ $this->edit_user = $user;
+ $this->edit_userdata = $userdata;
+
+ return true;
+ }
+
+ /**
+ * Modify user in the auth backend (modified user data has been recieved)
+ *
+ * @return bool whether succesful
+ */
+ protected function modifyUser()
+ {
+ global $conf, $INPUT;
+
+ if (!checkSecurityToken()) return false;
+ if (!$this->auth->canDo('UserMod')) return false;
+
+ // get currently valid user data
+ $olduser = $this->auth->cleanUser(preg_replace('/.*[:\/]/', '', $INPUT->str('userid_old')));
+ $oldinfo = $this->auth->getUserData($olduser);
+
+ // get new user data subject to change
+ list($newuser,$newpass,$newname,$newmail,$newgrps,$passconfirm) = $this->retrieveUser();
+ if (empty($newuser)) return false;
+
+ $changes = array();
+ if ($newuser != $olduser) {
+ if (!$this->auth->canDo('modLogin')) { // sanity check, shouldn't be possible
+ msg($this->lang['update_fail'], -1);
+ return false;
+ }
+
+ // check if $newuser already exists
+ if ($this->auth->getUserData($newuser)) {
+ msg(sprintf($this->lang['update_exists'], $newuser), -1);
+ $re_edit = true;
+ } else {
+ $changes['user'] = $newuser;
+ }
+ }
+ if ($this->auth->canDo('modPass')) {
+ if ($newpass || $passconfirm) {
+ if ($this->verifyPassword($newpass, $passconfirm)) {
+ $changes['pass'] = $newpass;
+ } else {
+ return false;
+ }
+ } else {
+ // no new password supplied, check if we need to generate one (or it stays unchanged)
+ if ($INPUT->has('usernotify')) {
+ $changes['pass'] = auth_pwgen($olduser);
+ }
+ }
+ }
+
+ if (!empty($newname) && $this->auth->canDo('modName') && $newname != $oldinfo['name']) {
+ $changes['name'] = $newname;
+ }
+ if (!empty($newmail) && $this->auth->canDo('modMail') && $newmail != $oldinfo['mail']) {
+ $changes['mail'] = $newmail;
+ }
+ if (!empty($newgrps) && $this->auth->canDo('modGroups') && $newgrps != $oldinfo['grps']) {
+ $changes['grps'] = $newgrps;
+ }
+
+ if ($ok = $this->auth->triggerUserMod('modify', array($olduser, $changes))) {
+ msg($this->lang['update_ok'], 1);
+
+ if ($INPUT->has('usernotify') && !empty($changes['pass'])) {
+ $notify = empty($changes['user']) ? $olduser : $newuser;
+ $this->notifyUser($notify, $changes['pass']);
+ }
+
+ // invalidate all sessions
+ io_saveFile($conf['cachedir'].'/sessionpurge', time());
+ } else {
+ msg($this->lang['update_fail'], -1);
+ }
+
+ if (!empty($re_edit)) {
+ $this->editUser($olduser);
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Send password change notification email
+ *
+ * @param string $user id of user
+ * @param string $password plain text
+ * @param bool $status_alert whether status alert should be shown
+ * @return bool whether succesful
+ */
+ protected function notifyUser($user, $password, $status_alert = true)
+ {
+
+ if ($sent = auth_sendPassword($user, $password)) {
+ if ($status_alert) {
+ msg($this->lang['notify_ok'], 1);
+ }
+ } else {
+ if ($status_alert) {
+ msg($this->lang['notify_fail'], -1);
+ }
+ }
+
+ return $sent;
+ }
+
+ /**
+ * Verify password meets minimum requirements
+ * :TODO: extend to support password strength
+ *
+ * @param string $password candidate string for new password
+ * @param string $confirm repeated password for confirmation
+ * @return bool true if meets requirements, false otherwise
+ */
+ protected function verifyPassword($password, $confirm)
+ {
+ global $lang;
+
+ if (empty($password) && empty($confirm)) {
+ return false;
+ }
+
+ if ($password !== $confirm) {
+ msg($lang['regbadpass'], -1);
+ return false;
+ }
+
+ // :TODO: test password for required strength
+
+ // if we make it this far the password is good
+ return true;
+ }
+
+ /**
+ * Retrieve & clean user data from the form
+ *
+ * @param bool $clean whether the cleanUser method of the authentication backend is applied
+ * @return array (user, password, full name, email, array(groups))
+ */
+ protected function retrieveUser($clean = true)
+ {
+ /** @var DokuWiki_Auth_Plugin $auth */
+ global $auth;
+ global $INPUT;
+
+ $user = array();
+ $user[0] = ($clean) ? $auth->cleanUser($INPUT->str('userid')) : $INPUT->str('userid');
+ $user[1] = $INPUT->str('userpass');
+ $user[2] = $INPUT->str('username');
+ $user[3] = $INPUT->str('usermail');
+ $user[4] = explode(',', $INPUT->str('usergroups'));
+ $user[5] = $INPUT->str('userpass2'); // repeated password for confirmation
+
+ $user[4] = array_map('trim', $user[4]);
+ if ($clean) $user[4] = array_map(array($auth,'cleanGroup'), $user[4]);
+ $user[4] = array_filter($user[4]);
+ $user[4] = array_unique($user[4]);
+ if (!count($user[4])) $user[4] = null;
+
+ return $user;
+ }
+
+ /**
+ * Set the filter with the current search terms or clear the filter
+ *
+ * @param string $op 'new' or 'clear'
+ */
+ protected function setFilter($op)
+ {
+
+ $this->filter = array();
+
+ if ($op == 'new') {
+ list($user,/* $pass */,$name,$mail,$grps) = $this->retrieveUser(false);
+
+ if (!empty($user)) $this->filter['user'] = $user;
+ if (!empty($name)) $this->filter['name'] = $name;
+ if (!empty($mail)) $this->filter['mail'] = $mail;
+ if (!empty($grps)) $this->filter['grps'] = join('|', $grps);
+ }
+ }
+
+ /**
+ * Get the current search terms
+ *
+ * @return array
+ */
+ protected function retrieveFilter()
+ {
+ global $INPUT;
+
+ $t_filter = $INPUT->arr('filter');
+
+ // messy, but this way we ensure we aren't getting any additional crap from malicious users
+ $filter = array();
+
+ if (isset($t_filter['user'])) $filter['user'] = $t_filter['user'];
+ if (isset($t_filter['name'])) $filter['name'] = $t_filter['name'];
+ if (isset($t_filter['mail'])) $filter['mail'] = $t_filter['mail'];
+ if (isset($t_filter['grps'])) $filter['grps'] = $t_filter['grps'];
+
+ return $filter;
+ }
+
+ /**
+ * Validate and improve the pagination values
+ */
+ protected function validatePagination()
+ {
+
+ if ($this->start >= $this->users_total) {
+ $this->start = $this->users_total - $this->pagesize;
+ }
+ if ($this->start < 0) $this->start = 0;
+
+ $this->last = min($this->users_total, $this->start + $this->pagesize);
+ }
+
+ /**
+ * Return an array of strings to enable/disable pagination buttons
+ *
+ * @return array with enable/disable attributes
+ */
+ protected function pagination()
+ {
+
+ $disabled = 'disabled="disabled"';
+
+ $buttons = array();
+ $buttons['start'] = $buttons['prev'] = ($this->start == 0) ? $disabled : '';
+
+ if ($this->users_total == -1) {
+ $buttons['last'] = $disabled;
+ $buttons['next'] = '';
+ } else {
+ $buttons['last'] = $buttons['next'] =
+ (($this->start + $this->pagesize) >= $this->users_total) ? $disabled : '';
+ }
+
+ if ($this->lastdisabled) {
+ $buttons['last'] = $disabled;
+ }
+
+ return $buttons;
+ }
+
+ /**
+ * Export a list of users in csv format using the current filter criteria
+ */
+ protected function exportCSV()
+ {
+ // list of users for export - based on current filter criteria
+ $user_list = $this->auth->retrieveUsers(0, 0, $this->filter);
+ $column_headings = array(
+ $this->lang["user_id"],
+ $this->lang["user_name"],
+ $this->lang["user_mail"],
+ $this->lang["user_groups"]
+ );
+
+ // ==============================================================================================
+ // GENERATE OUTPUT
+ // normal headers for downloading...
+ header('Content-type: text/csv;charset=utf-8');
+ header('Content-Disposition: attachment; filename="wikiusers.csv"');
+# // for debugging assistance, send as text plain to the browser
+# header('Content-type: text/plain;charset=utf-8');
+
+ // output the csv
+ $fd = fopen('php://output', 'w');
+ fputcsv($fd, $column_headings);
+ foreach ($user_list as $user => $info) {
+ $line = array($user, $info['name'], $info['mail'], join(',', $info['grps']));
+ fputcsv($fd, $line);
+ }
+ fclose($fd);
+ if (defined('DOKU_UNITTEST')) {
+ return;
+ }
+
+ die;
+ }
+
+ /**
+ * Import a file of users in csv format
+ *
+ * csv file should have 4 columns, user_id, full name, email, groups (comma separated)
+ *
+ * @return bool whether successful
+ */
+ protected function importCSV()
+ {
+ // check we are allowed to add users
+ if (!checkSecurityToken()) return false;
+ if (!$this->auth->canDo('addUser')) return false;
+
+ // check file uploaded ok.
+ if (empty($_FILES['import']['size']) ||
+ !empty($_FILES['import']['error']) && $this->isUploadedFile($_FILES['import']['tmp_name'])
+ ) {
+ msg($this->lang['import_error_upload'], -1);
+ return false;
+ }
+ // retrieve users from the file
+ $this->import_failures = array();
+ $import_success_count = 0;
+ $import_fail_count = 0;
+ $line = 0;
+ $fd = fopen($_FILES['import']['tmp_name'], 'r');
+ if ($fd) {
+ while ($csv = fgets($fd)) {
+ if (!\dokuwiki\Utf8\Clean::isUtf8($csv)) {
+ $csv = utf8_encode($csv);
+ }
+ $raw = str_getcsv($csv);
+ $error = ''; // clean out any errors from the previous line
+ // data checks...
+ if (1 == ++$line) {
+ if ($raw[0] == 'user_id' || $raw[0] == $this->lang['user_id']) continue; // skip headers
+ }
+ if (count($raw) < 4) { // need at least four fields
+ $import_fail_count++;
+ $error = sprintf($this->lang['import_error_fields'], count($raw));
+ $this->import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv);
+ continue;
+ }
+ array_splice($raw, 1, 0, auth_pwgen()); // splice in a generated password
+ $clean = $this->cleanImportUser($raw, $error);
+ if ($clean && $this->importUser($clean, $error)) {
+ $sent = $this->notifyUser($clean[0], $clean[1], false);
+ if (!$sent) {
+ msg(sprintf($this->lang['import_notify_fail'], $clean[0], $clean[3]), -1);
+ }
+ $import_success_count++;
+ } else {
+ $import_fail_count++;
+ array_splice($raw, 1, 1); // remove the spliced in password
+ $this->import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv);
+ }
+ }
+ msg(
+ sprintf(
+ $this->lang['import_success_count'],
+ ($import_success_count + $import_fail_count),
+ $import_success_count
+ ),
+ ($import_success_count ? 1 : -1)
+ );
+ if ($import_fail_count) {
+ msg(sprintf($this->lang['import_failure_count'], $import_fail_count), -1);
+ }
+ } else {
+ msg($this->lang['import_error_readfail'], -1);
+ }
+
+ // save import failures into the session
+ if (!headers_sent()) {
+ session_start();
+ $_SESSION['import_failures'] = $this->import_failures;
+ session_write_close();
+ }
+ return true;
+ }
+
+ /**
+ * Returns cleaned user data
+ *
+ * @param array $candidate raw values of line from input file
+ * @param string $error
+ * @return array|false cleaned data or false
+ */
+ protected function cleanImportUser($candidate, & $error)
+ {
+ global $INPUT;
+
+ // FIXME kludgy ....
+ $INPUT->set('userid', $candidate[0]);
+ $INPUT->set('userpass', $candidate[1]);
+ $INPUT->set('username', $candidate[2]);
+ $INPUT->set('usermail', $candidate[3]);
+ $INPUT->set('usergroups', $candidate[4]);
+
+ $cleaned = $this->retrieveUser();
+ list($user,/* $pass */,$name,$mail,/* $grps */) = $cleaned;
+ if (empty($user)) {
+ $error = $this->lang['import_error_baduserid'];
+ return false;
+ }
+
+ // no need to check password, handled elsewhere
+
+ if (!($this->auth->canDo('modName') xor empty($name))) {
+ $error = $this->lang['import_error_badname'];
+ return false;
+ }
+
+ if ($this->auth->canDo('modMail')) {
+ if (empty($mail) || !mail_isvalid($mail)) {
+ $error = $this->lang['import_error_badmail'];
+ return false;
+ }
+ } else {
+ if (!empty($mail)) {
+ $error = $this->lang['import_error_badmail'];
+ return false;
+ }
+ }
+
+ return $cleaned;
+ }
+
+ /**
+ * Adds imported user to auth backend
+ *
+ * Required a check of canDo('addUser') before
+ *
+ * @param array $user data of user
+ * @param string &$error reference catched error message
+ * @return bool whether successful
+ */
+ protected function importUser($user, &$error)
+ {
+ if (!$this->auth->triggerUserMod('create', $user)) {
+ $error = $this->lang['import_error_create'];
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Downloads failures as csv file
+ */
+ protected function downloadImportFailures()
+ {
+
+ // ==============================================================================================
+ // GENERATE OUTPUT
+ // normal headers for downloading...
+ header('Content-type: text/csv;charset=utf-8');
+ header('Content-Disposition: attachment; filename="importfails.csv"');
+# // for debugging assistance, send as text plain to the browser
+# header('Content-type: text/plain;charset=utf-8');
+
+ // output the csv
+ $fd = fopen('php://output', 'w');
+ foreach ($this->import_failures as $fail) {
+ fputs($fd, $fail['orig']);
+ }
+ fclose($fd);
+ die;
+ }
+
+ /**
+ * wrapper for is_uploaded_file to facilitate overriding by test suite
+ *
+ * @param string $file filename
+ * @return bool
+ */
+ protected function isUploadedFile($file)
+ {
+ return is_uploaded_file($file);
+ }
+}
diff --git a/platform/www/lib/plugins/usermanager/admin.svg b/platform/www/lib/plugins/usermanager/admin.svg
new file mode 100644
index 0000000..74a72c0
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/admin.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M16 13c-.29 0-.62 0-.97.05C16.19 13.89 17 15 17 16.5V19h6v-2.5c0-2.33-4.67-3.5-7-3.5m-8 0c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5m0-2a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m8 0a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3z"/></svg> \ No newline at end of file
diff --git a/platform/www/lib/plugins/usermanager/images/search.png b/platform/www/lib/plugins/usermanager/images/search.png
new file mode 100644
index 0000000..3f2a0b5
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/images/search.png
Binary files differ
diff --git a/platform/www/lib/plugins/usermanager/lang/en/add.txt b/platform/www/lib/plugins/usermanager/lang/en/add.txt
new file mode 100644
index 0000000..9afecb5
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/lang/en/add.txt
@@ -0,0 +1 @@
+===== Add user =====
diff --git a/platform/www/lib/plugins/usermanager/lang/en/delete.txt b/platform/www/lib/plugins/usermanager/lang/en/delete.txt
new file mode 100644
index 0000000..c3ca90d
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/lang/en/delete.txt
@@ -0,0 +1 @@
+===== Delete user =====
diff --git a/platform/www/lib/plugins/usermanager/lang/en/edit.txt b/platform/www/lib/plugins/usermanager/lang/en/edit.txt
new file mode 100644
index 0000000..4d02dfd
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/lang/en/edit.txt
@@ -0,0 +1 @@
+===== Edit user =====
diff --git a/platform/www/lib/plugins/usermanager/lang/en/import.txt b/platform/www/lib/plugins/usermanager/lang/en/import.txt
new file mode 100644
index 0000000..3a1cf99
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/lang/en/import.txt
@@ -0,0 +1,9 @@
+===== Bulk User Import =====
+
+Requires a CSV file of users with at least four columns.
+The columns must contain, in order: user-id, full name, email address and groups.
+The CSV fields should be separated by commas (,) and strings delimited by quotation marks (%%""%%). Backslash (\) can be used for escaping.
+For an example of a suitable file, try the "Export Users" function above.
+Duplicate user-ids will be ignored.
+
+A password will be generated and emailed to each successfully imported user.
diff --git a/platform/www/lib/plugins/usermanager/lang/en/intro.txt b/platform/www/lib/plugins/usermanager/lang/en/intro.txt
new file mode 100644
index 0000000..73bf556
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/lang/en/intro.txt
@@ -0,0 +1 @@
+====== User Manager ======
diff --git a/platform/www/lib/plugins/usermanager/lang/en/lang.php b/platform/www/lib/plugins/usermanager/lang/en/lang.php
new file mode 100644
index 0000000..5f47673
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/lang/en/lang.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * English language file
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+
+$lang['menu'] = 'User Manager';
+
+// custom language strings for the plugin
+$lang['noauth'] = '(user authentication not available)';
+$lang['nosupport'] = '(user management not supported)';
+
+$lang['badauth'] = 'invalid auth mechanism'; // should never be displayed!
+
+$lang['user_id'] = 'User';
+$lang['user_pass'] = 'Password';
+$lang['user_name'] = 'Real Name';
+$lang['user_mail'] = 'Email';
+$lang['user_groups'] = 'Groups';
+
+$lang['field'] = 'Field';
+$lang['value'] = 'Value';
+$lang['add'] = 'Add';
+$lang['delete'] = 'Delete';
+$lang['delete_selected'] = 'Delete Selected';
+$lang['edit'] = 'Edit';
+$lang['edit_prompt'] = 'Edit this user';
+$lang['modify'] = 'Save Changes';
+$lang['search'] = 'Search';
+$lang['search_prompt'] = 'Perform search';
+$lang['clear'] = 'Reset Search Filter';
+$lang['filter'] = 'Filter';
+$lang['export_all'] = 'Export All Users (CSV)';
+$lang['export_filtered'] = 'Export Filtered User list (CSV)';
+$lang['import'] = 'Import New Users';
+$lang['line'] = 'Line no.';
+$lang['error'] = 'Error message';
+
+$lang['summary'] = 'Displaying users %1$d-%2$d of %3$d found. %4$d users total.';
+$lang['nonefound'] = 'No users found. %d users total.';
+$lang['delete_ok'] = '%d users deleted';
+$lang['delete_fail'] = '%d failed deleting.';
+$lang['update_ok'] = 'User updated successfully';
+$lang['update_fail'] = 'User update failed';
+$lang['update_exists'] = 'User name change failed, the specified user name (%s) already exists (any other changes will be applied).';
+
+$lang['start'] = 'start';
+$lang['prev'] = 'previous';
+$lang['next'] = 'next';
+$lang['last'] = 'last';
+
+// added after 2006-03-09 release
+$lang['edit_usermissing'] = 'Selected user not found, the specified user name may have been deleted or changed elsewhere.';
+$lang['user_notify'] = 'Notify user';
+$lang['note_notify'] = 'Notification emails are only sent if the user is given a new password.';
+$lang['note_group'] = 'New users will be added to the default group (%s) if no group is specified.';
+$lang['note_pass'] = 'The password will be autogenerated if the field is left empty and notification of the user is enabled.';
+$lang['add_ok'] = 'User added successfully';
+$lang['add_fail'] = 'User addition failed';
+$lang['notify_ok'] = 'Notification email sent';
+$lang['notify_fail'] = 'Notification email could not be sent';
+
+// import & errors
+$lang['import_userlistcsv'] = 'User list file (CSV): ';
+$lang['import_header'] = 'Most Recent Import - Failures';
+$lang['import_success_count'] = 'User Import: %d users found, %d imported successfully.';
+$lang['import_failure_count'] = 'User Import: %d failed. Failures are listed below.';
+$lang['import_error_fields'] = "Insufficient fields, found %d, require 4.";
+$lang['import_error_baduserid'] = "User-id missing";
+$lang['import_error_badname'] = 'Bad name';
+$lang['import_error_badmail'] = 'Bad email address';
+$lang['import_error_upload'] = 'Import Failed. The csv file could not be uploaded or is empty.';
+$lang['import_error_readfail'] = 'Import Failed. Unable to read uploaded file.';
+$lang['import_error_create'] = 'Unable to create the user';
+$lang['import_notify_fail'] = 'Notification message could not be sent for imported user, %s with email %s.';
+$lang['import_downloadfailures'] = 'Download Failures as CSV for correction';
+
+$lang['addUser_error_missing_pass'] = 'Please either set a password or activate user notification to enable password generation.';
+$lang['addUser_error_pass_not_identical'] = 'The entered passwords were not identical.';
+$lang['addUser_error_modPass_disabled'] = 'Modifing passwords is currently disabled';
+$lang['addUser_error_name_missing'] = 'Please enter a name for the new user.';
+$lang['addUser_error_modName_disabled'] = 'Modifing names is currently disabled.';
+$lang['addUser_error_mail_missing'] = 'Please enter an Email-Adress for the new user.';
+$lang['addUser_error_modMail_disabled'] = 'Modifing Email-Adresses is currently disabled.';
+$lang['addUser_error_create_event_failed'] = 'A plugin prevented the new user being added. Review possible other messages for more information.';
diff --git a/platform/www/lib/plugins/usermanager/lang/en/list.txt b/platform/www/lib/plugins/usermanager/lang/en/list.txt
new file mode 100644
index 0000000..54c45ca
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/lang/en/list.txt
@@ -0,0 +1 @@
+===== User List =====
diff --git a/platform/www/lib/plugins/usermanager/plugin.info.txt b/platform/www/lib/plugins/usermanager/plugin.info.txt
new file mode 100644
index 0000000..607eca7
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/plugin.info.txt
@@ -0,0 +1,7 @@
+base usermanager
+author Chris Smith
+email chris@jalakai.co.uk
+date 2015-07-15
+name User Manager
+desc Manage DokuWiki user accounts
+url http://dokuwiki.org/plugin:usermanager
diff --git a/platform/www/lib/plugins/usermanager/script.js b/platform/www/lib/plugins/usermanager/script.js
new file mode 100644
index 0000000..3b7ad09
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/script.js
@@ -0,0 +1,8 @@
+/**
+ * Add JavaScript confirmation to the User Delete button
+ */
+jQuery(function(){
+ jQuery('#usrmgr__del').on('click', function(){
+ return confirm(LANG.del_confirm);
+ });
+});
diff --git a/platform/www/lib/plugins/usermanager/style.css b/platform/www/lib/plugins/usermanager/style.css
new file mode 100644
index 0000000..9028fed
--- /dev/null
+++ b/platform/www/lib/plugins/usermanager/style.css
@@ -0,0 +1,33 @@
+/* User Manager specific styles */
+#user__manager tr.disabled {
+ color: #6f6f6f;
+ background: #e4e4e4;
+}
+#user__manager tr.user_info {
+ vertical-align: top;
+}
+#user__manager div.edit_user {
+ width: 46%;
+ float: left;
+}
+#user__manager table {
+ margin-bottom: 1em;
+}
+#user__manager ul.notes {
+ padding-left: 0;
+ padding-right: 1.4em;
+}
+#user__manager button[disabled] {
+ color: #ccc!important;
+ border-color: #ccc!important;
+}
+#user__manager .import_users {
+ margin-top: 1.4em;
+}
+#user__manager .import_failures {
+ margin-top: 1.4em;
+}
+#user__manager .import_failures td.lineno {
+ text-align: center;
+}
+/* IE won't understand but doesn't require it */
diff --git a/platform/www/lib/scripts/behaviour.js b/platform/www/lib/scripts/behaviour.js
new file mode 100644
index 0000000..f9aad3d
--- /dev/null
+++ b/platform/www/lib/scripts/behaviour.js
@@ -0,0 +1,195 @@
+/**
+ * Hides elements with a slide animation
+ *
+ * @param {function} fn optional callback to run after hiding
+ * @param {bool} noaria supress aria-expanded state setting
+ * @author Adrian Lang <mail@adrianlang.de>
+ */
+jQuery.fn.dw_hide = function(fn, noaria) {
+ if(!noaria) this.attr('aria-expanded', 'false');
+ return this.slideUp('fast', fn);
+};
+
+/**
+ * Unhides elements with a slide animation
+ *
+ * @param {function} fn optional callback to run after hiding
+ * @param {bool} noaria supress aria-expanded state setting
+ * @author Adrian Lang <mail@adrianlang.de>
+ */
+jQuery.fn.dw_show = function(fn, noaria) {
+ if(!noaria) this.attr('aria-expanded', 'true');
+ return this.slideDown('fast', fn);
+};
+
+/**
+ * Toggles visibility of an element using a slide element
+ *
+ * @param {bool} state the current state of the element (optional)
+ * @param {function} fn callback after the state has been toggled
+ * @param {bool} noaria supress aria-expanded state setting
+ */
+jQuery.fn.dw_toggle = function(state, fn, noaria) {
+ return this.each(function() {
+ var $this = jQuery(this);
+ if (typeof state === 'undefined') {
+ state = $this.is(':hidden');
+ }
+ $this[state ? "dw_show" : "dw_hide" ](fn, noaria);
+ });
+};
+
+/**
+ * Automatic behaviours
+ *
+ * This class wraps various JavaScript functionalities that are triggered
+ * automatically whenever a certain object is in the DOM or a certain CSS
+ * class was found
+ */
+var dw_behaviour = {
+
+ init: function(){
+ dw_behaviour.focusMarker();
+ dw_behaviour.scrollToMarker();
+ dw_behaviour.removeHighlightOnClick();
+ dw_behaviour.quickSelect();
+ dw_behaviour.checkWindowsShares();
+ dw_behaviour.subscription();
+
+ dw_behaviour.revisionBoxHandler();
+ jQuery(document).on('click','#page__revisions input[type=checkbox]',
+ dw_behaviour.revisionBoxHandler
+ );
+
+ jQuery('.bounce').effect('bounce', {times:10}, 2000 );
+ },
+
+ /**
+ * Looks for an element with the ID scroll__here at scrolls to it
+ */
+ scrollToMarker: function(){
+ var $obj = jQuery('#scroll__here');
+ if($obj.length) {
+ if($obj.offset().top != 0) {
+ jQuery('html, body').animate({
+ scrollTop: $obj.offset().top - 100
+ }, 500);
+ } else {
+ // hidden object have no offset but can still be scrolled into view
+ $obj[0].scrollIntoView();
+ }
+ }
+ },
+
+ /**
+ * Looks for an element with the ID focus__this at sets focus to it
+ */
+ focusMarker: function(){
+ jQuery('#focus__this').trigger('focus');
+ },
+
+ /**
+ * Remove all search highlighting when clicking on a highlighted term
+ */
+ removeHighlightOnClick: function(){
+ jQuery('span.search_hit').on('click',
+ function(e){
+ jQuery(e.target).removeClass('search_hit', 1000);
+ }
+ );
+ },
+
+ /**
+ * Autosubmit quick select forms
+ *
+ * When a <select> tag has the class "quickselect", this script will
+ * automatically submit its parent form when the select value changes.
+ * It also hides the submit button of the form.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ quickSelect: function(){
+ jQuery('select.quickselect')
+ .on('change', function(e){ e.target.form.submit(); })
+ .closest('form').find(':button').not('.show').hide();
+ },
+
+ /**
+ * Display error for Windows Shares on browsers other than IE
+ *
+ * @author Michael Klier <chi@chimeric.de>
+ */
+ checkWindowsShares: function() {
+ if(!LANG.nosmblinks || navigator.userAgent.match(/(Trident|MSIE|Edge)/)) {
+ // No warning requested or none necessary
+ return;
+ }
+
+ jQuery('a.windows').on('click', function(){
+ alert(LANG.nosmblinks.replace(/\\n/,"\n"));
+ });
+ },
+
+ /**
+ * Hide list subscription style if target is a page
+ *
+ * @author Adrian Lang <lang@cosmocode.de>
+ * @author Pierre Spring <pierre.spring@caillou.ch>
+ */
+ subscription: function(){
+ var $form, $list, $digest;
+
+ $form = jQuery('#subscribe__form');
+ if (0 === $form.length) return;
+
+ $list = $form.find("input[name='sub_style'][value='list']");
+ $digest = $form.find("input[name='sub_style'][value='digest']");
+
+ $form.find("input[name='sub_target']")
+ .on('click',
+ function () {
+ var $this = jQuery(this), show_list;
+ if (!$this.prop('checked')) {
+ return;
+ }
+
+ show_list = $this.val().match(/:$/);
+ $list.parent().dw_toggle(show_list);
+ if (!show_list && $list.prop('checked')) {
+ $digest.prop('checked', 'checked');
+ }
+ }
+ )
+ .filter(':checked')
+ .trigger('click');
+ },
+
+ /**
+ * disable multiple revisions checkboxes if two are checked
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Anika Henke <anika@selfthinker.org>
+ */
+ revisionBoxHandler: function() {
+ var $revisions = jQuery('#page__revisions');
+ var $all = jQuery('input[type=checkbox]', $revisions);
+ var $checked = $all.filter(':checked');
+ var $button = jQuery('button', $revisions);
+
+ if($checked.length < 2) {
+ $all.prop('disabled', false);
+ $button.prop('disabled', true);
+ } else {
+ $all.prop('disabled', true);
+ $button.prop('disabled', false);
+ $checked.each(function(i) {
+ jQuery(this).prop('disabled', false);
+ if(i>1) {
+ jQuery(this).prop('checked', false);
+ }
+ });
+ }
+ }
+};
+
+jQuery(dw_behaviour.init);
diff --git a/platform/www/lib/scripts/compatibility.js b/platform/www/lib/scripts/compatibility.js
new file mode 100644
index 0000000..154aead
--- /dev/null
+++ b/platform/www/lib/scripts/compatibility.js
@@ -0,0 +1,42 @@
+/**
+ * Mark a JavaScript function as deprecated
+ *
+ * This will print a warning to the JavaScript console (if available) in
+ * Firebug and Chrome and a stack trace (if available) to easily locate the
+ * problematic function call.
+ *
+ * @param msg optional message to print
+ */
+function DEPRECATED(msg){
+ if(!window.console) return;
+ if(!msg) msg = '';
+
+ var func;
+ if(arguments.callee) func = arguments.callee.caller.name;
+ if(func) func = ' '+func+'()';
+ var line = 'DEPRECATED function call'+func+'. '+msg;
+
+ if(console.warn){
+ console.warn(line);
+ }else{
+ console.log(line);
+ }
+
+ if(console.trace) console.trace();
+}
+
+/**
+ * Construct a wrapper function for deprecated function names
+ *
+ * This function returns a wrapper function which just calls DEPRECATED
+ * and the new function.
+ *
+ * @param func The new function
+ * @param context Optional; The context (`this`) of the call
+ */
+function DEPRECATED_WRAP(func, context) {
+ return function () {
+ DEPRECATED();
+ return func.apply(context || this, arguments);
+ };
+}
diff --git a/platform/www/lib/scripts/cookie.js b/platform/www/lib/scripts/cookie.js
new file mode 100644
index 0000000..e260e59
--- /dev/null
+++ b/platform/www/lib/scripts/cookie.js
@@ -0,0 +1,71 @@
+/**
+* Handles the cookie used by several JavaScript functions
+*
+* Only a single cookie is written and read. You may only save
+* simple name-value pairs - no complex types!
+*
+* You should only use the getValue and setValue methods
+*
+* @author Andreas Gohr <andi@splitbrain.org>
+* @author Michal Rezler <m.rezler@centrum.cz>
+*/
+var DokuCookie = {
+ data: {},
+ name: 'DOKU_PREFS',
+
+ /**
+ * Save a value to the cookie
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ setValue: function(key,val){
+ var text = [],
+ _this = this;
+ this.init();
+ if (val === false){
+ delete this.data[key];
+ }else{
+ val = val + "";
+ this.data[key] = val;
+ }
+
+
+ //save the whole data array
+ jQuery.each(_this.data, function (key, val) {
+ if (_this.data.hasOwnProperty(key)) {
+ text.push(encodeURIComponent(key)+'#'+encodeURIComponent(val));
+ }
+ });
+ jQuery.cookie(this.name, text.join('#'), {expires: 365, path: DOKU_COOKIE_PARAM.path, secure: DOKU_COOKIE_PARAM.secure});
+ },
+
+ /**
+ * Get a Value from the Cookie
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param def default value if key does not exist; if not set, returns undefined by default
+ */
+ getValue: function(key, def){
+ this.init();
+ return this.data.hasOwnProperty(key) ? this.data[key] : def;
+ },
+
+ /**
+ * Loads the current set cookie
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ init: function(){
+ var text, parts, i;
+ if(!jQuery.isEmptyObject(this.data)) {
+ return;
+ }
+ text = jQuery.cookie(this.name);
+ if(text){
+ parts = text.split('#');
+ for(i = 0; i < parts.length; i += 2){
+ this.data[decodeURIComponent(parts[i])] = decodeURIComponent(parts[i+1]);
+ }
+ }
+ }
+};
diff --git a/platform/www/lib/scripts/delay.js b/platform/www/lib/scripts/delay.js
new file mode 100644
index 0000000..edd53de
--- /dev/null
+++ b/platform/www/lib/scripts/delay.js
@@ -0,0 +1,70 @@
+/**
+ * Manage delayed and timed actions
+ *
+ * @license GPL2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Adrian Lang <lang@cosmocode.de>
+ */
+
+/**
+ * Provide a global callback for window.setTimeout
+ *
+ * To get a timeout for non-global functions, just call
+ * delay.add(func, timeout).
+ */
+var timer = {
+ _cur_id: 0,
+ _handlers: {},
+
+ execDispatch: function (id) {
+ timer._handlers[id]();
+ },
+
+ add: function (func, timeout) {
+ var id = ++timer._cur_id;
+ timer._handlers[id] = func;
+ return window.setTimeout('timer.execDispatch(' + id + ')', timeout);
+ }
+};
+
+/**
+ * Provide a delayed start
+ *
+ * To call a function with a delay, just create a new Delay(func, timeout) and
+ * call that object’s method “start”.
+ */
+function Delay (func, timeout) {
+ this.func = func;
+ if (timeout) {
+ this.timeout = timeout;
+ }
+}
+
+Delay.prototype = {
+ func: null,
+ timeout: 500,
+
+ delTimer: function () {
+ if (this.timer !== null) {
+ window.clearTimeout(this.timer);
+ this.timer = null;
+ }
+ },
+
+ start: function () {
+ DEPRECATED('don\'t use the Delay object, use window.timeout with a callback instead');
+ this.delTimer();
+ var _this = this;
+ this.timer = timer.add(function () { _this.exec.call(_this); },
+ this.timeout);
+
+ this._data = {
+ _this: arguments[0],
+ _params: Array.prototype.slice.call(arguments, 2)
+ };
+ },
+
+ exec: function () {
+ this.delTimer();
+ this.func.call(this._data._this, this._data._params);
+ }
+};
diff --git a/platform/www/lib/scripts/edit.js b/platform/www/lib/scripts/edit.js
new file mode 100644
index 0000000..f53a6d4
--- /dev/null
+++ b/platform/www/lib/scripts/edit.js
@@ -0,0 +1,307 @@
+/**
+ * Functions for text editing (toolbar stuff)
+ *
+ * @todo most of the stuff in here should be revamped and then moved to toolbar.js
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+/**
+ * Creates a toolbar button through the DOM
+ * Called for each entry of toolbar definition array (built by inc/toolbar.php and extended via js)
+ *
+ * Style the buttons through the toolbutton class
+ *
+ * @param {string} icon image filename, relative to folder lib/images/toolbar/
+ * @param {string} label title of button, show on mouseover
+ * @param {string} key hint in title of button for access key
+ * @param {string} id id of button, and '<id>_ico' of icon
+ * @param {string} classname for styling buttons
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Michal Rezler <m.rezler@centrum.cz>
+ */
+function createToolButton(icon,label,key,id,classname){
+ var $btn = jQuery(document.createElement('button')),
+ $ico = jQuery(document.createElement('img'));
+
+ // prepare the basic button stuff
+ $btn.addClass('toolbutton');
+ if(classname){
+ $btn.addClass(classname);
+ }
+
+ $btn.attr('title', label).attr('aria-controls', 'wiki__text');
+ if(key){
+ $btn.attr('title', label + ' ['+key.toUpperCase()+']')
+ .attr('accessKey', key);
+ }
+
+ // set IDs if given
+ if(id){
+ $btn.attr('id', id);
+ $ico.attr('id', id+'_ico');
+ }
+
+ // create the icon and add it to the button
+ if(icon.substr(0,1) !== '/'){
+ icon = DOKU_BASE + 'lib/images/toolbar/' + icon;
+ }
+ $ico.attr('src', icon);
+ $ico.attr('alt', '');
+ $ico.attr('width', 16);
+ $ico.attr('height', 16);
+ $btn.append($ico);
+
+ // we have to return a DOM object (for compatibility reasons)
+ return $btn[0];
+}
+
+/**
+ * Creates a picker window for inserting text
+ *
+ * The given list can be an associative array with text,icon pairs
+ * or a simple list of text. Style the picker window through the picker
+ * class or the picker buttons with the pickerbutton class. Picker
+ * windows are appended to the body and created invisible.
+ *
+ * @param {string} id the ID to assign to the picker
+ * @param {Array} props the properties for the picker
+ * @param {string} edid the ID of the textarea
+ * @return DOMobject the created picker
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function createPicker(id,props,edid){
+ // create the wrapping div
+ var $picker = jQuery(document.createElement('div'));
+
+ $picker.addClass('picker a11y');
+ if(props['class']){
+ $picker.addClass(props['class']);
+ }
+
+ $picker.attr('id', id).css('position', 'absolute');
+
+ function $makebutton(title) {
+ var $btn = jQuery(document.createElement('button'))
+ .addClass('pickerbutton').attr('title', title)
+ .attr('aria-controls', edid)
+ .on('click', bind(pickerInsert, title, edid))
+ .appendTo($picker);
+ return $btn;
+ }
+
+ jQuery.each(props.list, function (key, item) {
+ if (!props.list.hasOwnProperty(key)) {
+ return;
+ }
+
+ if(isNaN(key)){
+ // associative array -> treat as text => image pairs
+ if (item.substr(0,1) !== '/') {
+ item = DOKU_BASE+'lib/images/'+props.icobase+'/'+item;
+ }
+ jQuery(document.createElement('img'))
+ .attr('src', item)
+ .attr('alt', '')
+ .appendTo($makebutton(key));
+ }else if (typeof item == 'string'){
+ // a list of text -> treat as text picker
+ $makebutton(item).text(item);
+ }else{
+ // a list of lists -> treat it as subtoolbar
+ initToolbar($picker,edid,props.list);
+ return false; // all buttons handled already
+ }
+
+ });
+ jQuery('body').append($picker);
+
+ // we have to return a DOM object (for compatibility reasons)
+ return $picker[0];
+}
+
+/**
+ * Called by picker buttons to insert Text and close the picker again
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function pickerInsert(text,edid){
+ insertAtCarret(edid,text);
+ pickerClose();
+}
+
+/**
+ * Add button action for signature button
+ *
+ * @param {jQuery} $btn Button element to add the action to
+ * @param {Array} props Associative array of button properties
+ * @param {string} edid ID of the editor textarea
+ * @return {string} picker id for aria-controls attribute
+ * @author Gabriel Birke <birke@d-scribe.de>
+ */
+function addBtnActionSignature($btn, props, edid) {
+ if(typeof SIG != 'undefined' && SIG != ''){
+ $btn.on('click', function (e) {
+ insertAtCarret(edid,SIG);
+ e.preventDefault();
+ });
+ return edid;
+ }
+ return '';
+}
+
+/**
+ * Determine the current section level while editing
+ *
+ * @param {string} textboxId ID of the text field
+ *
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+function currentHeadlineLevel(textboxId){
+ var field = jQuery('#' + textboxId)[0],
+ s = false,
+ opts = [field.value.substr(0,DWgetSelection(field).start)];
+ if (field.form && field.form.prefix) {
+ // we need to look in prefix context
+ opts.push(field.form.prefix.value);
+ }
+
+ jQuery.each(opts, function (_, opt) {
+ // Check whether there is a headline in the given string
+ var str = "\n" + opt,
+ lasthl = str.lastIndexOf("\n==");
+ if (lasthl !== -1) {
+ s = str.substr(lasthl+1,6);
+ return false;
+ }
+ });
+ if (s === false) {
+ return 0;
+ }
+ return 7 - s.match(/^={2,6}/)[0].length;
+}
+
+
+/**
+ * global var used for not saved yet warning
+ */
+window.textChanged = false;
+
+/**
+ * global var which stores original editor content
+ */
+window.doku_edit_text_content = '';
+/**
+ * Delete the draft before leaving the page
+ */
+function deleteDraft() {
+ if (is_opera || window.keepDraft) {
+ return;
+ }
+
+ var $dwform = jQuery('#dw__editform');
+
+ if($dwform.length === 0) {
+ return;
+ }
+
+ // remove a possibly saved draft using ajax
+ jQuery.post(DOKU_BASE + 'lib/exe/ajax.php',
+ {
+ call: 'draftdel',
+ id: $dwform.find('input[name=id]').val()
+ }
+ );
+}
+
+/**
+ * Activate "not saved" dialog, add draft deletion to page unload,
+ * add handlers to monitor changes
+ * Note: textChanged could be set by e.g. html_edit() as well
+ *
+ * Sets focus to the editbox as well
+ */
+jQuery(function () {
+ var $editform = jQuery('#dw__editform');
+ if ($editform.length == 0) {
+ return;
+ }
+
+ var $edit_text = jQuery('#wiki__text');
+ if ($edit_text.length > 0) {
+ if($edit_text.attr('readOnly')) {
+ return;
+ }
+
+ // set focus and place cursor at the start
+ var sel = DWgetSelection($edit_text[0]);
+ sel.start = 0;
+ sel.end = 0;
+ DWsetSelection(sel);
+ $edit_text.trigger('focus');
+
+ doku_edit_text_content = $edit_text.val();
+ }
+
+ var changeHandler = function() {
+ doku_hasTextBeenModified();
+
+ doku_summaryCheck();
+ };
+
+ $editform.change(changeHandler);
+ $editform.keydown(changeHandler);
+
+ window.onbeforeunload = function(){
+ if(window.textChanged) {
+ return LANG.notsavedyet;
+ }
+ };
+ window.onunload = deleteDraft;
+
+ // reset change memory var on submit
+ jQuery('#edbtn__save').on('click',
+ function() {
+ window.onbeforeunload = '';
+ textChanged = false;
+ }
+ );
+ jQuery('#edbtn__preview').on('click',
+ function() {
+ window.onbeforeunload = '';
+ textChanged = false;
+ window.keepDraft = true; // needed to keep draft on page unload
+ }
+ );
+
+ var $summary = jQuery('#edit__summary');
+ $summary.on('change keyup', doku_summaryCheck);
+
+ if (textChanged) doku_summaryCheck();
+});
+
+/**
+ * Updates textChanged variable if content of the editor has been modified
+ */
+function doku_hasTextBeenModified() {
+ if (!textChanged) {
+ var $edit_text = jQuery('#wiki__text');
+
+ if ($edit_text.length > 0) {
+ textChanged = doku_edit_text_content != $edit_text.val();
+ } else {
+ textChanged = true;
+ }
+ }
+}
+
+/**
+ * Checks if a summary was entered - if not the style is changed
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function doku_summaryCheck(){
+ var $sum = jQuery('#edit__summary'),
+ missing = $sum.val() === '';
+ $sum.toggleClass('missing', missing).toggleClass('edit', !missing);
+}
diff --git a/platform/www/lib/scripts/editor.js b/platform/www/lib/scripts/editor.js
new file mode 100644
index 0000000..0df5561
--- /dev/null
+++ b/platform/www/lib/scripts/editor.js
@@ -0,0 +1,205 @@
+/**
+ * The DokuWiki editor features
+ *
+ * These are the advanced features of the editor. It does NOT contain any
+ * code for the toolbar buttons and it functions. See toolbar.js for that.
+ */
+
+var dw_editor = {
+
+ /**
+ * initialize the default editor functionality
+ *
+ * All other functions can also be called separately for non-default
+ * textareas
+ */
+ init: function(){
+ var $editor = jQuery('#wiki__text');
+ if($editor.length === 0) {
+ return;
+ }
+
+ dw_editor.initSizeCtl('#size__ctl',$editor);
+
+ if($editor.attr('readOnly')) {
+ return;
+ }
+
+ $editor.keydown(dw_editor.keyHandler);
+
+ },
+
+ /**
+ * Add the edit window size and wrap controls
+ *
+ * Initial values are read from cookie if it exists
+ *
+ * @param selector ctlarea the div to place the controls
+ * @param selector editor the textarea to control
+ */
+ initSizeCtl: function(ctlarea,editor){
+ var $ctl = jQuery(ctlarea),
+ $textarea = jQuery(editor);
+
+ if($ctl.length === 0 || $textarea.length === 0) {
+ return;
+ }
+
+ $textarea.css('height', DokuCookie.getValue('sizeCtl') || '300px');
+
+ var wrp = DokuCookie.getValue('wrapCtl');
+ if(wrp){
+ dw_editor.setWrap($textarea[0], wrp);
+ } // else use default value
+
+ jQuery.each([
+ ['larger', function(){dw_editor.sizeCtl(editor,100);}],
+ ['smaller', function(){dw_editor.sizeCtl(editor,-100);}],
+ ['wrap', function(){dw_editor.toggleWrap(editor);}]
+ ], function (_, img) {
+ jQuery(document.createElement('img'))
+ .attr('src', DOKU_BASE+'lib/images/' + img[0] + '.gif')
+ .attr('alt', '')
+ .on('click', img[1])
+ .appendTo($ctl);
+ });
+ },
+
+ /**
+ * This sets the vertical size of the editbox and adjusts the cookie
+ *
+ * @param selector editor the textarea to control
+ * @param int val the relative value to resize in pixel
+ */
+ sizeCtl: function(editor,val){
+ var $textarea = jQuery(editor),
+ height = parseInt($textarea.css('height')) + val;
+ $textarea.css('height', height+'px');
+ DokuCookie.setValue('sizeCtl',$textarea.css('height'));
+ },
+
+ /**
+ * Toggle the wrapping mode of the editor textarea and adjusts the
+ * cookie
+ *
+ * @param selector editor the textarea to control
+ */
+ toggleWrap: function(editor){
+ var $textarea = jQuery(editor),
+ wrap = $textarea.attr('wrap');
+ dw_editor.setWrap($textarea[0],
+ (wrap && wrap.toLowerCase() == 'off') ? 'soft' : 'off');
+ DokuCookie.setValue('wrapCtl',$textarea.attr('wrap'));
+ },
+
+ /**
+ * Set the wrapping mode of a textarea
+ *
+ * @author Fluffy Convict <fluffyconvict@hotmail.com>
+ * @author <shutdown@flashmail.com>
+ * @link http://news.hping.org/comp.lang.javascript.archive/12265.html
+ * @link https://bugzilla.mozilla.org/show_bug.cgi?id=41464
+ * @param DomObject textarea
+ * @param string wrapAttrValue
+ */
+ setWrap: function(textarea, wrapAttrValue){
+ textarea.setAttribute('wrap', wrapAttrValue);
+
+ // Fix display for mozilla
+ var parNod = textarea.parentNode;
+ var nxtSib = textarea.nextSibling;
+ parNod.removeChild(textarea);
+ parNod.insertBefore(textarea, nxtSib);
+ },
+
+ /**
+ * Make intended formattings easier to handle
+ *
+ * Listens to all key inputs and handle indentions
+ * of lists and code blocks
+ *
+ * Currently handles space, backspace, enter and
+ * ctrl-enter presses
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @fixme handle tabs
+ * @param event e - the key press event object
+ */
+ keyHandler: function(e){
+ if(jQuery.inArray(e.keyCode,[8, 10, 13, 32]) === -1) {
+ return;
+ }
+ var selection = DWgetSelection(this);
+ if(selection.getLength() > 0) {
+ return; //there was text selected, keep standard behavior
+ }
+ var search = "\n"+this.value.substr(0,selection.start);
+ var linestart = Math.max(search.lastIndexOf("\n"),
+ search.lastIndexOf("\r")); //IE workaround
+ search = search.substr(linestart);
+
+ if((e.keyCode == 13 || e.keyCode == 10) && e.ctrlKey) { // Ctrl-Enter (With Chrome workaround)
+ // Submit current edit
+ jQuery('#edbtn__save').trigger('click');
+ e.preventDefault(); // prevent enter key
+ return false;
+ }else if(e.keyCode == 13){ // Enter
+ // keep current indention for lists and code
+ var match = search.match(/(\n +([\*-] ?)?)/);
+ if(match){
+ var scroll = this.scrollHeight;
+ var match2 = search.match(/^\n +[\*-]\s*$/);
+ // Cancel list if the last item is empty (i. e. two times enter)
+ if (match2 && this.value.substr(selection.start).match(/^($|\r?\n)/)) {
+ this.value = this.value.substr(0, linestart) + "\n" +
+ this.value.substr(selection.start);
+ selection.start = linestart + 1;
+ selection.end = linestart + 1;
+ DWsetSelection(selection);
+ } else {
+ insertAtCarret(this.id,match[1]);
+ }
+ this.scrollTop += (this.scrollHeight - scroll);
+ e.preventDefault(); // prevent enter key
+ return false;
+ }
+ }else if(e.keyCode == 8){ // Backspace
+ // unindent lists
+ var match = search.match(/(\n +)([*-] ?)$/);
+ if(match){
+ var spaces = match[1].length-1;
+
+ if(spaces > 3){ // unindent one level
+ this.value = this.value.substr(0,linestart)+
+ this.value.substr(linestart+2);
+ selection.start = selection.start - 2;
+ selection.end = selection.start;
+ }else{ // delete list point
+ this.value = this.value.substr(0,linestart)+
+ this.value.substr(selection.start);
+ selection.start = linestart;
+ selection.end = linestart;
+ }
+ DWsetSelection(selection);
+ e.preventDefault(); // prevent backspace
+ return false;
+ }
+ }else if(e.keyCode == 32){ // Space
+ // intend list item
+ var match = search.match(/(\n +)([*-] )$/);
+ if(match){
+ this.value = this.value.substr(0,linestart)+' '+
+ this.value.substr(linestart);
+ selection.start = selection.start + 2;
+ selection.end = selection.start;
+ DWsetSelection(selection);
+ e.preventDefault(); // prevent space
+ return false;
+ }
+ }
+ }
+
+
+};
+
+jQuery(dw_editor.init);
diff --git a/platform/www/lib/scripts/fileuploader.js b/platform/www/lib/scripts/fileuploader.js
new file mode 100644
index 0000000..d627895
--- /dev/null
+++ b/platform/www/lib/scripts/fileuploader.js
@@ -0,0 +1,1249 @@
+/**
+ * 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){
+ if(obj.hasOwnProperty(i) && typeof obj[i] != 'function') {
+ 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/platform/www/lib/scripts/fileuploaderextended.js b/platform/www/lib/scripts/fileuploaderextended.js
new file mode 100644
index 0000000..b7f9f5f
--- /dev/null
+++ b/platform/www/lib/scripts/fileuploaderextended.js
@@ -0,0 +1,345 @@
+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';
+
+ // name suggestion (simplified cleanID)
+ var nameElement = this._find(item, 'nameInput');
+ fileName = fileName.toLowerCase();
+ fileName = fileName.replace(/([ !"#$%&\'()+,\/;<=>?@[\]^`{|}~:]+)/g, '_');
+ fileName = fileName.replace(/^_+/,'');
+ 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>' +
+ '<ul class="qq-upload-list"></ul>' +
+ '<div class="qq-action-container">' +
+ ' <button class="qq-upload-action" type="submit" id="mediamanager__upload_button">' + LANG.media_upload_btn + '</button>' +
+ ' <label class="qq-overwrite-check"><input type="checkbox" value="1" name="ow" class="dw__ow"> <span>' + LANG.media_overwrt + '</span></label>' +
+ '</div>' +
+ '</div>',
+
+ // template for one item in file list
+ fileTemplate: '<li>' +
+ '<span class="qq-upload-file hidden"></span>' +
+ ' <input class="qq-upload-name-input edit" type="text" value="" />' +
+ ' <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 error">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',
+ nameInput: 'qq-upload-name-input',
+ overwriteInput: 'qq-overwrite-check',
+ uploadButton: 'qq-upload-action',
+ 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).prop('disabled', true);
+ });
+ });
+ },
+
+ _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, '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 + '" id="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 type="hidden" value="1" name="recent">';
+ button += '<button type="submit">' + LANG.media_done_btn + '</button></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").each(function (i) {
+ jQuery(this).removeClass('hidden');
+ });
+ 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').is(':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').is(':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").each(function (i) {
+ jQuery(this).removeClass('hidden');
+ });
+ for (key in this._files) {
+ this.upload(key, params);
+ }
+
+ }
+});
diff --git a/platform/www/lib/scripts/helpers.js b/platform/www/lib/scripts/helpers.js
new file mode 100644
index 0000000..99137c5
--- /dev/null
+++ b/platform/www/lib/scripts/helpers.js
@@ -0,0 +1,69 @@
+/**
+ * Various helper functions
+ */
+
+/**
+ * A PHP-style substr_replace
+ *
+ * Supports negative start and length and omitting length, but not
+ * str and replace arrays.
+ * See http://php.net/substr-replace for further documentation.
+ */
+function substr_replace(str, replace, start, length) {
+ var a2, b1;
+ a2 = (start < 0 ? str.length : 0) + start;
+ if (typeof length === 'undefined') {
+ length = str.length - a2;
+ } else if (length < 0 && start < 0 && length <= start) {
+ length = 0;
+ }
+ b1 = (length < 0 ? str.length : a2) + length;
+ return str.substring(0, a2) + replace + str.substring(b1);
+}
+
+/**
+ * Bind variables to a function call creating a closure
+ *
+ * Use this to circumvent variable scope problems when creating closures
+ * inside a loop
+ *
+ * @author Adrian Lang <lang@cosmocode.de>
+ * @link http://www.cosmocode.de/en/blog/gohr/2009-10/15-javascript-fixing-the-closure-scope-in-loops
+ * @param functionref fnc - the function to be called
+ * @param mixed - any arguments to be passed to the function
+ * @returns functionref
+ */
+function bind(fnc/*, ... */) {
+ var Aps = Array.prototype.slice,
+ // Store passed arguments in this scope.
+ // Since arguments is no Array nor has an own slice method,
+ // we have to apply the slice method from the Array.prototype
+ static_args = Aps.call(arguments, 1);
+
+ // Return a function evaluating the passed function with the
+ // given args and optional arguments passed on invocation.
+ return function (/* ... */) {
+ // Same here, but we use Array.prototype.slice solely for
+ // converting arguments to an Array.
+ return fnc.apply(this,
+ static_args.concat(Aps.call(arguments, 0)));
+ };
+}
+
+/**
+ * Report an error from a JS file to the console
+ *
+ * @param e The error object
+ * @param file The file in which the error occurred
+ */
+function logError(e, file) {
+ if (window.console && console.error) {
+ console.error('The error "%s: %s" occurred in file "%s". ' +
+ 'If this is in a plugin try updating or disabling the plugin, ' +
+ 'if this is in a template try updating the template or switching to the "dokuwiki" template.',
+ e.name, e.message, file);
+ if(e.stack) {
+ console.error(e.stack);
+ }
+ }
+}
diff --git a/platform/www/lib/scripts/hotkeys.js b/platform/www/lib/scripts/hotkeys.js
new file mode 100644
index 0000000..76a277a
--- /dev/null
+++ b/platform/www/lib/scripts/hotkeys.js
@@ -0,0 +1,302 @@
+/**
+ * Some of these scripts were taken from TinyMCE (http://tinymce.moxiecode.com/) and were modified for DokuWiki
+ *
+ * Class handles accesskeys using javascript and also provides ability
+ * to register and use other hotkeys as well.
+ *
+ * @author Marek Sacha <sachamar@fel.cvut.cz>
+ */
+function Hotkeys() {
+
+ this.shortcuts = new Array();
+
+ /**
+ * Set modifier keys, for instance:
+ * this.modifier = 'ctrl';
+ * this.modifier = 'ctrl+shift';
+ * this.modifier = 'ctrl+alt+shift';
+ * this.modifier = 'alt';
+ * this.modifier = 'alt+shift';
+ *
+ * overwritten in intitialize (see below)
+ */
+ this.modifier = 'ctrl+alt';
+
+ /**
+ * Initialization
+ *
+ * This function looks up all the accesskeys used in the current page
+ * (at anchor elements and button elements [type="submit"]) and registers
+ * appropriate shortcuts.
+ *
+ * Secondly, initialization registers listeners on document to catch all
+ * keyboard events.
+ *
+ * @author Marek Sacha <sachamar@fel.cvut.cz>
+ */
+ this.initialize = function() {
+ var t = this;
+
+ //switch modifier key based on OS FS#1958
+ if(is_macos){
+ t.modifier = 'ctrl+alt';
+ }else{
+ t.modifier = 'alt';
+ }
+
+ /**
+ * Lookup all anchors with accesskey and register event - go to anchor
+ * target.
+ */
+ var anchors = document.getElementsByTagName("a");
+ t.each(anchors, function(a) {
+ if (a.accessKey != "") {
+ t.addShortcut(t.modifier + '+' + a.accessKey, function() {
+ location.href = a.href;
+ });
+ a.accessKey = '';
+ }
+ });
+
+ /**
+ * Lookup all button [type="submit"] with accesskey and register event -
+ * perform "click" on a button.
+ */
+ var inputs = document.getElementsByTagName("button");
+ t.each(inputs, function(i) {
+ if (i.type == "submit" && i.accessKey != "") {
+ t.addShortcut(t.modifier + '+' + i.accessKey, function() {
+ i.click();
+ });
+ i.accessKey = '';
+ }
+ });
+
+ /**
+ * Lookup all buttons with accesskey and register event -
+ * perform "click" on a button.
+ */
+ var buttons = document.getElementsByTagName("button");
+ t.each(buttons, function(b) {
+ if (b.accessKey != "") {
+ t.addShortcut(t.modifier + '+' + b.accessKey, function() {
+ b.click();
+ });
+ b.accessKey = '';
+ }
+ });
+
+ /**
+ * Register listeners on document to catch keyboard events.
+ */
+
+ addEvent(document,'keyup',function (e) {
+ return t.onkeyup.call(t,e);
+ });
+
+ addEvent(document,'keypress',function (e) {
+ return t.onkeypress.call(t,e);
+ });
+
+ addEvent(document,'keydown',function (e) {
+ return t.onkeydown.call(t,e);
+ });
+ };
+
+ /**
+ * Keyup processing function
+ * Function returns true if keyboard event has registered handler, and
+ * executes the handler function.
+ *
+ * @param e KeyboardEvent
+ * @author Marek Sacha <sachamar@fel.cvut.cz>
+ * @return b boolean
+ */
+ this.onkeyup = function(e) {
+ var t = this;
+ var v = t.findShortcut(e);
+ if (v != null && v != false) {
+ v.func.call(t);
+ return false;
+ }
+ return true;
+ };
+
+ /**
+ * Keydown processing function
+ * Function returns true if keyboard event has registered handler
+ *
+ * @param e KeyboardEvent
+ * @author Marek Sacha <sachamar@fel.cvut.cz>
+ * @return b boolean
+ */
+ this.onkeydown = function(e) {
+ var t = this;
+ var v = t.findShortcut(e);
+ if (v != null && v != false) {
+ return false;
+ }
+ return true;
+ };
+
+ /**
+ * Keypress processing function
+ * Function returns true if keyboard event has registered handler
+ *
+ * @param e KeyboardEvent
+ * @author Marek Sacha <sachamar@fel.cvut.cz>
+ * @return b
+ */
+ this.onkeypress = function(e) {
+ var t = this;
+ var v = t.findShortcut(e);
+ if (v != null && v != false) {
+ return false;
+ }
+ return true;
+ };
+
+ /**
+ * Register new shortcut
+ *
+ * This function registers new shortcuts, each shortcut is defined by its
+ * modifier keys and a key (with + as delimiter). If shortcut is pressed
+ * cmd_function is performed.
+ *
+ * For example:
+ * pa = "ctrl+alt+p";
+ * pa = "shift+alt+s";
+ *
+ * Full example of method usage:
+ * hotkeys.addShortcut('ctrl+s',function() {
+ * document.getElementByID('form_1').submit();
+ * });
+ *
+ * @param pa String description of the shortcut (ctrl+a, ctrl+shift+p, .. )
+ * @param cmd_func Function to be called if shortcut is pressed
+ * @author Marek Sacha <sachamar@fel.cvut.cz>
+ */
+ this.addShortcut = function(pa, cmd_func) {
+ var t = this;
+
+ var o = {
+ func : cmd_func,
+ alt : false,
+ ctrl : false,
+ shift : false
+ };
+
+ t.each(t.explode(pa, '+'), function(v) {
+ switch (v) {
+ case 'alt':
+ case 'ctrl':
+ case 'shift':
+ o[v] = true;
+ break;
+
+ default:
+ o.charCode = v.charCodeAt(0);
+ o.keyCode = v.toUpperCase().charCodeAt(0);
+ }
+ });
+
+ t.shortcuts.push((o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode, o);
+
+ return true;
+ };
+
+ /**
+ * @property isMac
+ */
+ this.isMac = is_macos;
+
+ /**
+ * Apply function cb on each element of o in the namespace of s
+ * @param o Array of objects
+ * @param cb Function to be called on each object
+ * @param s Namespace to be used during call of cb (default namespace is o)
+ * @author Marek Sacha <sachamar@fel.cvut.cz>
+ */
+ this.each = function(o, cb, s) {
+ var n, l;
+
+ if (!o)
+ return 0;
+
+ s = s || o;
+
+ if (o.length !== undefined) {
+ // Indexed arrays, needed for Safari
+ for (n=0, l = o.length; n < l; n++) {
+ if (cb.call(s, o[n], n, o) === false)
+ return 0;
+ }
+ } else {
+ // Hashtables
+ for (n in o) {
+ if (o.hasOwnProperty(n)) {
+ if (cb.call(s, o[n], n, o) === false)
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+ };
+
+ /**
+ * Explode string according to delimiter
+ * @param s String
+ * @param d Delimiter (default ',')
+ * @author Marek Sacha <sachamar@fel.cvut.cz>
+ * @return a Array of tokens
+ */
+ this.explode = function(s, d) {
+ return s.split(d || ',');
+ };
+
+ /**
+ * Find if the shortcut was registered
+ *
+ * @param e KeyboardEvent
+ * @author Marek Sacha <sachamar@fel.cvut.cz>
+ * @return v Shortcut structure or null if not found
+ */
+ this.findShortcut = function (e) {
+ var t = this;
+ var v = null;
+
+ /* No modifier key used - shortcut does not exist */
+ if (!e.altKey && !e.ctrlKey && !e.metaKey) {
+ return v;
+ }
+
+ t.each(t.shortcuts, function(o) {
+ if (o.ctrl != e.ctrlKey)
+ return;
+
+ if (o.alt != e.altKey)
+ return;
+
+ if (o.shift != e.shiftKey)
+ return;
+
+ if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
+ v = o;
+ return;
+ }
+ });
+ return v;
+ };
+}
+
+/**
+ * Init function for hotkeys. Called from js.php, to ensure hotkyes are initialized after toolbar.
+ * Call of addInitEvent(initializeHotkeys) is unnecessary now.
+ *
+ * @author Marek Sacha <sachamar@fel.cvut.cz>
+ */
+function initializeHotkeys() {
+ var hotkeys = new Hotkeys();
+ hotkeys.initialize();
+}
diff --git a/platform/www/lib/scripts/index.html b/platform/www/lib/scripts/index.html
new file mode 100644
index 0000000..977f90e
--- /dev/null
+++ b/platform/www/lib/scripts/index.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="refresh" content="0; URL=../../" />
+<meta name="robots" content="noindex" />
+<title>nothing here...</title>
+</head>
+<body>
+<!-- this is just here to prevent directory browsing -->
+</body>
+</html>
diff --git a/platform/www/lib/scripts/index.js b/platform/www/lib/scripts/index.js
new file mode 100644
index 0000000..4b67a0b
--- /dev/null
+++ b/platform/www/lib/scripts/index.js
@@ -0,0 +1,16 @@
+var dw_index = jQuery('#index__tree').dw_tree({deferInit: true,
+ load_data: function (show_sublist, $clicky) {
+ jQuery.post(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ $clicky[0].search.substr(1) + '&call=index',
+ show_sublist, 'html'
+ );
+ }
+});
+jQuery(function () {
+ var $tree = jQuery('#index__tree');
+
+ dw_index.$obj = $tree;
+
+ dw_index.init();
+});
diff --git a/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_55_fbf9ee_1x400.png b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_55_fbf9ee_1x400.png
new file mode 100644
index 0000000..6b1f24d
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_55_fbf9ee_1x400.png
Binary files differ
diff --git a/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_65_ffffff_1x400.png b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_65_ffffff_1x400.png
new file mode 100644
index 0000000..5f6fc57
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_65_ffffff_1x400.png
Binary files differ
diff --git a/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_75_dadada_1x400.png b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_75_dadada_1x400.png
new file mode 100644
index 0000000..8bc2c8b
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_75_dadada_1x400.png
Binary files differ
diff --git a/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_75_e6e6e6_1x400.png b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_75_e6e6e6_1x400.png
new file mode 100644
index 0000000..be993d5
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_75_e6e6e6_1x400.png
Binary files differ
diff --git a/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_95_fef1ec_1x400.png b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_95_fef1ec_1x400.png
new file mode 100644
index 0000000..b9356a2
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_95_fef1ec_1x400.png
Binary files differ
diff --git a/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png
new file mode 100644
index 0000000..9e3afb5
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png
Binary files differ
diff --git a/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_222222_256x240.png b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_222222_256x240.png
new file mode 100644
index 0000000..6ba300b
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_222222_256x240.png
Binary files differ
diff --git a/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_2e83ff_256x240.png b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_2e83ff_256x240.png
new file mode 100644
index 0000000..1c7b633
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_2e83ff_256x240.png
Binary files differ
diff --git a/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_454545_256x240.png b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_454545_256x240.png
new file mode 100644
index 0000000..bd90b26
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_454545_256x240.png
Binary files differ
diff --git a/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_888888_256x240.png b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_888888_256x240.png
new file mode 100644
index 0000000..813f18d
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_888888_256x240.png
Binary files differ
diff --git a/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_cd0a0a_256x240.png b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_cd0a0a_256x240.png
new file mode 100644
index 0000000..cfb3d44
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui-theme/images/ui-icons_cd0a0a_256x240.png
Binary files differ
diff --git a/platform/www/lib/scripts/jquery/jquery-ui-theme/smoothness.css b/platform/www/lib/scripts/jquery/jquery-ui-theme/smoothness.css
new file mode 100644
index 0000000..eab564a
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui-theme/smoothness.css
@@ -0,0 +1,1311 @@
+/*! jQuery UI - v1.12.1 - 2016-09-14
+* http://jqueryui.com
+* Includes: core.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, draggable.css, resizable.css, progressbar.css, selectable.css, selectmenu.css, slider.css, sortable.css, spinner.css, tabs.css, tooltip.css, theme.css
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
+* Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden {
+ display: none;
+}
+.ui-helper-hidden-accessible {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+.ui-helper-reset {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ line-height: 1.3;
+ text-decoration: none;
+ font-size: 100%;
+ list-style: none;
+}
+.ui-helper-clearfix:before,
+.ui-helper-clearfix:after {
+ content: "";
+ display: table;
+ border-collapse: collapse;
+}
+.ui-helper-clearfix:after {
+ clear: both;
+}
+.ui-helper-zfix {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ position: absolute;
+ opacity: 0;
+ filter:Alpha(Opacity=0); /* support: IE8 */
+}
+
+.ui-front {
+ z-index: 100;
+}
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled {
+ cursor: default !important;
+ pointer-events: none;
+}
+
+
+/* Icons
+----------------------------------*/
+.ui-icon {
+ display: inline-block;
+ vertical-align: middle;
+ margin-top: -.25em;
+ position: relative;
+ text-indent: -99999px;
+ overflow: hidden;
+ background-repeat: no-repeat;
+}
+
+.ui-widget-icon-block {
+ left: 50%;
+ margin-left: -8px;
+ display: block;
+}
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+.ui-accordion .ui-accordion-header {
+ display: block;
+ cursor: pointer;
+ position: relative;
+ margin: 2px 0 0 0;
+ padding: .5em .5em .5em .7em;
+ font-size: 100%;
+}
+.ui-accordion .ui-accordion-content {
+ padding: 1em 2.2em;
+ border-top: 0;
+ overflow: auto;
+}
+.ui-autocomplete {
+ position: absolute;
+ top: 0;
+ left: 0;
+ cursor: default;
+}
+.ui-menu {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: block;
+ outline: 0;
+}
+.ui-menu .ui-menu {
+ position: absolute;
+}
+.ui-menu .ui-menu-item {
+ margin: 0;
+ cursor: pointer;
+ /* support: IE10, see #8844 */
+ list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
+}
+.ui-menu .ui-menu-item-wrapper {
+ position: relative;
+ padding: 3px 1em 3px .4em;
+}
+.ui-menu .ui-menu-divider {
+ margin: 5px 0;
+ height: 0;
+ font-size: 0;
+ line-height: 0;
+ border-width: 1px 0 0 0;
+}
+.ui-menu .ui-state-focus,
+.ui-menu .ui-state-active {
+ margin: -1px;
+}
+
+/* icon support */
+.ui-menu-icons {
+ position: relative;
+}
+.ui-menu-icons .ui-menu-item-wrapper {
+ padding-left: 2em;
+}
+
+/* left-aligned */
+.ui-menu .ui-icon {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: .2em;
+ margin: auto 0;
+}
+
+/* right-aligned */
+.ui-menu .ui-menu-icon {
+ left: auto;
+ right: 0;
+}
+.ui-button {
+ padding: .4em 1em;
+ display: inline-block;
+ position: relative;
+ line-height: normal;
+ margin-right: .1em;
+ cursor: pointer;
+ vertical-align: middle;
+ text-align: center;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+
+ /* Support: IE <= 11 */
+ overflow: visible;
+}
+
+.ui-button,
+.ui-button:link,
+.ui-button:visited,
+.ui-button:hover,
+.ui-button:active {
+ text-decoration: none;
+}
+
+/* to make room for the icon, a width needs to be set here */
+.ui-button-icon-only {
+ width: 2em;
+ box-sizing: border-box;
+ text-indent: -9999px;
+ white-space: nowrap;
+}
+
+/* no icon support for input elements */
+input.ui-button.ui-button-icon-only {
+ text-indent: 0;
+}
+
+/* button icon element(s) */
+.ui-button-icon-only .ui-icon {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ margin-top: -8px;
+ margin-left: -8px;
+}
+
+.ui-button.ui-icon-notext .ui-icon {
+ padding: 0;
+ width: 2.1em;
+ height: 2.1em;
+ text-indent: -9999px;
+ white-space: nowrap;
+
+}
+
+input.ui-button.ui-icon-notext .ui-icon {
+ width: auto;
+ height: auto;
+ text-indent: 0;
+ white-space: normal;
+ padding: .4em 1em;
+}
+
+/* workarounds */
+/* Support: Firefox 5 - 40 */
+input.ui-button::-moz-focus-inner,
+button.ui-button::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+.ui-controlgroup {
+ vertical-align: middle;
+ display: inline-block;
+}
+.ui-controlgroup > .ui-controlgroup-item {
+ float: left;
+ margin-left: 0;
+ margin-right: 0;
+}
+.ui-controlgroup > .ui-controlgroup-item:focus,
+.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus {
+ z-index: 9999;
+}
+.ui-controlgroup-vertical > .ui-controlgroup-item {
+ display: block;
+ float: none;
+ width: 100%;
+ margin-top: 0;
+ margin-bottom: 0;
+ text-align: left;
+}
+.ui-controlgroup-vertical .ui-controlgroup-item {
+ box-sizing: border-box;
+}
+.ui-controlgroup .ui-controlgroup-label {
+ padding: .4em 1em;
+}
+.ui-controlgroup .ui-controlgroup-label span {
+ font-size: 80%;
+}
+.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item {
+ border-left: none;
+}
+.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item {
+ border-top: none;
+}
+.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content {
+ border-right: none;
+}
+.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content {
+ border-bottom: none;
+}
+
+/* Spinner specific style fixes */
+.ui-controlgroup-vertical .ui-spinner-input {
+
+ /* Support: IE8 only, Android < 4.4 only */
+ width: 75%;
+ width: calc( 100% - 2.4em );
+}
+.ui-controlgroup-vertical .ui-spinner .ui-spinner-up {
+ border-top-style: solid;
+}
+
+.ui-checkboxradio-label .ui-icon-background {
+ box-shadow: inset 1px 1px 1px #ccc;
+ border-radius: .12em;
+ border: none;
+}
+.ui-checkboxradio-radio-label .ui-icon-background {
+ width: 16px;
+ height: 16px;
+ border-radius: 1em;
+ overflow: visible;
+ border: none;
+}
+.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon,
+.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon {
+ background-image: none;
+ width: 8px;
+ height: 8px;
+ border-width: 4px;
+ border-style: solid;
+}
+.ui-checkboxradio-disabled {
+ pointer-events: none;
+}
+.ui-datepicker {
+ width: 17em;
+ padding: .2em .2em 0;
+ display: none;
+}
+.ui-datepicker .ui-datepicker-header {
+ position: relative;
+ padding: .2em 0;
+}
+.ui-datepicker .ui-datepicker-prev,
+.ui-datepicker .ui-datepicker-next {
+ position: absolute;
+ top: 2px;
+ width: 1.8em;
+ height: 1.8em;
+}
+.ui-datepicker .ui-datepicker-prev-hover,
+.ui-datepicker .ui-datepicker-next-hover {
+ top: 1px;
+}
+.ui-datepicker .ui-datepicker-prev {
+ left: 2px;
+}
+.ui-datepicker .ui-datepicker-next {
+ right: 2px;
+}
+.ui-datepicker .ui-datepicker-prev-hover {
+ left: 1px;
+}
+.ui-datepicker .ui-datepicker-next-hover {
+ right: 1px;
+}
+.ui-datepicker .ui-datepicker-prev span,
+.ui-datepicker .ui-datepicker-next span {
+ display: block;
+ position: absolute;
+ left: 50%;
+ margin-left: -8px;
+ top: 50%;
+ margin-top: -8px;
+}
+.ui-datepicker .ui-datepicker-title {
+ margin: 0 2.3em;
+ line-height: 1.8em;
+ text-align: center;
+}
+.ui-datepicker .ui-datepicker-title select {
+ font-size: 1em;
+ margin: 1px 0;
+}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year {
+ width: 45%;
+}
+.ui-datepicker table {
+ width: 100%;
+ font-size: .9em;
+ border-collapse: collapse;
+ margin: 0 0 .4em;
+}
+.ui-datepicker th {
+ padding: .7em .3em;
+ text-align: center;
+ font-weight: bold;
+ border: 0;
+}
+.ui-datepicker td {
+ border: 0;
+ padding: 1px;
+}
+.ui-datepicker td span,
+.ui-datepicker td a {
+ display: block;
+ padding: .2em;
+ text-align: right;
+ text-decoration: none;
+}
+.ui-datepicker .ui-datepicker-buttonpane {
+ background-image: none;
+ margin: .7em 0 0 0;
+ padding: 0 .2em;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 0;
+}
+.ui-datepicker .ui-datepicker-buttonpane button {
+ float: right;
+ margin: .5em .2em .4em;
+ cursor: pointer;
+ padding: .2em .6em .3em .6em;
+ width: auto;
+ overflow: visible;
+}
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
+ float: left;
+}
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi {
+ width: auto;
+}
+.ui-datepicker-multi .ui-datepicker-group {
+ float: left;
+}
+.ui-datepicker-multi .ui-datepicker-group table {
+ width: 95%;
+ margin: 0 auto .4em;
+}
+.ui-datepicker-multi-2 .ui-datepicker-group {
+ width: 50%;
+}
+.ui-datepicker-multi-3 .ui-datepicker-group {
+ width: 33.3%;
+}
+.ui-datepicker-multi-4 .ui-datepicker-group {
+ width: 25%;
+}
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
+ border-left-width: 0;
+}
+.ui-datepicker-multi .ui-datepicker-buttonpane {
+ clear: left;
+}
+.ui-datepicker-row-break {
+ clear: both;
+ width: 100%;
+ font-size: 0;
+}
+
+/* RTL support */
+.ui-datepicker-rtl {
+ direction: rtl;
+}
+.ui-datepicker-rtl .ui-datepicker-prev {
+ right: 2px;
+ left: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-next {
+ left: 2px;
+ right: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-prev:hover {
+ right: 1px;
+ left: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-next:hover {
+ left: 1px;
+ right: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane {
+ clear: right;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane button {
+ float: left;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
+.ui-datepicker-rtl .ui-datepicker-group {
+ float: right;
+}
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
+ border-right-width: 0;
+ border-left-width: 1px;
+}
+
+/* Icons */
+.ui-datepicker .ui-icon {
+ display: block;
+ text-indent: -99999px;
+ overflow: hidden;
+ background-repeat: no-repeat;
+ left: .5em;
+ top: .3em;
+}
+.ui-dialog {
+ position: absolute;
+ top: 0;
+ left: 0;
+ padding: .2em;
+ outline: 0;
+}
+.ui-dialog .ui-dialog-titlebar {
+ padding: .4em 1em;
+ position: relative;
+}
+.ui-dialog .ui-dialog-title {
+ float: left;
+ margin: .1em 0;
+ white-space: nowrap;
+ width: 90%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.ui-dialog .ui-dialog-titlebar-close {
+ position: absolute;
+ right: .3em;
+ top: 50%;
+ width: 20px;
+ margin: -10px 0 0 0;
+ padding: 1px;
+ height: 20px;
+}
+.ui-dialog .ui-dialog-content {
+ position: relative;
+ border: 0;
+ padding: .5em 1em;
+ background: none;
+ overflow: auto;
+}
+.ui-dialog .ui-dialog-buttonpane {
+ text-align: left;
+ border-width: 1px 0 0 0;
+ background-image: none;
+ margin-top: .5em;
+ padding: .3em 1em .5em .4em;
+}
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
+ float: right;
+}
+.ui-dialog .ui-dialog-buttonpane button {
+ margin: .5em .4em .5em 0;
+ cursor: pointer;
+}
+.ui-dialog .ui-resizable-n {
+ height: 2px;
+ top: 0;
+}
+.ui-dialog .ui-resizable-e {
+ width: 2px;
+ right: 0;
+}
+.ui-dialog .ui-resizable-s {
+ height: 2px;
+ bottom: 0;
+}
+.ui-dialog .ui-resizable-w {
+ width: 2px;
+ left: 0;
+}
+.ui-dialog .ui-resizable-se,
+.ui-dialog .ui-resizable-sw,
+.ui-dialog .ui-resizable-ne,
+.ui-dialog .ui-resizable-nw {
+ width: 7px;
+ height: 7px;
+}
+.ui-dialog .ui-resizable-se {
+ right: 0;
+ bottom: 0;
+}
+.ui-dialog .ui-resizable-sw {
+ left: 0;
+ bottom: 0;
+}
+.ui-dialog .ui-resizable-ne {
+ right: 0;
+ top: 0;
+}
+.ui-dialog .ui-resizable-nw {
+ left: 0;
+ top: 0;
+}
+.ui-draggable .ui-dialog-titlebar {
+ cursor: move;
+}
+.ui-draggable-handle {
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.ui-resizable {
+ position: relative;
+}
+.ui-resizable-handle {
+ position: absolute;
+ font-size: 0.1px;
+ display: block;
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.ui-resizable-disabled .ui-resizable-handle,
+.ui-resizable-autohide .ui-resizable-handle {
+ display: none;
+}
+.ui-resizable-n {
+ cursor: n-resize;
+ height: 7px;
+ width: 100%;
+ top: -5px;
+ left: 0;
+}
+.ui-resizable-s {
+ cursor: s-resize;
+ height: 7px;
+ width: 100%;
+ bottom: -5px;
+ left: 0;
+}
+.ui-resizable-e {
+ cursor: e-resize;
+ width: 7px;
+ right: -5px;
+ top: 0;
+ height: 100%;
+}
+.ui-resizable-w {
+ cursor: w-resize;
+ width: 7px;
+ left: -5px;
+ top: 0;
+ height: 100%;
+}
+.ui-resizable-se {
+ cursor: se-resize;
+ width: 12px;
+ height: 12px;
+ right: 1px;
+ bottom: 1px;
+}
+.ui-resizable-sw {
+ cursor: sw-resize;
+ width: 9px;
+ height: 9px;
+ left: -5px;
+ bottom: -5px;
+}
+.ui-resizable-nw {
+ cursor: nw-resize;
+ width: 9px;
+ height: 9px;
+ left: -5px;
+ top: -5px;
+}
+.ui-resizable-ne {
+ cursor: ne-resize;
+ width: 9px;
+ height: 9px;
+ right: -5px;
+ top: -5px;
+}
+.ui-progressbar {
+ height: 2em;
+ text-align: left;
+ overflow: hidden;
+}
+.ui-progressbar .ui-progressbar-value {
+ margin: -1px;
+ height: 100%;
+}
+.ui-progressbar .ui-progressbar-overlay {
+ background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");
+ height: 100%;
+ filter: alpha(opacity=25); /* support: IE8 */
+ opacity: 0.25;
+}
+.ui-progressbar-indeterminate .ui-progressbar-value {
+ background-image: none;
+}
+.ui-selectable {
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.ui-selectable-helper {
+ position: absolute;
+ z-index: 100;
+ border: 1px dotted black;
+}
+.ui-selectmenu-menu {
+ padding: 0;
+ margin: 0;
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: none;
+}
+.ui-selectmenu-menu .ui-menu {
+ overflow: auto;
+ overflow-x: hidden;
+ padding-bottom: 1px;
+}
+.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup {
+ font-size: 1em;
+ font-weight: bold;
+ line-height: 1.5;
+ padding: 2px 0.4em;
+ margin: 0.5em 0 0 0;
+ height: auto;
+ border: 0;
+}
+.ui-selectmenu-open {
+ display: block;
+}
+.ui-selectmenu-text {
+ display: block;
+ margin-right: 20px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.ui-selectmenu-button.ui-button {
+ text-align: left;
+ white-space: nowrap;
+ width: 14em;
+}
+.ui-selectmenu-icon.ui-icon {
+ float: right;
+ margin-top: 0;
+}
+.ui-slider {
+ position: relative;
+ text-align: left;
+}
+.ui-slider .ui-slider-handle {
+ position: absolute;
+ z-index: 2;
+ width: 1.2em;
+ height: 1.2em;
+ cursor: default;
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.ui-slider .ui-slider-range {
+ position: absolute;
+ z-index: 1;
+ font-size: .7em;
+ display: block;
+ border: 0;
+ background-position: 0 0;
+}
+
+/* support: IE8 - See #6727 */
+.ui-slider.ui-state-disabled .ui-slider-handle,
+.ui-slider.ui-state-disabled .ui-slider-range {
+ filter: inherit;
+}
+
+.ui-slider-horizontal {
+ height: .8em;
+}
+.ui-slider-horizontal .ui-slider-handle {
+ top: -.3em;
+ margin-left: -.6em;
+}
+.ui-slider-horizontal .ui-slider-range {
+ top: 0;
+ height: 100%;
+}
+.ui-slider-horizontal .ui-slider-range-min {
+ left: 0;
+}
+.ui-slider-horizontal .ui-slider-range-max {
+ right: 0;
+}
+
+.ui-slider-vertical {
+ width: .8em;
+ height: 100px;
+}
+.ui-slider-vertical .ui-slider-handle {
+ left: -.3em;
+ margin-left: 0;
+ margin-bottom: -.6em;
+}
+.ui-slider-vertical .ui-slider-range {
+ left: 0;
+ width: 100%;
+}
+.ui-slider-vertical .ui-slider-range-min {
+ bottom: 0;
+}
+.ui-slider-vertical .ui-slider-range-max {
+ top: 0;
+}
+.ui-sortable-handle {
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.ui-spinner {
+ position: relative;
+ display: inline-block;
+ overflow: hidden;
+ padding: 0;
+ vertical-align: middle;
+}
+.ui-spinner-input {
+ border: none;
+ background: none;
+ color: inherit;
+ padding: .222em 0;
+ margin: .2em 0;
+ vertical-align: middle;
+ margin-left: .4em;
+ margin-right: 2em;
+}
+.ui-spinner-button {
+ width: 1.6em;
+ height: 50%;
+ font-size: .5em;
+ padding: 0;
+ margin: 0;
+ text-align: center;
+ position: absolute;
+ cursor: default;
+ display: block;
+ overflow: hidden;
+ right: 0;
+}
+/* more specificity required here to override default borders */
+.ui-spinner a.ui-spinner-button {
+ border-top-style: none;
+ border-bottom-style: none;
+ border-right-style: none;
+}
+.ui-spinner-up {
+ top: 0;
+}
+.ui-spinner-down {
+ bottom: 0;
+}
+.ui-tabs {
+ position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+ padding: .2em;
+}
+.ui-tabs .ui-tabs-nav {
+ margin: 0;
+ padding: .2em .2em 0;
+}
+.ui-tabs .ui-tabs-nav li {
+ list-style: none;
+ float: left;
+ position: relative;
+ top: 0;
+ margin: 1px .2em 0 0;
+ border-bottom-width: 0;
+ padding: 0;
+ white-space: nowrap;
+}
+.ui-tabs .ui-tabs-nav .ui-tabs-anchor {
+ float: left;
+ padding: .5em 1em;
+ text-decoration: none;
+}
+.ui-tabs .ui-tabs-nav li.ui-tabs-active {
+ margin-bottom: -1px;
+ padding-bottom: 1px;
+}
+.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,
+.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,
+.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor {
+ cursor: text;
+}
+.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor {
+ cursor: pointer;
+}
+.ui-tabs .ui-tabs-panel {
+ display: block;
+ border-width: 0;
+ padding: 1em 1.4em;
+ background: none;
+}
+.ui-tooltip {
+ padding: 8px;
+ position: absolute;
+ z-index: 9999;
+ max-width: 300px;
+}
+body .ui-tooltip {
+ border-width: 2px;
+}
+/* Component containers
+----------------------------------*/
+.ui-widget {
+
+ font-size: 1.1em;
+}
+.ui-widget .ui-widget {
+ font-size: 1em;
+}
+.ui-widget input,
+.ui-widget select,
+.ui-widget textarea,
+.ui-widget button {
+
+ font-size: 1em;
+}
+.ui-widget.ui-widget-content {
+ border: 1px solid #d3d3d3;
+}
+.ui-widget-content {
+ border: 1px solid #aaaaaa;
+ background: #ffffff;
+ color: #222222;
+}
+.ui-widget-content a {
+ color: #222222;
+}
+.ui-widget-header {
+ border: 1px solid #aaaaaa;
+ background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x;
+ color: #222222;
+ font-weight: bold;
+}
+.ui-widget-header a {
+ color: #222222;
+}
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default,
+.ui-widget-content .ui-state-default,
+.ui-widget-header .ui-state-default,
+.ui-button,
+
+/* We use html here because we need a greater specificity to make sure disabled
+works properly when clicked or hovered */
+html .ui-button.ui-state-disabled:hover,
+html .ui-button.ui-state-disabled:active {
+ border: 1px solid #d3d3d3;
+ background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x;
+ font-weight: normal;
+ color: #555555;
+}
+.ui-state-default a,
+.ui-state-default a:link,
+.ui-state-default a:visited,
+a.ui-button,
+a:link.ui-button,
+a:visited.ui-button,
+.ui-button {
+ color: #555555;
+ text-decoration: none;
+}
+.ui-state-hover,
+.ui-widget-content .ui-state-hover,
+.ui-widget-header .ui-state-hover,
+.ui-state-focus,
+.ui-widget-content .ui-state-focus,
+.ui-widget-header .ui-state-focus,
+.ui-button:hover,
+.ui-button:focus {
+ border: 1px solid #999999;
+ background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x;
+ font-weight: normal;
+ color: #212121;
+}
+.ui-state-hover a,
+.ui-state-hover a:hover,
+.ui-state-hover a:link,
+.ui-state-hover a:visited,
+.ui-state-focus a,
+.ui-state-focus a:hover,
+.ui-state-focus a:link,
+.ui-state-focus a:visited,
+a.ui-button:hover,
+a.ui-button:focus {
+ color: #212121;
+ text-decoration: none;
+}
+
+.ui-visual-focus {
+ box-shadow: 0 0 3px 1px rgb(94, 158, 214);
+}
+.ui-state-active,
+.ui-widget-content .ui-state-active,
+.ui-widget-header .ui-state-active,
+a.ui-button:active,
+.ui-button:active,
+.ui-button.ui-state-active:hover {
+ border: 1px solid #aaaaaa;
+ background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;
+ font-weight: normal;
+ color: #212121;
+}
+.ui-icon-background,
+.ui-state-active .ui-icon-background {
+ border: #aaaaaa;
+ background-color: #212121;
+}
+.ui-state-active a,
+.ui-state-active a:link,
+.ui-state-active a:visited {
+ color: #212121;
+ text-decoration: none;
+}
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight,
+.ui-widget-content .ui-state-highlight,
+.ui-widget-header .ui-state-highlight {
+ border: 1px solid #fcefa1;
+ background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x;
+ color: #363636;
+}
+.ui-state-checked {
+ border: 1px solid #fcefa1;
+ background: #fbf9ee;
+}
+.ui-state-highlight a,
+.ui-widget-content .ui-state-highlight a,
+.ui-widget-header .ui-state-highlight a {
+ color: #363636;
+}
+.ui-state-error,
+.ui-widget-content .ui-state-error,
+.ui-widget-header .ui-state-error {
+ border: 1px solid #cd0a0a;
+ background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;
+ color: #cd0a0a;
+}
+.ui-state-error a,
+.ui-widget-content .ui-state-error a,
+.ui-widget-header .ui-state-error a {
+ color: #cd0a0a;
+}
+.ui-state-error-text,
+.ui-widget-content .ui-state-error-text,
+.ui-widget-header .ui-state-error-text {
+ color: #cd0a0a;
+}
+.ui-priority-primary,
+.ui-widget-content .ui-priority-primary,
+.ui-widget-header .ui-priority-primary {
+ font-weight: bold;
+}
+.ui-priority-secondary,
+.ui-widget-content .ui-priority-secondary,
+.ui-widget-header .ui-priority-secondary {
+ opacity: .7;
+ filter:Alpha(Opacity=70); /* support: IE8 */
+ font-weight: normal;
+}
+.ui-state-disabled,
+.ui-widget-content .ui-state-disabled,
+.ui-widget-header .ui-state-disabled {
+ opacity: .35;
+ filter:Alpha(Opacity=35); /* support: IE8 */
+ background-image: none;
+}
+.ui-state-disabled .ui-icon {
+ filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
+}
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon {
+ width: 16px;
+ height: 16px;
+}
+.ui-icon,
+.ui-widget-content .ui-icon {
+ background-image: url("images/ui-icons_222222_256x240.png");
+}
+.ui-widget-header .ui-icon {
+ background-image: url("images/ui-icons_222222_256x240.png");
+}
+.ui-state-hover .ui-icon,
+.ui-state-focus .ui-icon,
+.ui-button:hover .ui-icon,
+.ui-button:focus .ui-icon {
+ background-image: url("images/ui-icons_454545_256x240.png");
+}
+.ui-state-active .ui-icon,
+.ui-button:active .ui-icon {
+ background-image: url("images/ui-icons_454545_256x240.png");
+}
+.ui-state-highlight .ui-icon,
+.ui-button .ui-state-highlight.ui-icon {
+ background-image: url("images/ui-icons_2e83ff_256x240.png");
+}
+.ui-state-error .ui-icon,
+.ui-state-error-text .ui-icon {
+ background-image: url("images/ui-icons_cd0a0a_256x240.png");
+}
+.ui-button .ui-icon {
+ background-image: url("images/ui-icons_888888_256x240.png");
+}
+
+/* positioning */
+.ui-icon-blank { background-position: 16px 16px; }
+.ui-icon-caret-1-n { background-position: 0 0; }
+.ui-icon-caret-1-ne { background-position: -16px 0; }
+.ui-icon-caret-1-e { background-position: -32px 0; }
+.ui-icon-caret-1-se { background-position: -48px 0; }
+.ui-icon-caret-1-s { background-position: -65px 0; }
+.ui-icon-caret-1-sw { background-position: -80px 0; }
+.ui-icon-caret-1-w { background-position: -96px 0; }
+.ui-icon-caret-1-nw { background-position: -112px 0; }
+.ui-icon-caret-2-n-s { background-position: -128px 0; }
+.ui-icon-caret-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -65px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -65px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 1px -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-on { background-position: -96px -144px; }
+.ui-icon-radio-off { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all,
+.ui-corner-top,
+.ui-corner-left,
+.ui-corner-tl {
+ border-top-left-radius: 4px;
+}
+.ui-corner-all,
+.ui-corner-top,
+.ui-corner-right,
+.ui-corner-tr {
+ border-top-right-radius: 4px;
+}
+.ui-corner-all,
+.ui-corner-bottom,
+.ui-corner-left,
+.ui-corner-bl {
+ border-bottom-left-radius: 4px;
+}
+.ui-corner-all,
+.ui-corner-bottom,
+.ui-corner-right,
+.ui-corner-br {
+ border-bottom-right-radius: 4px;
+}
+
+/* Overlays */
+.ui-widget-overlay {
+ background: #aaaaaa;
+ opacity: .3;
+ filter: Alpha(Opacity=30); /* support: IE8 */
+}
+.ui-widget-shadow {
+ -webkit-box-shadow: -8px -8px 8px #aaaaaa;
+ box-shadow: -8px -8px 8px #aaaaaa;
+}
diff --git a/platform/www/lib/scripts/jquery/jquery-ui.min.js b/platform/www/lib/scripts/jquery/jquery-ui.min.js
new file mode 100644
index 0000000..117cb35
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery-ui.min.js
@@ -0,0 +1,13 @@
+/*! jQuery UI - v1.12.1 - 2016-09-14
+* http://jqueryui.com
+* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js
+* Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){function e(t){for(var e=t.css("visibility");"inherit"===e;)t=t.parent(),e=t.css("visibility");return"hidden"!==e}function i(t){for(var e,i;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(i=parseInt(t.css("zIndex"),10),!isNaN(i)&&0!==i))return i;t=t.parent()}return 0}function s(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=n(t("<div id='"+this._mainDivId+"' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>"))}function n(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.on("mouseout",i,function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).on("mouseover",i,o)}function o(){t.datepicker._isDisabledDatepicker(m.inline?m.dpDiv.parent()[0]:m.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,i){t.extend(e,i);for(var s in i)null==i[s]&&(e[s]=i[s]);return e}function r(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.ui.version="1.12.1";var h=0,l=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},n=t[h][e],o=t[h][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:h,widgetName:e,widgetFullName:l}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var i,s,n=l.call(arguments,1),o=0,a=n.length;a>o;o++)for(i in n[o])s=n[o][i],n[o].hasOwnProperty(i)&&void 0!==s&&(e[i]=t.isPlainObject(s)?t.isPlainObject(e[i])?t.widget.extend({},e[i],s):t.widget.extend({},s):s);return e},t.widget.bridge=function(e,i){var s=i.prototype.widgetFullName||e;t.fn[e]=function(n){var o="string"==typeof n,a=l.call(arguments,1),r=this;return o?this.length||"instance"!==n?this.each(function(){var i,o=t.data(this,s);return"instance"===n?(r=o,!1):o?t.isFunction(o[n])&&"_"!==n.charAt(0)?(i=o[n].apply(o,a),i!==o&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+n+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+n+"'")}):r=void 0:(a.length&&(n=t.widget.extend.apply(null,[n].concat(a))),this.each(function(){var e=t.data(this,s);e?(e.option(n||{}),e._init&&e._init()):t.data(this,s,new i(n,this))})),r}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{classes:{},disabled:!1,create:null},_createWidget:function(e,i){i=t(i||this.defaultElement||this)[0],this.element=t(i),this.uuid=h++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},i!==this&&(t.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===i&&this.destroy()}}),this.document=t(i.style?i.ownerDocument:i.document||i),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+o.eventNamespace,c=h[2];c?n.on(l,c,r):i.on(l,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.width<e.element[0].scrollWidth,o="scroll"===s||"auto"===s&&e.height<e.element[0].scrollHeight;return{width:o?t.position.scrollbarWidth():0,height:n?t.position.scrollbarWidth():0}},getWithinInfo:function(e){var i=t(e||window),s=t.isWindow(i[0]),n=!!i[0]&&9===i[0].nodeType,o=!s&&!n;return{element:i,isWindow:s,isDocument:n,offset:o?t(e).offset():{left:0,top:0},scrollLeft:i.scrollLeft(),scrollTop:i.scrollTop(),width:i.outerWidth(),height:i.outerHeight()}}},t.fn.position=function(n){if(!n||!n.of)return d.apply(this,arguments);n=t.extend({},n);var u,p,f,g,m,_,v=t(n.of),b=t.position.getWithinInfo(n.within),y=t.position.getScrollInfo(b),w=(n.collision||"flip").split(" "),k={};return _=s(v),v[0].preventDefault&&(n.at="left top"),p=_.width,f=_.height,g=_.offset,m=t.extend({},g),t.each(["my","at"],function(){var t,e,i=(n[this]||"").split(" ");1===i.length&&(i=r.test(i[0])?i.concat(["center"]):h.test(i[0])?["center"].concat(i):["center","center"]),i[0]=r.test(i[0])?i[0]:"center",i[1]=h.test(i[1])?i[1]:"center",t=l.exec(i[0]),e=l.exec(i[1]),k[this]=[t?t[0]:0,e?e[0]:0],n[this]=[c.exec(i[0])[0],c.exec(i[1])[0]]}),1===w.length&&(w[1]=w[0]),"right"===n.at[0]?m.left+=p:"center"===n.at[0]&&(m.left+=p/2),"bottom"===n.at[1]?m.top+=f:"center"===n.at[1]&&(m.top+=f/2),u=e(k.at,p,f),m.left+=u[0],m.top+=u[1],this.each(function(){var s,r,h=t(this),l=h.outerWidth(),c=h.outerHeight(),d=i(this,"marginLeft"),_=i(this,"marginTop"),x=l+d+i(this,"marginRight")+y.width,C=c+_+i(this,"marginBottom")+y.height,D=t.extend({},m),I=e(k.my,h.outerWidth(),h.outerHeight());"right"===n.my[0]?D.left-=l:"center"===n.my[0]&&(D.left-=l/2),"bottom"===n.my[1]?D.top-=c:"center"===n.my[1]&&(D.top-=c/2),D.left+=I[0],D.top+=I[1],s={marginLeft:d,marginTop:_},t.each(["left","top"],function(e,i){t.ui.position[w[e]]&&t.ui.position[w[e]][i](D,{targetWidth:p,targetHeight:f,elemWidth:l,elemHeight:c,collisionPosition:s,collisionWidth:x,collisionHeight:C,offset:[u[0]+I[0],u[1]+I[1]],my:n.my,at:n.at,within:b,elem:h})}),n.using&&(r=function(t){var e=g.left-D.left,i=e+p-l,s=g.top-D.top,r=s+f-c,u={target:{element:v,left:g.left,top:g.top,width:p,height:f},element:{element:h,left:D.left,top:D.top,width:l,height:c},horizontal:0>i?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};l>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),h.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-a-n;e.collisionWidth>a?h>0&&0>=l?(i=t.left+h+e.collisionWidth-a-n,t.left+=h-i):t.left=l>0&&0>=h?n:h>l?n+a-e.collisionWidth:n:h>0?t.left+=h:l>0?t.left-=l:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,h=n-r,l=r+e.collisionHeight-a-n;e.collisionHeight>a?h>0&&0>=l?(i=t.top+h+e.collisionHeight-a-n,t.top+=h-i):t.top=l>0&&0>=h?n:h>l?n+a-e.collisionHeight:n:h>0?t.top+=h:l>0?t.top-=l:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,u=l+e.collisionWidth-r-h,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-h,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,u=l+e.collisionHeight-r-h,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-h,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}});var c="ui-effects-",u="ui-effects-style",d="ui-effects-animated",p=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,o){var a,r=o.re.exec(i),h=r&&o.parse(r),l=o.space||"rgba";return h?(a=s[l](h),s[c[l].cache]=a[c[l].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("<p>")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,a,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,h],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),o=c[n],a=0===this.alpha()?l("transparent"):this,r=a[o.cache]||o.to(a._rgba),h=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],l=s[o],c=u[n.type]||{};null!==l&&(null===a?h[o]=l:(c.mod&&(l-a>c.mod/2?a+=c.mod:a-l>c.mod/2&&(a-=c.mod)),h[o]=i((l-a)*e+a,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),h=Math.min(s,n,o),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-o)/l+360:n===r?60*(o-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[a]&&(this[a]=h(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[a]=d,n):l(d)},f(o,function(e,i){l.fn[e]||(l.fn[e]=function(n){var o,a=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=l(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(p),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,o,a={};for(s in i)o=i[s],e[s]!==o&&(n[s]||(t.fx.step[s]||!isNaN(parseFloat(o)))&&(a[s]=o));return a}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(p.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var h=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",l=h.children?a.find("*").addBack():a;l=l.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function e(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function i(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),n=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,o=n.exec(t)||["",0,i,s,0];return{top:parseFloat(o[1])||0,right:"auto"===o[2]?i:parseFloat(o[2]),bottom:"auto"===o[3]?s:parseFloat(o[3]),left:parseFloat(o[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(d)||e(i)}}(t.expr.filters.animated)),t.uiBackCompat!==!1&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;s>i;i++)null!==e[i]&&t.data(c+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,n=e.length;n>s;s++)null!==e[s]&&(i=t.data(c+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,n="vertical"!==i?(e||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data(u,t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data(u)||"",t.removeData(u)},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),n=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),"float":e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data(c+"placeholder",i)),e.css({position:s,left:n.left,top:n.top}),i},removePlaceholder:function(t){var e=c+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function i(e){function i(){r.removeData(d),t.effects.cleanUp(r),"hide"===s.mode&&r.hide(),a()}function a(){t.isFunction(h)&&h.call(r[0]),t.isFunction(e)&&e()}var r=t(this);s.mode=c.shift(),t.uiBackCompat===!1||o?"none"===s.mode?(r[l](),a()):n.call(r[0],s,i):(r.is(":hidden")?"hide"===l:"show"===l)?(r[l](),a()):n.call(r[0],s,a)}var s=e.apply(this,arguments),n=t.effects.effect[s.effect],o=n.mode,a=s.queue,r=a||"fx",h=s.complete,l=s.mode,c=[],u=function(e){var i=t(this),s=t.effects.mode(i,l)||o;i.data(d,!0),c.push(s),o&&("show"===s||s===o&&"hide"===s)&&i.show(),o&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!n?l?this[l](s.duration,h):this.each(function(){h&&h.call(this)}):a===!1?this.each(u).each(i):this.queue(r,u).queue(r,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="show",this.effect.call(this,n)
+}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("<div class='ui-effects-transfer'></div>").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var f=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},h=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),h&&h.css(t.effects.clipToBox(r)),r.clip=a),h&&h.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,h="hide"===r,l="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(l||h?1:0),f=e.duration/p,g=e.easing,m="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u||(u=a["top"===m?"outerHeight":"outerWidth"]()/3),l&&(n={opacity:1},n[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),h&&(u/=Math.pow(2,d-1)),n={},n[m]=o;d>v;v++)s={},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g).animate(n,f,g),u=h?2*u:u/2;h&&(s={opacity:0},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("clip","hide",function(e,i){var s,n={},o=t(this),a=e.direction||"vertical",r="both"===a,h=r||"horizontal"===a,l=r||"vertical"===a;s=o.cssClip(),n.clip={top:l?(s.bottom-s.top)/2:s.top,right:h?(s.right-s.left)/2:s.right,bottom:l?(s.bottom-s.top)/2:s.bottom,left:h?(s.right-s.left)/2:s.left},t.effects.createPlaceholder(o),"show"===e.mode&&(o.cssClip(n.clip),n.clip=s),o.animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",h="up"===r||"down"===r?"top":"left",l="up"===r||"left"===r?"-=":"+=",c="+="===l?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0)/2,u[h]=l+s,a&&(n.css(u),u[h]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("explode","hide",function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=e.mode,g="show"===f,m=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*_,l=a-(d-1)/2,p.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacity:g?1:0},e.duration||500,e.easing,s)}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=e.size||15,h=/([0-9]+)%/.exec(r),l=!!e.horizFirst,c=l?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},g={clip:t.extend({},p)},m=[p[c[0]],p[c[1]]],_=s.queue().length;h&&(r=parseInt(h[1],10)/100*m[a?0:1]),f.clip[c[0]]=r,g.clip[c[0]]=r,g.clip[c[1]]=0,o&&(s.cssClip(g.clip),d&&d.css(t.effects.clipToBox(g)),g.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(g),u,e.easing),i()}).animate(f,u,e.easing).animate(g,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,n,o,a=t(this),r=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,u="effect"!==c,d=e.scale||"both",p=e.origin||["middle","center"],f=a.css("position"),g=a.position(),m=t.effects.scaledDimensions(a),_=e.from||m,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===c&&(o=_,_=v,v=o),n={from:{y:_.height/m.height,x:_.width/m.width},to:{y:v.height/m.height,x:v.width/m.width}},("box"===d||"both"===d)&&(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,h,n.from.y,_),v=t.effects.setTransition(a,h,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,l,n.from.x,_),v=t.effects.setTransition(a,l,n.to.x,v))),("content"===d||"both"===d)&&n.from.y!==n.to.y&&(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),p&&(s=t.effects.getBaseline(p,m),_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left,v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left),a.css(_),("content"===d||"both"===d)&&(h=h.concat(["marginTop","marginBottom"]).concat(r),l=l.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),o={height:s.height*n.from.y,width:s.width*n.from.x,outerHeight:s.outerHeight*n.from.y,outerWidth:s.outerWidth*n.from.x},a={height:s.height*n.to.y,width:s.width*n.to.x,outerHeight:s.height*n.to.y,outerWidth:s.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,h,n.from.y,o),a=t.effects.setTransition(i,h,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,l,n.from.x,o),a=t.effects.setTransition(i,l,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),n=e.mode,o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==n?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,o,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=o||a,h=2*(e.times||5)+(r?1:0),l=e.duration/h,c=0,u=1,d=s.queue().length;for((o||!s.is(":visible"))&&(s.css("opacity",0).show(),c=1);h>u;u++)s.animate({opacity:c},l,e.easing),c=1-c;s.animate({opacity:c},l,e.easing),s.queue(i),t.effects.unshift(s,d,h+1)}),t.effects.define("shake",function(e,i){var s=1,n=t(this),o=e.direction||"left",a=e.distance||20,r=e.times||3,h=2*r+1,l=Math.round(e.duration/h),c="up"===o||"down"===o?"top":"left",u="up"===o||"left"===o,d={},p={},f={},g=n.queue().length;for(t.effects.createPlaceholder(n),d[c]=(u?"-=":"+=")+a,p[c]=(u?"+=":"-=")+2*a,f[c]=(u?"-=":"+=")+2*a,n.animate(d,l,e.easing);r>s;s++)n.animate(p,l,e.easing).animate(f,l,e.easing);n.animate(p,l,e.easing).animate(d,l/2,e.easing).queue(i),t.effects.unshift(n,g,h+1)}),t.effects.define("slide","show",function(e,i){var s,n,o=t(this),a={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},r=e.mode,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,u=e.distance||o["top"===l?"outerHeight":"outerWidth"](!0),d={};t.effects.createPlaceholder(o),s=o.cssClip(),n=o.position()[l],d[l]=(c?-1:1)*u+n,d.clip=o.cssClip(),d.clip[a[h][1]]=d.clip[a[h][0]],"show"===r&&(o.cssClip(d.clip),o.css(l,d[l]),d.clip=s,d[l]=n),o.animate(d,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});var f;t.uiBackCompat!==!1&&(f=t.effects.define("transfer",function(e,i){t(this).transfer(e,i)})),t.ui.focusable=function(i,s){var n,o,a,r,h,l=i.nodeName.toLowerCase();return"area"===l?(n=i.parentNode,o=n.name,i.href&&o&&"map"===n.nodeName.toLowerCase()?(a=t("img[usemap='#"+o+"']"),a.length>0&&a.is(":visible")):!1):(/^(input|select|textarea|button|object)$/.test(l)?(r=!i.disabled,r&&(h=t(i).closest("fieldset")[0],h&&(r=!h.disabled))):r="a"===l?i.href||s:s,r&&t(i).is(":visible")&&e(t(i)))},t.extend(t.expr[":"],{focusable:function(e){return t.ui.focusable(e,null!=t.attr(e,"tabindex"))}}),t.ui.focusable,t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},"1.7"===t.fn.jquery.substring(0,3)&&(t.each(["Width","Height"],function(e,i){function s(e,i,s,o){return t.each(n,function(){i-=parseFloat(t.css(e,"padding"+this))||0,s&&(i-=parseFloat(t.css(e,"border"+this+"Width"))||0),o&&(i-=parseFloat(t.css(e,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],o=i.toLowerCase(),a={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+i]=function(e){return void 0===e?a["inner"+i].call(this):this.each(function(){t(this).css(o,s(this,e)+"px")})},t.fn["outer"+i]=function(e,n){return"number"!=typeof e?a["outer"+i].call(this,e):this.each(function(){t(this).css(o,s(this,e,!0,n)+"px")})}}),t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.ui.escapeSelector=function(){var t=/([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g;return function(e){return e.replace(t,"\\$1")}}(),t.fn.labels=function(){var e,i,s,n,o;return this[0].labels&&this[0].labels.length?this.pushStack(this[0].labels):(n=this.eq(0).parents("label"),s=this.attr("id"),s&&(e=this.eq(0).parents().last(),o=e.add(e.length?e.siblings():this.siblings()),i="label[for='"+t.ui.escapeSelector(s)+"']",n=n.add(o.find(i).addBack(i))),this.pushStack(n))},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.extend(t.expr[":"],{tabbable:function(e){var i=t.attr(e,"tabindex"),s=null!=i;return(!s||i>=0)&&t.ui.focusable(e,s)}}),t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.widget("ui.accordion",{version:"1.12.1",options:{active:0,animate:{},classes:{"ui-accordion-header":"ui-corner-top","ui-accordion-header-collapsed":"ui-corner-all","ui-accordion-content":"ui-corner-bottom"},collapsible:!1,event:"click",header:"> li > :first-child, > :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),0>e.active&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var e,i,s=this.options.icons;s&&(e=t("<span>"),this._addClass(e,"ui-accordion-header-icon","ui-icon "+s.header),e.prependTo(this.headers),i=this.active.children(".ui-accordion-header-icon"),this._removeClass(i,s.header)._addClass(i,null,s.activeHeader)._addClass(this.headers,"ui-accordion-icons"))},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),void 0)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var i=t.ui.keyCode,s=this.headers.length,n=this.headers.index(e.target),o=!1;switch(e.keyCode){case i.RIGHT:case i.DOWN:o=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:o=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(e);break;case i.HOME:o=this.headers[0];break;case i.END:o=this.headers[s-1]}o&&(t(e.target).attr("tabIndex",-1),t(o).attr("tabIndex",0),t(o).trigger("focus"),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().trigger("focus")},refresh:function(){var e=this.options;this._processPanels(),e.active===!1&&e.collapsible===!0||!this.headers.length?(e.active=!1,this.active=t()):e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var e=t(this),i=e.uniqueId().attr("id"),s=e.next(),n=s.uniqueId().attr("id");e.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(e=n.height(),this.element.siblings(":visible").each(function(){var i=t(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(e-=i.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===s&&(e=0,this.headers.next().each(function(){var i=t(this).is(":visible");i||t(this).show(),e=Math.max(e,t(this).css("height","").height()),i||t(this).hide()}).height(e))},_activate:function(e){var i=this._findActive(e)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var i={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var i,s,n=this.options,o=this.active,a=t(e.currentTarget),r=a[0]===o[0],h=r&&n.collapsible,l=h?t():a.next(),c=o.next(),u={oldHeader:o,oldPanel:c,newHeader:h?t():a,newPanel:l};e.preventDefault(),r&&!n.collapsible||this._trigger("beforeActivate",e,u)===!1||(n.active=h?!1:this.headers.index(a),this.active=r?t():a,this._toggle(u),this._removeClass(o,"ui-accordion-header-active","ui-state-active"),n.icons&&(i=o.children(".ui-accordion-header-icon"),this._removeClass(i,null,n.icons.activeHeader)._addClass(i,null,n.icons.header)),r||(this._removeClass(a,"ui-accordion-header-collapsed")._addClass(a,"ui-accordion-header-active","ui-state-active"),n.icons&&(s=a.children(".ui-accordion-header-icon"),this._removeClass(s,null,n.icons.header)._addClass(s,null,n.icons.activeHeader)),this._addClass(a.next(),"ui-accordion-content-active")))},_toggle:function(e){var i=e.newPanel,s=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,e):(s.hide(),i.show(),this._toggleComplete(e)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(t(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,e,i){var s,n,o,a=this,r=0,h=t.css("box-sizing"),l=t.length&&(!e.length||t.index()<e.index()),c=this.options.animate||{},u=l&&c.down||c,d=function(){a._toggleComplete(i)};return"number"==typeof u&&(o=u),"string"==typeof u&&(n=u),n=n||u.easing||c.easing,o=o||u.duration||c.duration,e.length?t.length?(s=t.show().outerHeight(),e.animate(this.hideProps,{duration:o,easing:n,step:function(t,e){e.now=Math.round(t)}}),t.hide().animate(this.showProps,{duration:o,easing:n,complete:d,step:function(t,i){i.now=Math.round(t),"height"!==i.prop?"content-box"===h&&(r+=i.now):"content"!==a.options.heightStyle&&(i.now=Math.round(s-e.outerHeight()-r),r=0)}}),void 0):e.animate(this.hideProps,o,n,d):t.animate(this.showProps,o,n,d)},_toggleComplete:function(t){var e=t.oldPanel,i=e.prev();this._removeClass(e,"ui-accordion-content-active"),this._removeClass(i,"ui-accordion-header-active")._addClass(i,"ui-accordion-header-collapsed"),e.length&&(e.parent()[0].className=e.parent()[0].className),this._trigger("activate",null,t)}}),t.ui.safeActiveElement=function(t){var e;try{e=t.activeElement}catch(i){e=t.body}return e||(e=t.body),e.nodeName||(e=t.body),e},t.widget("ui.menu",{version:"1.12.1",defaultElement:"<ul>",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,h=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=h.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("<span>").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=h.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()<this.element.prop("scrollHeight")},select:function(e){this.active=this.active||t(e.target).closest(".ui-menu-item");var i={item:this.active};this.active.has(".ui-menu").length||this.collapseAll(e,!0),this._trigger("select",e,i)},_filterMenuItems:function(e){var i=e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&"),s=RegExp("^"+i,"i");return this.activeMenu.find(this.options.items).filter(".ui-menu-item").filter(function(){return s.test(t.trim(t(this).children(".ui-menu-item-wrapper").text()))})}}),t.widget("ui.autocomplete",{version:"1.12.1",defaultElement:"<input>",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n;
+this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("<ul>").appendTo(this._appendTo()).menu({role:null}).hide().menu("instance"),this._addClass(this.menu.element,"ui-autocomplete","ui-front"),this._on(this.menu.element,{mousedown:function(e){e.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,this.element[0]!==t.ui.safeActiveElement(this.document[0])&&this.element.trigger("focus")})},menufocus:function(e,i){var s,n;return this.isNewMenu&&(this.isNewMenu=!1,e.originalEvent&&/^mouse/.test(e.originalEvent.type))?(this.menu.blur(),this.document.one("mousemove",function(){t(e.target).trigger(e.originalEvent)}),void 0):(n=i.item.data("ui-autocomplete-item"),!1!==this._trigger("focus",e,{item:n})&&e.originalEvent&&/^key/.test(e.originalEvent.type)&&this._value(n.value),s=i.item.attr("aria-label")||n.value,s&&t.trim(s).length&&(this.liveRegion.children().hide(),t("<div>").text(s).appendTo(this.liveRegion)),void 0)},menuselect:function(e,i){var s=i.item.data("ui-autocomplete-item"),n=this.previous;this.element[0]!==t.ui.safeActiveElement(this.document[0])&&(this.element.trigger("focus"),this.previous=n,this._delay(function(){this.previous=n,this.selectedItem=s})),!1!==this._trigger("select",e,{item:s})&&this._value(s.value),this.term=this._value(),this.close(e),this.selectedItem=s}}),this.liveRegion=t("<div>",{role:"status","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(t,e){this._super(t,e),"source"===t&&this._initSource(),"appendTo"===t&&this.menu.element.appendTo(this._appendTo()),"disabled"===t&&e&&this.xhr&&this.xhr.abort()},_isEventTargetInWidget:function(e){var i=this.menu.element[0];return e.target===this.element[0]||e.target===i||t.contains(i,e.target)},_closeOnClickOutside:function(t){this._isEventTargetInWidget(t)||this.close()},_appendTo:function(){var e=this.options.appendTo;return e&&(e=e.jquery||e.nodeType?t(e):this.document.find(e).eq(0)),e&&e[0]||(e=this.element.closest(".ui-front, dialog")),e.length||(e=this.document[0].body),e},_initSource:function(){var e,i,s=this;t.isArray(this.options.source)?(e=this.options.source,this.source=function(i,s){s(t.ui.autocomplete.filter(e,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(e,n){s.xhr&&s.xhr.abort(),s.xhr=t.ajax({url:i,data:e,dataType:"json",success:function(t){n(t)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(t){clearTimeout(this.searching),this.searching=this._delay(function(){var e=this.term===this._value(),i=this.menu.element.is(":visible"),s=t.altKey||t.ctrlKey||t.metaKey||t.shiftKey;(!e||e&&!i&&!s)&&(this.selectedItem=null,this.search(null,t))},this.options.delay)},search:function(t,e){return t=null!=t?t:this._value(),this.term=this._value(),t.length<this.options.minLength?this.close(e):this._trigger("search",e)!==!1?this._search(t):void 0},_search:function(t){this.pending++,this._addClass("ui-autocomplete-loading"),this.cancelSearch=!1,this.source({term:t},this._response())},_response:function(){var e=++this.requestIndex;return t.proxy(function(t){e===this.requestIndex&&this.__response(t),this.pending--,this.pending||this._removeClass("ui-autocomplete-loading")},this)},__response:function(t){t&&(t=this._normalize(t)),this._trigger("response",null,{content:t}),!this.options.disabled&&t&&t.length&&!this.cancelSearch?(this._suggest(t),this._trigger("open")):this._close()},close:function(t){this.cancelSearch=!0,this._close(t)},_close:function(t){this._off(this.document,"mousedown"),this.menu.element.is(":visible")&&(this.menu.element.hide(),this.menu.blur(),this.isNewMenu=!0,this._trigger("close",t))},_change:function(t){this.previous!==this._value()&&this._trigger("change",t,{item:this.selectedItem})},_normalize:function(e){return e.length&&e[0].label&&e[0].value?e:t.map(e,function(e){return"string"==typeof e?{label:e,value:e}:t.extend({},e,{label:e.label||e.value,value:e.value||e.label})})},_suggest:function(e){var i=this.menu.element.empty();this._renderMenu(i,e),this.isNewMenu=!0,this.menu.refresh(),i.show(),this._resizeMenu(),i.position(t.extend({of:this.element},this.options.position)),this.options.autoFocus&&this.menu.next(),this._on(this.document,{mousedown:"_closeOnClickOutside"})},_resizeMenu:function(){var t=this.menu.element;t.outerWidth(Math.max(t.width("").outerWidth()+1,this.element.outerWidth()))},_renderMenu:function(e,i){var s=this;t.each(i,function(t,i){s._renderItemData(e,i)})},_renderItemData:function(t,e){return this._renderItem(t,e).data("ui-autocomplete-item",e)},_renderItem:function(e,i){return t("<li>").append(t("<div>").text(i.label)).appendTo(e)},_move:function(t,e){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(t)||this.menu.isLastItem()&&/^next/.test(t)?(this.isMultiLine||this._value(this.term),this.menu.blur(),void 0):(this.menu[t](e),void 0):(this.search(null,e),void 0)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(t,e){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(t,e),e.preventDefault())},_isContentEditable:function(t){if(!t.length)return!1;var e=t.prop("contentEditable");return"inherit"===e?this._isContentEditable(t.parent()):"true"===e}}),t.extend(t.ui.autocomplete,{escapeRegex:function(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(e,i){var s=RegExp(t.ui.autocomplete.escapeRegex(i),"i");return t.grep(e,function(t){return s.test(t.label||t.value||t)})}}),t.widget("ui.autocomplete",t.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(t){return t+(t>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var i;this._superApply(arguments),this.options.disabled||this.cancelSearch||(i=e&&e.length?this.options.messages.results(e.length):this.options.messages.noResults,this.liveRegion.children().hide(),t("<div>").text(i).appendTo(this.liveRegion))}}),t.ui.autocomplete;var g=/ui-corner-([a-z]){2,6}/g;t.widget("ui.controlgroup",{version:"1.12.1",defaultElement:"<div>",options:{direction:"horizontal",disabled:null,onlyVisible:!0,items:{button:"input[type=button], input[type=submit], input[type=reset], button, a",controlgroupLabel:".ui-controlgroup-label",checkboxradio:"input[type='checkbox'], input[type='radio']",selectmenu:"select",spinner:".ui-spinner-input"}},_create:function(){this._enhance()},_enhance:function(){this.element.attr("role","toolbar"),this.refresh()},_destroy:function(){this._callChildMethod("destroy"),this.childWidgets.removeData("ui-controlgroup-data"),this.element.removeAttr("role"),this.options.items.controlgroupLabel&&this.element.find(this.options.items.controlgroupLabel).find(".ui-controlgroup-label-contents").contents().unwrap()},_initWidgets:function(){var e=this,i=[];t.each(this.options.items,function(s,n){var o,a={};return n?"controlgroupLabel"===s?(o=e.element.find(n),o.each(function(){var e=t(this);e.children(".ui-controlgroup-label-contents").length||e.contents().wrapAll("<span class='ui-controlgroup-label-contents'></span>")}),e._addClass(o,null,"ui-widget ui-widget-content ui-state-default"),i=i.concat(o.get()),void 0):(t.fn[s]&&(a=e["_"+s+"Options"]?e["_"+s+"Options"]("middle"):{classes:{}},e.element.find(n).each(function(){var n=t(this),o=n[s]("instance"),r=t.widget.extend({},a);if("button"!==s||!n.parent(".ui-spinner").length){o||(o=n[s]()[s]("instance")),o&&(r.classes=e._resolveClassesValues(r.classes,o)),n[s](r);var h=n[s]("widget");t.data(h[0],"ui-controlgroup-data",o?o:n[s]("instance")),i.push(h[0])}})),void 0):void 0}),this.childWidgets=t(t.unique(i)),this._addClass(this.childWidgets,"ui-controlgroup-item")},_callChildMethod:function(e){this.childWidgets.each(function(){var i=t(this),s=i.data("ui-controlgroup-data");s&&s[e]&&s[e]()})},_updateCornerClass:function(t,e){var i="ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-corner-all",s=this._buildSimpleOptions(e,"label").classes.label;this._removeClass(t,null,i),this._addClass(t,null,s)},_buildSimpleOptions:function(t,e){var i="vertical"===this.options.direction,s={classes:{}};return s.classes[e]={middle:"",first:"ui-corner-"+(i?"top":"left"),last:"ui-corner-"+(i?"bottom":"right"),only:"ui-corner-all"}[t],s},_spinnerOptions:function(t){var e=this._buildSimpleOptions(t,"ui-spinner");return e.classes["ui-spinner-up"]="",e.classes["ui-spinner-down"]="",e},_buttonOptions:function(t){return this._buildSimpleOptions(t,"ui-button")},_checkboxradioOptions:function(t){return this._buildSimpleOptions(t,"ui-checkboxradio-label")},_selectmenuOptions:function(t){var e="vertical"===this.options.direction;return{width:e?"auto":!1,classes:{middle:{"ui-selectmenu-button-open":"","ui-selectmenu-button-closed":""},first:{"ui-selectmenu-button-open":"ui-corner-"+(e?"top":"tl"),"ui-selectmenu-button-closed":"ui-corner-"+(e?"top":"left")},last:{"ui-selectmenu-button-open":e?"":"ui-corner-tr","ui-selectmenu-button-closed":"ui-corner-"+(e?"bottom":"right")},only:{"ui-selectmenu-button-open":"ui-corner-top","ui-selectmenu-button-closed":"ui-corner-all"}}[t]}},_resolveClassesValues:function(e,i){var s={};return t.each(e,function(n){var o=i.options.classes[n]||"";o=t.trim(o.replace(g,"")),s[n]=(o+" "+e[n]).replace(/\s+/g," ")}),s},_setOption:function(t,e){return"direction"===t&&this._removeClass("ui-controlgroup-"+this.options.direction),this._super(t,e),"disabled"===t?(this._callChildMethod(e?"disable":"enable"),void 0):(this.refresh(),void 0)},refresh:function(){var e,i=this;this._addClass("ui-controlgroup ui-controlgroup-"+this.options.direction),"horizontal"===this.options.direction&&this._addClass(null,"ui-helper-clearfix"),this._initWidgets(),e=this.childWidgets,this.options.onlyVisible&&(e=e.filter(":visible")),e.length&&(t.each(["first","last"],function(t,s){var n=e[s]().data("ui-controlgroup-data");if(n&&i["_"+n.widgetName+"Options"]){var o=i["_"+n.widgetName+"Options"](1===e.length?"only":s);o.classes=i._resolveClassesValues(o.classes,n),n.element[n.widgetName](o)}else i._updateCornerClass(e[s](),s)}),this._callChildMethod("refresh"))}}),t.widget("ui.checkboxradio",[t.ui.formResetMixin,{version:"1.12.1",options:{disabled:null,label:null,icon:!0,classes:{"ui-checkboxradio-label":"ui-corner-all","ui-checkboxradio-icon":"ui-corner-all"}},_getCreateOptions:function(){var e,i,s=this,n=this._super()||{};return this._readType(),i=this.element.labels(),this.label=t(i[i.length-1]),this.label.length||t.error("No label found for checkboxradio widget"),this.originalLabel="",this.label.contents().not(this.element[0]).each(function(){s.originalLabel+=3===this.nodeType?t(this).text():this.outerHTML}),this.originalLabel&&(n.label=this.originalLabel),e=this.element[0].disabled,null!=e&&(n.disabled=e),n},_create:function(){var t=this.element[0].checked;this._bindFormResetHandler(),null==this.options.disabled&&(this.options.disabled=this.element[0].disabled),this._setOption("disabled",this.options.disabled),this._addClass("ui-checkboxradio","ui-helper-hidden-accessible"),this._addClass(this.label,"ui-checkboxradio-label","ui-button ui-widget"),"radio"===this.type&&this._addClass(this.label,"ui-checkboxradio-radio-label"),this.options.label&&this.options.label!==this.originalLabel?this._updateLabel():this.originalLabel&&(this.options.label=this.originalLabel),this._enhance(),t&&(this._addClass(this.label,"ui-checkboxradio-checked","ui-state-active"),this.icon&&this._addClass(this.icon,null,"ui-state-hover")),this._on({change:"_toggleClasses",focus:function(){this._addClass(this.label,null,"ui-state-focus ui-visual-focus")},blur:function(){this._removeClass(this.label,null,"ui-state-focus ui-visual-focus")}})},_readType:function(){var e=this.element[0].nodeName.toLowerCase();this.type=this.element[0].type,"input"===e&&/radio|checkbox/.test(this.type)||t.error("Can't create checkboxradio on element.nodeName="+e+" and element.type="+this.type)},_enhance:function(){this._updateIcon(this.element[0].checked)},widget:function(){return this.label},_getRadioGroup:function(){var e,i=this.element[0].name,s="input[name='"+t.ui.escapeSelector(i)+"']";return i?(e=this.form.length?t(this.form[0].elements).filter(s):t(s).filter(function(){return 0===t(this).form().length}),e.not(this.element)):t([])},_toggleClasses:function(){var e=this.element[0].checked;this._toggleClass(this.label,"ui-checkboxradio-checked","ui-state-active",e),this.options.icon&&"checkbox"===this.type&&this._toggleClass(this.icon,null,"ui-icon-check ui-state-checked",e)._toggleClass(this.icon,null,"ui-icon-blank",!e),"radio"===this.type&&this._getRadioGroup().each(function(){var e=t(this).checkboxradio("instance");e&&e._removeClass(e.label,"ui-checkboxradio-checked","ui-state-active")})},_destroy:function(){this._unbindFormResetHandler(),this.icon&&(this.icon.remove(),this.iconSpace.remove())},_setOption:function(t,e){return"label"!==t||e?(this._super(t,e),"disabled"===t?(this._toggleClass(this.label,null,"ui-state-disabled",e),this.element[0].disabled=e,void 0):(this.refresh(),void 0)):void 0},_updateIcon:function(e){var i="ui-icon ui-icon-background ";this.options.icon?(this.icon||(this.icon=t("<span>"),this.iconSpace=t("<span> </span>"),this._addClass(this.iconSpace,"ui-checkboxradio-icon-space")),"checkbox"===this.type?(i+=e?"ui-icon-check ui-state-checked":"ui-icon-blank",this._removeClass(this.icon,null,e?"ui-icon-blank":"ui-icon-check")):i+="ui-icon-blank",this._addClass(this.icon,"ui-checkboxradio-icon",i),e||this._removeClass(this.icon,null,"ui-icon-check ui-state-checked"),this.icon.prependTo(this.label).after(this.iconSpace)):void 0!==this.icon&&(this.icon.remove(),this.iconSpace.remove(),delete this.icon)},_updateLabel:function(){var t=this.label.contents().not(this.element[0]);this.icon&&(t=t.not(this.icon[0])),this.iconSpace&&(t=t.not(this.iconSpace[0])),t.remove(),this.label.append(this.options.label)},refresh:function(){var t=this.element[0].checked,e=this.element[0].disabled;this._updateIcon(t),this._toggleClass(this.label,"ui-checkboxradio-checked","ui-state-active",t),null!==this.options.label&&this._updateLabel(),e!==this.options.disabled&&this._setOptions({disabled:e})}}]),t.ui.checkboxradio,t.widget("ui.button",{version:"1.12.1",defaultElement:"<button>",options:{classes:{"ui-button":"ui-corner-all"},disabled:null,icon:null,iconPosition:"beginning",label:null,showLabel:!0},_getCreateOptions:function(){var t,e=this._super()||{};return this.isInput=this.element.is("input"),t=this.element[0].disabled,null!=t&&(e.disabled=t),this.originalLabel=this.isInput?this.element.val():this.element.html(),this.originalLabel&&(e.label=this.originalLabel),e},_create:function(){!this.option.showLabel&!this.options.icon&&(this.options.showLabel=!0),null==this.options.disabled&&(this.options.disabled=this.element[0].disabled||!1),this.hasTitle=!!this.element.attr("title"),this.options.label&&this.options.label!==this.originalLabel&&(this.isInput?this.element.val(this.options.label):this.element.html(this.options.label)),this._addClass("ui-button","ui-widget"),this._setOption("disabled",this.options.disabled),this._enhance(),this.element.is("a")&&this._on({keyup:function(e){e.keyCode===t.ui.keyCode.SPACE&&(e.preventDefault(),this.element[0].click?this.element[0].click():this.element.trigger("click"))}})},_enhance:function(){this.element.is("button")||this.element.attr("role","button"),this.options.icon&&(this._updateIcon("icon",this.options.icon),this._updateTooltip())},_updateTooltip:function(){this.title=this.element.attr("title"),this.options.showLabel||this.title||this.element.attr("title",this.options.label)},_updateIcon:function(e,i){var s="iconPosition"!==e,n=s?this.options.iconPosition:i,o="top"===n||"bottom"===n;this.icon?s&&this._removeClass(this.icon,null,this.options.icon):(this.icon=t("<span>"),this._addClass(this.icon,"ui-button-icon","ui-icon"),this.options.showLabel||this._addClass("ui-button-icon-only")),s&&this._addClass(this.icon,null,i),this._attachIcon(n),o?(this._addClass(this.icon,null,"ui-widget-icon-block"),this.iconSpace&&this.iconSpace.remove()):(this.iconSpace||(this.iconSpace=t("<span> </span>"),this._addClass(this.iconSpace,"ui-button-icon-space")),this._removeClass(this.icon,null,"ui-wiget-icon-block"),this._attachIconSpace(n))},_destroy:function(){this.element.removeAttr("role"),this.icon&&this.icon.remove(),this.iconSpace&&this.iconSpace.remove(),this.hasTitle||this.element.removeAttr("title")},_attachIconSpace:function(t){this.icon[/^(?:end|bottom)/.test(t)?"before":"after"](this.iconSpace)},_attachIcon:function(t){this.element[/^(?:end|bottom)/.test(t)?"append":"prepend"](this.icon)},_setOptions:function(t){var e=void 0===t.showLabel?this.options.showLabel:t.showLabel,i=void 0===t.icon?this.options.icon:t.icon;e||i||(t.showLabel=!0),this._super(t)},_setOption:function(t,e){"icon"===t&&(e?this._updateIcon(t,e):this.icon&&(this.icon.remove(),this.iconSpace&&this.iconSpace.remove())),"iconPosition"===t&&this._updateIcon(t,e),"showLabel"===t&&(this._toggleClass("ui-button-icon-only",null,!e),this._updateTooltip()),"label"===t&&(this.isInput?this.element.val(e):(this.element.html(e),this.icon&&(this._attachIcon(this.options.iconPosition),this._attachIconSpace(this.options.iconPosition)))),this._super(t,e),"disabled"===t&&(this._toggleClass(null,"ui-state-disabled",e),this.element[0].disabled=e,e&&this.element.blur())},refresh:function(){var t=this.element.is("input, button")?this.element[0].disabled:this.element.hasClass("ui-button-disabled");t!==this.options.disabled&&this._setOptions({disabled:t}),this._updateTooltip()}}),t.uiBackCompat!==!1&&(t.widget("ui.button",t.ui.button,{options:{text:!0,icons:{primary:null,secondary:null}},_create:function(){this.options.showLabel&&!this.options.text&&(this.options.showLabel=this.options.text),!this.options.showLabel&&this.options.text&&(this.options.text=this.options.showLabel),this.options.icon||!this.options.icons.primary&&!this.options.icons.secondary?this.options.icon&&(this.options.icons.primary=this.options.icon):this.options.icons.primary?this.options.icon=this.options.icons.primary:(this.options.icon=this.options.icons.secondary,this.options.iconPosition="end"),this._super()},_setOption:function(t,e){return"text"===t?(this._super("showLabel",e),void 0):("showLabel"===t&&(this.options.text=e),"icon"===t&&(this.options.icons.primary=e),"icons"===t&&(e.primary?(this._super("icon",e.primary),this._super("iconPosition","beginning")):e.secondary&&(this._super("icon",e.secondary),this._super("iconPosition","end"))),this._superApply(arguments),void 0)}}),t.fn.button=function(e){return function(){return!this.length||this.length&&"INPUT"!==this[0].tagName||this.length&&"INPUT"===this[0].tagName&&"checkbox"!==this.attr("type")&&"radio"!==this.attr("type")?e.apply(this,arguments):(t.ui.checkboxradio||t.error("Checkboxradio widget missing"),0===arguments.length?this.checkboxradio({icon:!1}):this.checkboxradio.apply(this,arguments))}}(t.fn.button),t.fn.buttonset=function(){return t.ui.controlgroup||t.error("Controlgroup widget missing"),"option"===arguments[0]&&"items"===arguments[1]&&arguments[2]?this.controlgroup.apply(this,[arguments[0],"items.button",arguments[2]]):"option"===arguments[0]&&"items"===arguments[1]?this.controlgroup.apply(this,[arguments[0],"items.button"]):("object"==typeof arguments[0]&&arguments[0].items&&(arguments[0].items={button:arguments[0].items}),this.controlgroup.apply(this,arguments))}),t.ui.button,t.extend(t.ui,{datepicker:{version:"1.12.1"}});var m;t.extend(s.prototype,{markerClassName:"hasDatepicker",maxRows:4,_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(t){return a(this._defaults,t||{}),this},_attachDatepicker:function(e,i){var s,n,o;s=e.nodeName.toLowerCase(),n="div"===s||"span"===s,e.id||(this.uuid+=1,e.id="dp"+this.uuid),o=this._newInst(t(e),n),o.settings=t.extend({},i||{}),"input"===s?this._connectDatepicker(e,o):n&&this._inlineDatepicker(e,o)},_newInst:function(e,i){var s=e[0].id.replace(/([^A-Za-z0-9_\-])/g,"\\\\$1");return{id:s,input:e,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:i,dpDiv:i?n(t("<div class='"+this._inlineClass+" ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")):this.dpDiv}},_connectDatepicker:function(e,i){var s=t(e);i.append=t([]),i.trigger=t([]),s.hasClass(this.markerClassName)||(this._attachments(s,i),s.addClass(this.markerClassName).on("keydown",this._doKeyDown).on("keypress",this._doKeyPress).on("keyup",this._doKeyUp),this._autoSize(i),t.data(e,"datepicker",i),i.settings.disabled&&this._disableDatepicker(e))},_attachments:function(e,i){var s,n,o,a=this._get(i,"appendText"),r=this._get(i,"isRTL");i.append&&i.append.remove(),a&&(i.append=t("<span class='"+this._appendClass+"'>"+a+"</span>"),e[r?"before":"after"](i.append)),e.off("focus",this._showDatepicker),i.trigger&&i.trigger.remove(),s=this._get(i,"showOn"),("focus"===s||"both"===s)&&e.on("focus",this._showDatepicker),("button"===s||"both"===s)&&(n=this._get(i,"buttonText"),o=this._get(i,"buttonImage"),i.trigger=t(this._get(i,"buttonImageOnly")?t("<img/>").addClass(this._triggerClass).attr({src:o,alt:n,title:n}):t("<button type='button'></button>").addClass(this._triggerClass).html(o?t("<img/>").attr({src:o,alt:n,title:n}):n)),e[r?"before":"after"](i.trigger),i.trigger.on("click",function(){return t.datepicker._datepickerShowing&&t.datepicker._lastInput===e[0]?t.datepicker._hideDatepicker():t.datepicker._datepickerShowing&&t.datepicker._lastInput!==e[0]?(t.datepicker._hideDatepicker(),t.datepicker._showDatepicker(e[0])):t.datepicker._showDatepicker(e[0]),!1}))},_autoSize:function(t){if(this._get(t,"autoSize")&&!t.inline){var e,i,s,n,o=new Date(2009,11,20),a=this._get(t,"dateFormat");a.match(/[DM]/)&&(e=function(t){for(i=0,s=0,n=0;t.length>n;n++)t[n].length>i&&(i=t[n].length,s=n);return s},o.setMonth(e(this._get(t,a.match(/MM/)?"monthNames":"monthNamesShort"))),o.setDate(e(this._get(t,a.match(/DD/)?"dayNames":"dayNamesShort"))+20-o.getDay())),t.input.attr("size",this._formatDate(t,o).length)}},_inlineDatepicker:function(e,i){var s=t(e);s.hasClass(this.markerClassName)||(s.addClass(this.markerClassName).append(i.dpDiv),t.data(e,"datepicker",i),this._setDate(i,this._getDefaultDate(i),!0),this._updateDatepicker(i),this._updateAlternate(i),i.settings.disabled&&this._disableDatepicker(e),i.dpDiv.css("display","block"))},_dialogDatepicker:function(e,i,s,n,o){var r,h,l,c,u,d=this._dialogInst;return d||(this.uuid+=1,r="dp"+this.uuid,this._dialogInput=t("<input type='text' id='"+r+"' style='position: absolute; top: -100px; width: 0px;'/>"),this._dialogInput.on("keydown",this._doKeyDown),t("body").append(this._dialogInput),d=this._dialogInst=this._newInst(this._dialogInput,!1),d.settings={},t.data(this._dialogInput[0],"datepicker",d)),a(d.settings,n||{}),i=i&&i.constructor===Date?this._formatDate(d,i):i,this._dialogInput.val(i),this._pos=o?o.length?o:[o.pageX,o.pageY]:null,this._pos||(h=document.documentElement.clientWidth,l=document.documentElement.clientHeight,c=document.documentElement.scrollLeft||document.body.scrollLeft,u=document.documentElement.scrollTop||document.body.scrollTop,this._pos=[h/2-100+c,l/2-150+u]),this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),d.settings.onSelect=s,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),t.blockUI&&t.blockUI(this.dpDiv),t.data(this._dialogInput[0],"datepicker",d),this},_destroyDatepicker:function(e){var i,s=t(e),n=t.data(e,"datepicker");s.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),t.removeData(e,"datepicker"),"input"===i?(n.append.remove(),n.trigger.remove(),s.removeClass(this.markerClassName).off("focus",this._showDatepicker).off("keydown",this._doKeyDown).off("keypress",this._doKeyPress).off("keyup",this._doKeyUp)):("div"===i||"span"===i)&&s.removeClass(this.markerClassName).empty(),m===n&&(m=null))},_enableDatepicker:function(e){var i,s,n=t(e),o=t.data(e,"datepicker");n.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),"input"===i?(e.disabled=!1,o.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().removeClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)),this._disabledInputs=t.map(this._disabledInputs,function(t){return t===e?null:t}))},_disableDatepicker:function(e){var i,s,n=t(e),o=t.data(e,"datepicker");n.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),"input"===i?(e.disabled=!0,o.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().addClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)),this._disabledInputs=t.map(this._disabledInputs,function(t){return t===e?null:t}),this._disabledInputs[this._disabledInputs.length]=e)},_isDisabledDatepicker:function(t){if(!t)return!1;for(var e=0;this._disabledInputs.length>e;e++)if(this._disabledInputs[e]===t)return!0;return!1},_getInst:function(e){try{return t.data(e,"datepicker")}catch(i){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(e,i,s){var n,o,r,h,l=this._getInst(e);return 2===arguments.length&&"string"==typeof i?"defaults"===i?t.extend({},t.datepicker._defaults):l?"all"===i?t.extend({},l.settings):this._get(l,i):null:(n=i||{},"string"==typeof i&&(n={},n[i]=s),l&&(this._curInst===l&&this._hideDatepicker(),o=this._getDateDatepicker(e,!0),r=this._getMinMaxDate(l,"min"),h=this._getMinMaxDate(l,"max"),a(l.settings,n),null!==r&&void 0!==n.dateFormat&&void 0===n.minDate&&(l.settings.minDate=this._formatDate(l,r)),null!==h&&void 0!==n.dateFormat&&void 0===n.maxDate&&(l.settings.maxDate=this._formatDate(l,h)),"disabled"in n&&(n.disabled?this._disableDatepicker(e):this._enableDatepicker(e)),this._attachments(t(e),l),this._autoSize(l),this._setDate(l,o),this._updateAlternate(l),this._updateDatepicker(l)),void 0)},_changeDatepicker:function(t,e,i){this._optionDatepicker(t,e,i)},_refreshDatepicker:function(t){var e=this._getInst(t);e&&this._updateDatepicker(e)},_setDateDatepicker:function(t,e){var i=this._getInst(t);i&&(this._setDate(i,e),this._updateDatepicker(i),this._updateAlternate(i))},_getDateDatepicker:function(t,e){var i=this._getInst(t);return i&&!i.inline&&this._setDateFromField(i,e),i?this._getDate(i):null},_doKeyDown:function(e){var i,s,n,o=t.datepicker._getInst(e.target),a=!0,r=o.dpDiv.is(".ui-datepicker-rtl");if(o._keyEvent=!0,t.datepicker._datepickerShowing)switch(e.keyCode){case 9:t.datepicker._hideDatepicker(),a=!1;break;case 13:return n=t("td."+t.datepicker._dayOverClass+":not(."+t.datepicker._currentClass+")",o.dpDiv),n[0]&&t.datepicker._selectDay(e.target,o.selectedMonth,o.selectedYear,n[0]),i=t.datepicker._get(o,"onSelect"),i?(s=t.datepicker._formatDate(o),i.apply(o.input?o.input[0]:null,[s,o])):t.datepicker._hideDatepicker(),!1;case 27:t.datepicker._hideDatepicker();break;case 33:t.datepicker._adjustDate(e.target,e.ctrlKey?-t.datepicker._get(o,"stepBigMonths"):-t.datepicker._get(o,"stepMonths"),"M");break;case 34:t.datepicker._adjustDate(e.target,e.ctrlKey?+t.datepicker._get(o,"stepBigMonths"):+t.datepicker._get(o,"stepMonths"),"M");break;case 35:(e.ctrlKey||e.metaKey)&&t.datepicker._clearDate(e.target),a=e.ctrlKey||e.metaKey;break;case 36:(e.ctrlKey||e.metaKey)&&t.datepicker._gotoToday(e.target),a=e.ctrlKey||e.metaKey;break;case 37:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,r?1:-1,"D"),a=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&t.datepicker._adjustDate(e.target,e.ctrlKey?-t.datepicker._get(o,"stepBigMonths"):-t.datepicker._get(o,"stepMonths"),"M");break;case 38:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,-7,"D"),a=e.ctrlKey||e.metaKey;break;case 39:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,r?-1:1,"D"),a=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&t.datepicker._adjustDate(e.target,e.ctrlKey?+t.datepicker._get(o,"stepBigMonths"):+t.datepicker._get(o,"stepMonths"),"M");break;case 40:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,7,"D"),a=e.ctrlKey||e.metaKey;break;default:a=!1}else 36===e.keyCode&&e.ctrlKey?t.datepicker._showDatepicker(this):a=!1;a&&(e.preventDefault(),e.stopPropagation())},_doKeyPress:function(e){var i,s,n=t.datepicker._getInst(e.target);return t.datepicker._get(n,"constrainInput")?(i=t.datepicker._possibleChars(t.datepicker._get(n,"dateFormat")),s=String.fromCharCode(null==e.charCode?e.keyCode:e.charCode),e.ctrlKey||e.metaKey||" ">s||!i||i.indexOf(s)>-1):void 0},_doKeyUp:function(e){var i,s=t.datepicker._getInst(e.target);if(s.input.val()!==s.lastVal)try{i=t.datepicker.parseDate(t.datepicker._get(s,"dateFormat"),s.input?s.input.val():null,t.datepicker._getFormatConfig(s)),i&&(t.datepicker._setDateFromField(s),t.datepicker._updateAlternate(s),t.datepicker._updateDatepicker(s))}catch(n){}return!0},_showDatepicker:function(e){if(e=e.target||e,"input"!==e.nodeName.toLowerCase()&&(e=t("input",e.parentNode)[0]),!t.datepicker._isDisabledDatepicker(e)&&t.datepicker._lastInput!==e){var s,n,o,r,h,l,c;s=t.datepicker._getInst(e),t.datepicker._curInst&&t.datepicker._curInst!==s&&(t.datepicker._curInst.dpDiv.stop(!0,!0),s&&t.datepicker._datepickerShowing&&t.datepicker._hideDatepicker(t.datepicker._curInst.input[0])),n=t.datepicker._get(s,"beforeShow"),o=n?n.apply(e,[e,s]):{},o!==!1&&(a(s.settings,o),s.lastVal=null,t.datepicker._lastInput=e,t.datepicker._setDateFromField(s),t.datepicker._inDialog&&(e.value=""),t.datepicker._pos||(t.datepicker._pos=t.datepicker._findPos(e),t.datepicker._pos[1]+=e.offsetHeight),r=!1,t(e).parents().each(function(){return r|="fixed"===t(this).css("position"),!r}),h={left:t.datepicker._pos[0],top:t.datepicker._pos[1]},t.datepicker._pos=null,s.dpDiv.empty(),s.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),t.datepicker._updateDatepicker(s),h=t.datepicker._checkOffset(s,h,r),s.dpDiv.css({position:t.datepicker._inDialog&&t.blockUI?"static":r?"fixed":"absolute",display:"none",left:h.left+"px",top:h.top+"px"}),s.inline||(l=t.datepicker._get(s,"showAnim"),c=t.datepicker._get(s,"duration"),s.dpDiv.css("z-index",i(t(e))+1),t.datepicker._datepickerShowing=!0,t.effects&&t.effects.effect[l]?s.dpDiv.show(l,t.datepicker._get(s,"showOptions"),c):s.dpDiv[l||"show"](l?c:null),t.datepicker._shouldFocusInput(s)&&s.input.trigger("focus"),t.datepicker._curInst=s))
+}},_updateDatepicker:function(e){this.maxRows=4,m=e,e.dpDiv.empty().append(this._generateHTML(e)),this._attachHandlers(e);var i,s=this._getNumberOfMonths(e),n=s[1],a=17,r=e.dpDiv.find("."+this._dayOverClass+" a");r.length>0&&o.apply(r.get(0)),e.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),n>1&&e.dpDiv.addClass("ui-datepicker-multi-"+n).css("width",a*n+"em"),e.dpDiv[(1!==s[0]||1!==s[1]?"add":"remove")+"Class"]("ui-datepicker-multi"),e.dpDiv[(this._get(e,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),e===t.datepicker._curInst&&t.datepicker._datepickerShowing&&t.datepicker._shouldFocusInput(e)&&e.input.trigger("focus"),e.yearshtml&&(i=e.yearshtml,setTimeout(function(){i===e.yearshtml&&e.yearshtml&&e.dpDiv.find("select.ui-datepicker-year:first").replaceWith(e.yearshtml),i=e.yearshtml=null},0))},_shouldFocusInput:function(t){return t.input&&t.input.is(":visible")&&!t.input.is(":disabled")&&!t.input.is(":focus")},_checkOffset:function(e,i,s){var n=e.dpDiv.outerWidth(),o=e.dpDiv.outerHeight(),a=e.input?e.input.outerWidth():0,r=e.input?e.input.outerHeight():0,h=document.documentElement.clientWidth+(s?0:t(document).scrollLeft()),l=document.documentElement.clientHeight+(s?0:t(document).scrollTop());return i.left-=this._get(e,"isRTL")?n-a:0,i.left-=s&&i.left===e.input.offset().left?t(document).scrollLeft():0,i.top-=s&&i.top===e.input.offset().top+r?t(document).scrollTop():0,i.left-=Math.min(i.left,i.left+n>h&&h>n?Math.abs(i.left+n-h):0),i.top-=Math.min(i.top,i.top+o>l&&l>o?Math.abs(o+r):0),i},_findPos:function(e){for(var i,s=this._getInst(e),n=this._get(s,"isRTL");e&&("hidden"===e.type||1!==e.nodeType||t.expr.filters.hidden(e));)e=e[n?"previousSibling":"nextSibling"];return i=t(e).offset(),[i.left,i.top]},_hideDatepicker:function(e){var i,s,n,o,a=this._curInst;!a||e&&a!==t.data(e,"datepicker")||this._datepickerShowing&&(i=this._get(a,"showAnim"),s=this._get(a,"duration"),n=function(){t.datepicker._tidyDialog(a)},t.effects&&(t.effects.effect[i]||t.effects[i])?a.dpDiv.hide(i,t.datepicker._get(a,"showOptions"),s,n):a.dpDiv["slideDown"===i?"slideUp":"fadeIn"===i?"fadeOut":"hide"](i?s:null,n),i||n(),this._datepickerShowing=!1,o=this._get(a,"onClose"),o&&o.apply(a.input?a.input[0]:null,[a.input?a.input.val():"",a]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),t.blockUI&&(t.unblockUI(),t("body").append(this.dpDiv))),this._inDialog=!1)},_tidyDialog:function(t){t.dpDiv.removeClass(this._dialogClass).off(".ui-datepicker-calendar")},_checkExternalClick:function(e){if(t.datepicker._curInst){var i=t(e.target),s=t.datepicker._getInst(i[0]);(i[0].id!==t.datepicker._mainDivId&&0===i.parents("#"+t.datepicker._mainDivId).length&&!i.hasClass(t.datepicker.markerClassName)&&!i.closest("."+t.datepicker._triggerClass).length&&t.datepicker._datepickerShowing&&(!t.datepicker._inDialog||!t.blockUI)||i.hasClass(t.datepicker.markerClassName)&&t.datepicker._curInst!==s)&&t.datepicker._hideDatepicker()}},_adjustDate:function(e,i,s){var n=t(e),o=this._getInst(n[0]);this._isDisabledDatepicker(n[0])||(this._adjustInstDate(o,i+("M"===s?this._get(o,"showCurrentAtPos"):0),s),this._updateDatepicker(o))},_gotoToday:function(e){var i,s=t(e),n=this._getInst(s[0]);this._get(n,"gotoCurrent")&&n.currentDay?(n.selectedDay=n.currentDay,n.drawMonth=n.selectedMonth=n.currentMonth,n.drawYear=n.selectedYear=n.currentYear):(i=new Date,n.selectedDay=i.getDate(),n.drawMonth=n.selectedMonth=i.getMonth(),n.drawYear=n.selectedYear=i.getFullYear()),this._notifyChange(n),this._adjustDate(s)},_selectMonthYear:function(e,i,s){var n=t(e),o=this._getInst(n[0]);o["selected"+("M"===s?"Month":"Year")]=o["draw"+("M"===s?"Month":"Year")]=parseInt(i.options[i.selectedIndex].value,10),this._notifyChange(o),this._adjustDate(n)},_selectDay:function(e,i,s,n){var o,a=t(e);t(n).hasClass(this._unselectableClass)||this._isDisabledDatepicker(a[0])||(o=this._getInst(a[0]),o.selectedDay=o.currentDay=t("a",n).html(),o.selectedMonth=o.currentMonth=i,o.selectedYear=o.currentYear=s,this._selectDate(e,this._formatDate(o,o.currentDay,o.currentMonth,o.currentYear)))},_clearDate:function(e){var i=t(e);this._selectDate(i,"")},_selectDate:function(e,i){var s,n=t(e),o=this._getInst(n[0]);i=null!=i?i:this._formatDate(o),o.input&&o.input.val(i),this._updateAlternate(o),s=this._get(o,"onSelect"),s?s.apply(o.input?o.input[0]:null,[i,o]):o.input&&o.input.trigger("change"),o.inline?this._updateDatepicker(o):(this._hideDatepicker(),this._lastInput=o.input[0],"object"!=typeof o.input[0]&&o.input.trigger("focus"),this._lastInput=null)},_updateAlternate:function(e){var i,s,n,o=this._get(e,"altField");o&&(i=this._get(e,"altFormat")||this._get(e,"dateFormat"),s=this._getDate(e),n=this.formatDate(i,s,this._getFormatConfig(e)),t(o).val(n))},noWeekends:function(t){var e=t.getDay();return[e>0&&6>e,""]},iso8601Week:function(t){var e,i=new Date(t.getTime());return i.setDate(i.getDate()+4-(i.getDay()||7)),e=i.getTime(),i.setMonth(0),i.setDate(1),Math.floor(Math.round((e-i)/864e5)/7)+1},parseDate:function(e,i,s){if(null==e||null==i)throw"Invalid arguments";if(i="object"==typeof i?""+i:i+"",""===i)return null;var n,o,a,r,h=0,l=(s?s.shortYearCutoff:null)||this._defaults.shortYearCutoff,c="string"!=typeof l?l:(new Date).getFullYear()%100+parseInt(l,10),u=(s?s.dayNamesShort:null)||this._defaults.dayNamesShort,d=(s?s.dayNames:null)||this._defaults.dayNames,p=(s?s.monthNamesShort:null)||this._defaults.monthNamesShort,f=(s?s.monthNames:null)||this._defaults.monthNames,g=-1,m=-1,_=-1,v=-1,b=!1,y=function(t){var i=e.length>n+1&&e.charAt(n+1)===t;return i&&n++,i},w=function(t){var e=y(t),s="@"===t?14:"!"===t?20:"y"===t&&e?4:"o"===t?3:2,n="y"===t?s:1,o=RegExp("^\\d{"+n+","+s+"}"),a=i.substring(h).match(o);if(!a)throw"Missing number at position "+h;return h+=a[0].length,parseInt(a[0],10)},k=function(e,s,n){var o=-1,a=t.map(y(e)?n:s,function(t,e){return[[e,t]]}).sort(function(t,e){return-(t[1].length-e[1].length)});if(t.each(a,function(t,e){var s=e[1];return i.substr(h,s.length).toLowerCase()===s.toLowerCase()?(o=e[0],h+=s.length,!1):void 0}),-1!==o)return o+1;throw"Unknown name at position "+h},x=function(){if(i.charAt(h)!==e.charAt(n))throw"Unexpected literal at position "+h;h++};for(n=0;e.length>n;n++)if(b)"'"!==e.charAt(n)||y("'")?x():b=!1;else switch(e.charAt(n)){case"d":_=w("d");break;case"D":k("D",u,d);break;case"o":v=w("o");break;case"m":m=w("m");break;case"M":m=k("M",p,f);break;case"y":g=w("y");break;case"@":r=new Date(w("@")),g=r.getFullYear(),m=r.getMonth()+1,_=r.getDate();break;case"!":r=new Date((w("!")-this._ticksTo1970)/1e4),g=r.getFullYear(),m=r.getMonth()+1,_=r.getDate();break;case"'":y("'")?x():b=!0;break;default:x()}if(i.length>h&&(a=i.substr(h),!/^\s+/.test(a)))throw"Extra/unparsed characters found in date: "+a;if(-1===g?g=(new Date).getFullYear():100>g&&(g+=(new Date).getFullYear()-(new Date).getFullYear()%100+(c>=g?0:-100)),v>-1)for(m=1,_=v;;){if(o=this._getDaysInMonth(g,m-1),o>=_)break;m++,_-=o}if(r=this._daylightSavingAdjust(new Date(g,m-1,_)),r.getFullYear()!==g||r.getMonth()+1!==m||r.getDate()!==_)throw"Invalid date";return r},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:1e7*60*60*24*(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925)),formatDate:function(t,e,i){if(!e)return"";var s,n=(i?i.dayNamesShort:null)||this._defaults.dayNamesShort,o=(i?i.dayNames:null)||this._defaults.dayNames,a=(i?i.monthNamesShort:null)||this._defaults.monthNamesShort,r=(i?i.monthNames:null)||this._defaults.monthNames,h=function(e){var i=t.length>s+1&&t.charAt(s+1)===e;return i&&s++,i},l=function(t,e,i){var s=""+e;if(h(t))for(;i>s.length;)s="0"+s;return s},c=function(t,e,i,s){return h(t)?s[e]:i[e]},u="",d=!1;if(e)for(s=0;t.length>s;s++)if(d)"'"!==t.charAt(s)||h("'")?u+=t.charAt(s):d=!1;else switch(t.charAt(s)){case"d":u+=l("d",e.getDate(),2);break;case"D":u+=c("D",e.getDay(),n,o);break;case"o":u+=l("o",Math.round((new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime()-new Date(e.getFullYear(),0,0).getTime())/864e5),3);break;case"m":u+=l("m",e.getMonth()+1,2);break;case"M":u+=c("M",e.getMonth(),a,r);break;case"y":u+=h("y")?e.getFullYear():(10>e.getFullYear()%100?"0":"")+e.getFullYear()%100;break;case"@":u+=e.getTime();break;case"!":u+=1e4*e.getTime()+this._ticksTo1970;break;case"'":h("'")?u+="'":d=!0;break;default:u+=t.charAt(s)}return u},_possibleChars:function(t){var e,i="",s=!1,n=function(i){var s=t.length>e+1&&t.charAt(e+1)===i;return s&&e++,s};for(e=0;t.length>e;e++)if(s)"'"!==t.charAt(e)||n("'")?i+=t.charAt(e):s=!1;else switch(t.charAt(e)){case"d":case"m":case"y":case"@":i+="0123456789";break;case"D":case"M":return null;case"'":n("'")?i+="'":s=!0;break;default:i+=t.charAt(e)}return i},_get:function(t,e){return void 0!==t.settings[e]?t.settings[e]:this._defaults[e]},_setDateFromField:function(t,e){if(t.input.val()!==t.lastVal){var i=this._get(t,"dateFormat"),s=t.lastVal=t.input?t.input.val():null,n=this._getDefaultDate(t),o=n,a=this._getFormatConfig(t);try{o=this.parseDate(i,s,a)||n}catch(r){s=e?"":s}t.selectedDay=o.getDate(),t.drawMonth=t.selectedMonth=o.getMonth(),t.drawYear=t.selectedYear=o.getFullYear(),t.currentDay=s?o.getDate():0,t.currentMonth=s?o.getMonth():0,t.currentYear=s?o.getFullYear():0,this._adjustInstDate(t)}},_getDefaultDate:function(t){return this._restrictMinMax(t,this._determineDate(t,this._get(t,"defaultDate"),new Date))},_determineDate:function(e,i,s){var n=function(t){var e=new Date;return e.setDate(e.getDate()+t),e},o=function(i){try{return t.datepicker.parseDate(t.datepicker._get(e,"dateFormat"),i,t.datepicker._getFormatConfig(e))}catch(s){}for(var n=(i.toLowerCase().match(/^c/)?t.datepicker._getDate(e):null)||new Date,o=n.getFullYear(),a=n.getMonth(),r=n.getDate(),h=/([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,l=h.exec(i);l;){switch(l[2]||"d"){case"d":case"D":r+=parseInt(l[1],10);break;case"w":case"W":r+=7*parseInt(l[1],10);break;case"m":case"M":a+=parseInt(l[1],10),r=Math.min(r,t.datepicker._getDaysInMonth(o,a));break;case"y":case"Y":o+=parseInt(l[1],10),r=Math.min(r,t.datepicker._getDaysInMonth(o,a))}l=h.exec(i)}return new Date(o,a,r)},a=null==i||""===i?s:"string"==typeof i?o(i):"number"==typeof i?isNaN(i)?s:n(i):new Date(i.getTime());return a=a&&"Invalid Date"==""+a?s:a,a&&(a.setHours(0),a.setMinutes(0),a.setSeconds(0),a.setMilliseconds(0)),this._daylightSavingAdjust(a)},_daylightSavingAdjust:function(t){return t?(t.setHours(t.getHours()>12?t.getHours()+2:0),t):null},_setDate:function(t,e,i){var s=!e,n=t.selectedMonth,o=t.selectedYear,a=this._restrictMinMax(t,this._determineDate(t,e,new Date));t.selectedDay=t.currentDay=a.getDate(),t.drawMonth=t.selectedMonth=t.currentMonth=a.getMonth(),t.drawYear=t.selectedYear=t.currentYear=a.getFullYear(),n===t.selectedMonth&&o===t.selectedYear||i||this._notifyChange(t),this._adjustInstDate(t),t.input&&t.input.val(s?"":this._formatDate(t))},_getDate:function(t){var e=!t.currentYear||t.input&&""===t.input.val()?null:this._daylightSavingAdjust(new Date(t.currentYear,t.currentMonth,t.currentDay));return e},_attachHandlers:function(e){var i=this._get(e,"stepMonths"),s="#"+e.id.replace(/\\\\/g,"\\");e.dpDiv.find("[data-handler]").map(function(){var e={prev:function(){t.datepicker._adjustDate(s,-i,"M")},next:function(){t.datepicker._adjustDate(s,+i,"M")},hide:function(){t.datepicker._hideDatepicker()},today:function(){t.datepicker._gotoToday(s)},selectDay:function(){return t.datepicker._selectDay(s,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return t.datepicker._selectMonthYear(s,this,"M"),!1},selectYear:function(){return t.datepicker._selectMonthYear(s,this,"Y"),!1}};t(this).on(this.getAttribute("data-event"),e[this.getAttribute("data-handler")])})},_generateHTML:function(t){var e,i,s,n,o,a,r,h,l,c,u,d,p,f,g,m,_,v,b,y,w,k,x,C,D,I,T,P,M,S,H,z,O,A,N,W,E,F,L,R=new Date,B=this._daylightSavingAdjust(new Date(R.getFullYear(),R.getMonth(),R.getDate())),Y=this._get(t,"isRTL"),j=this._get(t,"showButtonPanel"),q=this._get(t,"hideIfNoPrevNext"),K=this._get(t,"navigationAsDateFormat"),U=this._getNumberOfMonths(t),V=this._get(t,"showCurrentAtPos"),$=this._get(t,"stepMonths"),X=1!==U[0]||1!==U[1],G=this._daylightSavingAdjust(t.currentDay?new Date(t.currentYear,t.currentMonth,t.currentDay):new Date(9999,9,9)),Q=this._getMinMaxDate(t,"min"),J=this._getMinMaxDate(t,"max"),Z=t.drawMonth-V,te=t.drawYear;if(0>Z&&(Z+=12,te--),J)for(e=this._daylightSavingAdjust(new Date(J.getFullYear(),J.getMonth()-U[0]*U[1]+1,J.getDate())),e=Q&&Q>e?Q:e;this._daylightSavingAdjust(new Date(te,Z,1))>e;)Z--,0>Z&&(Z=11,te--);for(t.drawMonth=Z,t.drawYear=te,i=this._get(t,"prevText"),i=K?this.formatDate(i,this._daylightSavingAdjust(new Date(te,Z-$,1)),this._getFormatConfig(t)):i,s=this._canAdjustMonth(t,-1,te,Z)?"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click' title='"+i+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"e":"w")+"'>"+i+"</span></a>":q?"":"<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='"+i+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"e":"w")+"'>"+i+"</span></a>",n=this._get(t,"nextText"),n=K?this.formatDate(n,this._daylightSavingAdjust(new Date(te,Z+$,1)),this._getFormatConfig(t)):n,o=this._canAdjustMonth(t,1,te,Z)?"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click' title='"+n+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"w":"e")+"'>"+n+"</span></a>":q?"":"<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='"+n+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"w":"e")+"'>"+n+"</span></a>",a=this._get(t,"currentText"),r=this._get(t,"gotoCurrent")&&t.currentDay?G:B,a=K?this.formatDate(a,r,this._getFormatConfig(t)):a,h=t.inline?"":"<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>"+this._get(t,"closeText")+"</button>",l=j?"<div class='ui-datepicker-buttonpane ui-widget-content'>"+(Y?h:"")+(this._isInRange(t,r)?"<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'>"+a+"</button>":"")+(Y?"":h)+"</div>":"",c=parseInt(this._get(t,"firstDay"),10),c=isNaN(c)?0:c,u=this._get(t,"showWeek"),d=this._get(t,"dayNames"),p=this._get(t,"dayNamesMin"),f=this._get(t,"monthNames"),g=this._get(t,"monthNamesShort"),m=this._get(t,"beforeShowDay"),_=this._get(t,"showOtherMonths"),v=this._get(t,"selectOtherMonths"),b=this._getDefaultDate(t),y="",k=0;U[0]>k;k++){for(x="",this.maxRows=4,C=0;U[1]>C;C++){if(D=this._daylightSavingAdjust(new Date(te,Z,t.selectedDay)),I=" ui-corner-all",T="",X){if(T+="<div class='ui-datepicker-group",U[1]>1)switch(C){case 0:T+=" ui-datepicker-group-first",I=" ui-corner-"+(Y?"right":"left");break;case U[1]-1:T+=" ui-datepicker-group-last",I=" ui-corner-"+(Y?"left":"right");break;default:T+=" ui-datepicker-group-middle",I=""}T+="'>"}for(T+="<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix"+I+"'>"+(/all|left/.test(I)&&0===k?Y?o:s:"")+(/all|right/.test(I)&&0===k?Y?s:o:"")+this._generateMonthYearHeader(t,Z,te,Q,J,k>0||C>0,f,g)+"</div><table class='ui-datepicker-calendar'><thead>"+"<tr>",P=u?"<th class='ui-datepicker-week-col'>"+this._get(t,"weekHeader")+"</th>":"",w=0;7>w;w++)M=(w+c)%7,P+="<th scope='col'"+((w+c+6)%7>=5?" class='ui-datepicker-week-end'":"")+">"+"<span title='"+d[M]+"'>"+p[M]+"</span></th>";for(T+=P+"</tr></thead><tbody>",S=this._getDaysInMonth(te,Z),te===t.selectedYear&&Z===t.selectedMonth&&(t.selectedDay=Math.min(t.selectedDay,S)),H=(this._getFirstDayOfMonth(te,Z)-c+7)%7,z=Math.ceil((H+S)/7),O=X?this.maxRows>z?this.maxRows:z:z,this.maxRows=O,A=this._daylightSavingAdjust(new Date(te,Z,1-H)),N=0;O>N;N++){for(T+="<tr>",W=u?"<td class='ui-datepicker-week-col'>"+this._get(t,"calculateWeek")(A)+"</td>":"",w=0;7>w;w++)E=m?m.apply(t.input?t.input[0]:null,[A]):[!0,""],F=A.getMonth()!==Z,L=F&&!v||!E[0]||Q&&Q>A||J&&A>J,W+="<td class='"+((w+c+6)%7>=5?" ui-datepicker-week-end":"")+(F?" ui-datepicker-other-month":"")+(A.getTime()===D.getTime()&&Z===t.selectedMonth&&t._keyEvent||b.getTime()===A.getTime()&&b.getTime()===D.getTime()?" "+this._dayOverClass:"")+(L?" "+this._unselectableClass+" ui-state-disabled":"")+(F&&!_?"":" "+E[1]+(A.getTime()===G.getTime()?" "+this._currentClass:"")+(A.getTime()===B.getTime()?" ui-datepicker-today":""))+"'"+(F&&!_||!E[2]?"":" title='"+E[2].replace(/'/g,"&#39;")+"'")+(L?"":" data-handler='selectDay' data-event='click' data-month='"+A.getMonth()+"' data-year='"+A.getFullYear()+"'")+">"+(F&&!_?"&#xa0;":L?"<span class='ui-state-default'>"+A.getDate()+"</span>":"<a class='ui-state-default"+(A.getTime()===B.getTime()?" ui-state-highlight":"")+(A.getTime()===G.getTime()?" ui-state-active":"")+(F?" ui-priority-secondary":"")+"' href='#'>"+A.getDate()+"</a>")+"</td>",A.setDate(A.getDate()+1),A=this._daylightSavingAdjust(A);T+=W+"</tr>"}Z++,Z>11&&(Z=0,te++),T+="</tbody></table>"+(X?"</div>"+(U[0]>0&&C===U[1]-1?"<div class='ui-datepicker-row-break'></div>":""):""),x+=T}y+=x}return y+=l,t._keyEvent=!1,y},_generateMonthYearHeader:function(t,e,i,s,n,o,a,r){var h,l,c,u,d,p,f,g,m=this._get(t,"changeMonth"),_=this._get(t,"changeYear"),v=this._get(t,"showMonthAfterYear"),b="<div class='ui-datepicker-title'>",y="";if(o||!m)y+="<span class='ui-datepicker-month'>"+a[e]+"</span>";else{for(h=s&&s.getFullYear()===i,l=n&&n.getFullYear()===i,y+="<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>",c=0;12>c;c++)(!h||c>=s.getMonth())&&(!l||n.getMonth()>=c)&&(y+="<option value='"+c+"'"+(c===e?" selected='selected'":"")+">"+r[c]+"</option>");y+="</select>"}if(v||(b+=y+(!o&&m&&_?"":"&#xa0;")),!t.yearshtml)if(t.yearshtml="",o||!_)b+="<span class='ui-datepicker-year'>"+i+"</span>";else{for(u=this._get(t,"yearRange").split(":"),d=(new Date).getFullYear(),p=function(t){var e=t.match(/c[+\-].*/)?i+parseInt(t.substring(1),10):t.match(/[+\-].*/)?d+parseInt(t,10):parseInt(t,10);return isNaN(e)?d:e},f=p(u[0]),g=Math.max(f,p(u[1]||"")),f=s?Math.max(f,s.getFullYear()):f,g=n?Math.min(g,n.getFullYear()):g,t.yearshtml+="<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>";g>=f;f++)t.yearshtml+="<option value='"+f+"'"+(f===i?" selected='selected'":"")+">"+f+"</option>";t.yearshtml+="</select>",b+=t.yearshtml,t.yearshtml=null}return b+=this._get(t,"yearSuffix"),v&&(b+=(!o&&m&&_?"":"&#xa0;")+y),b+="</div>"},_adjustInstDate:function(t,e,i){var s=t.selectedYear+("Y"===i?e:0),n=t.selectedMonth+("M"===i?e:0),o=Math.min(t.selectedDay,this._getDaysInMonth(s,n))+("D"===i?e:0),a=this._restrictMinMax(t,this._daylightSavingAdjust(new Date(s,n,o)));t.selectedDay=a.getDate(),t.drawMonth=t.selectedMonth=a.getMonth(),t.drawYear=t.selectedYear=a.getFullYear(),("M"===i||"Y"===i)&&this._notifyChange(t)},_restrictMinMax:function(t,e){var i=this._getMinMaxDate(t,"min"),s=this._getMinMaxDate(t,"max"),n=i&&i>e?i:e;return s&&n>s?s:n},_notifyChange:function(t){var e=this._get(t,"onChangeMonthYear");e&&e.apply(t.input?t.input[0]:null,[t.selectedYear,t.selectedMonth+1,t])},_getNumberOfMonths:function(t){var e=this._get(t,"numberOfMonths");return null==e?[1,1]:"number"==typeof e?[1,e]:e},_getMinMaxDate:function(t,e){return this._determineDate(t,this._get(t,e+"Date"),null)},_getDaysInMonth:function(t,e){return 32-this._daylightSavingAdjust(new Date(t,e,32)).getDate()},_getFirstDayOfMonth:function(t,e){return new Date(t,e,1).getDay()},_canAdjustMonth:function(t,e,i,s){var n=this._getNumberOfMonths(t),o=this._daylightSavingAdjust(new Date(i,s+(0>e?e:n[0]*n[1]),1));return 0>e&&o.setDate(this._getDaysInMonth(o.getFullYear(),o.getMonth())),this._isInRange(t,o)},_isInRange:function(t,e){var i,s,n=this._getMinMaxDate(t,"min"),o=this._getMinMaxDate(t,"max"),a=null,r=null,h=this._get(t,"yearRange");return h&&(i=h.split(":"),s=(new Date).getFullYear(),a=parseInt(i[0],10),r=parseInt(i[1],10),i[0].match(/[+\-].*/)&&(a+=s),i[1].match(/[+\-].*/)&&(r+=s)),(!n||e.getTime()>=n.getTime())&&(!o||e.getTime()<=o.getTime())&&(!a||e.getFullYear()>=a)&&(!r||r>=e.getFullYear())},_getFormatConfig:function(t){var e=this._get(t,"shortYearCutoff");return e="string"!=typeof e?e:(new Date).getFullYear()%100+parseInt(e,10),{shortYearCutoff:e,dayNamesShort:this._get(t,"dayNamesShort"),dayNames:this._get(t,"dayNames"),monthNamesShort:this._get(t,"monthNamesShort"),monthNames:this._get(t,"monthNames")}},_formatDate:function(t,e,i,s){e||(t.currentDay=t.selectedDay,t.currentMonth=t.selectedMonth,t.currentYear=t.selectedYear);var n=e?"object"==typeof e?e:this._daylightSavingAdjust(new Date(s,i,e)):this._daylightSavingAdjust(new Date(t.currentYear,t.currentMonth,t.currentDay));return this.formatDate(this._get(t,"dateFormat"),n,this._getFormatConfig(t))}}),t.fn.datepicker=function(e){if(!this.length)return this;t.datepicker.initialized||(t(document).on("mousedown",t.datepicker._checkExternalClick),t.datepicker.initialized=!0),0===t("#"+t.datepicker._mainDivId).length&&t("body").append(t.datepicker.dpDiv);var i=Array.prototype.slice.call(arguments,1);return"string"!=typeof e||"isDisabled"!==e&&"getDate"!==e&&"widget"!==e?"option"===e&&2===arguments.length&&"string"==typeof arguments[1]?t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this[0]].concat(i)):this.each(function(){"string"==typeof e?t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this].concat(i)):t.datepicker._attachDatepicker(this,e)}):t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this[0]].concat(i))},t.datepicker=new s,t.datepicker.initialized=!1,t.datepicker.uuid=(new Date).getTime(),t.datepicker.version="1.12.1",t.datepicker,t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());var _=!1;t(document).on("mouseup",function(){_=!1}),t.widget("ui.mouse",{version:"1.12.1",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(e){if(!_){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(e),this._mouseDownEvent=e;var i=this,s=1===e.which,n="string"==typeof this.options.cancel&&e.target.nodeName?t(e.target).closest(this.options.cancel).length:!1;return s&&!n&&this._mouseCapture(e)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(e)!==!1,!this._mouseStarted)?(e.preventDefault(),!0):(!0===t.data(e.target,this.widgetName+".preventClickEvent")&&t.removeData(e.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return i._mouseMove(t)},this._mouseUpDelegate=function(t){return i._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),e.preventDefault(),_=!0,!0)):!0}},_mouseMove:function(e){if(this._mouseMoved){if(t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button)return this._mouseUp(e);if(!e.which)if(e.originalEvent.altKey||e.originalEvent.ctrlKey||e.originalEvent.metaKey||e.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(e)}return(e.which||e.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,_=!1,e.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),t.ui.plugin={add:function(e,i,s){var n,o=t.ui[e].prototype;for(n in s)o.plugins[n]=o.plugins[n]||[],o.plugins[n].push([i,s[n]])},call:function(t,e,i,s){var n,o=t.plugins[e];if(o&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(n=0;o.length>n;n++)t.options[o[n][0]]&&o[n][1].apply(t.element,i)}},t.ui.safeBlur=function(e){e&&"body"!==e.nodeName.toLowerCase()&&t(e).trigger("blur")},t.widget("ui.draggable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"===this.options.helper&&this._setPositionRelative(),this.options.addClasses&&this._addClass("ui-draggable"),this._setHandleClassName(),this._mouseInit()},_setOption:function(t,e){this._super(t,e),"handle"===t&&(this._removeHandleClassName(),this._setHandleClassName())},_destroy:function(){return(this.helper||this.element).is(".ui-draggable-dragging")?(this.destroyOnClear=!0,void 0):(this._removeHandleClassName(),this._mouseDestroy(),void 0)},_mouseCapture:function(e){var i=this.options;return this.helper||i.disabled||t(e.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(e),this.handle?(this._blurActiveElement(e),this._blockFrames(i.iframeFix===!0?"iframe":i.iframeFix),!0):!1)},_blockFrames:function(e){this.iframeBlocks=this.document.find(e).map(function(){var e=t(this);return t("<div>").css("position","absolute").appendTo(e.parent()).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(e){var i=t.ui.safeActiveElement(this.document[0]),s=t(e.target);s.closest(i).length||t.ui.safeBlur(i)},_mouseStart:function(e){var i=this.options;return this.helper=this._createHelper(e),this._addClass(this.helper,"ui-draggable-dragging"),this._cacheHelperProportions(),t.ui.ddmanager&&(t.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=this.helper.parents().filter(function(){return"fixed"===t(this).css("position")}).length>0,this.positionAbs=this.element.offset(),this._refreshOffsets(e),this.originalPosition=this.position=this._generatePosition(e,!1),this.originalPageX=e.pageX,this.originalPageY=e.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",e)===!1?(this._clear(),!1):(this._cacheHelperProportions(),t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this._mouseDrag(e,!0),t.ui.ddmanager&&t.ui.ddmanager.dragStart(this,e),!0)},_refreshOffsets:function(t){this.offset={top:this.positionAbs.top-this.margins.top,left:this.positionAbs.left-this.margins.left,scroll:!1,parent:this._getParentOffset(),relative:this._getRelativeOffset()},this.offset.click={left:t.pageX-this.offset.left,top:t.pageY-this.offset.top}},_mouseDrag:function(e,i){if(this.hasFixedAncestor&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(e,!0),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",e,s)===!1)return this._mouseUp(new t.Event("mouseup",e)),!1;this.position=s.position}return this.helper[0].style.left=this.position.left+"px",this.helper[0].style.top=this.position.top+"px",t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),!1},_mouseStop:function(e){var i=this,s=!1;return t.ui.ddmanager&&!this.options.dropBehaviour&&(s=t.ui.ddmanager.drop(this,e)),this.dropped&&(s=this.dropped,this.dropped=!1),"invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||t.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?t(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",e)!==!1&&i._clear()}):this._trigger("stop",e)!==!1&&this._clear(),!1},_mouseUp:function(e){return this._unblockFrames(),t.ui.ddmanager&&t.ui.ddmanager.dragStop(this,e),this.handleElement.is(e.target)&&this.element.trigger("focus"),t.ui.mouse.prototype._mouseUp.call(this,e)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp(new t.Event("mouseup",{target:this.element[0]})):this._clear(),this},_getHandle:function(e){return this.options.handle?!!t(e.target).closest(this.element.find(this.options.handle)).length:!0},_setHandleClassName:function(){this.handleElement=this.options.handle?this.element.find(this.options.handle):this.element,this._addClass(this.handleElement,"ui-draggable-handle")},_removeHandleClassName:function(){this._removeClass(this.handleElement,"ui-draggable-handle")},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper),n=s?t(i.helper.apply(this.element[0],[e])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return n.parents("body").length||n.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s&&n[0]===this.element[0]&&this._setPositionRelative(),n[0]===this.element[0]||/(fixed|absolute)/.test(n.css("position"))||n.css("position","absolute"),n},_setPositionRelative:function(){/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative")},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_isRootNode:function(t){return/(html|body)/i.test(t.tagName)||t===this.document[0]},_getParentOffset:function(){var e=this.offsetParent.offset(),i=this.document[0];return"absolute"===this.cssPosition&&this.scrollParent[0]!==i&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),this._isRootNode(this.offsetParent[0])&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"!==this.cssPosition)return{top:0,left:0};var t=this.element.position(),e=this._isRootNode(this.scrollParent[0]);return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+(e?0:this.scrollParent.scrollTop()),left:t.left-(parseInt(this.helper.css("left"),10)||0)+(e?0:this.scrollParent.scrollLeft())}
+},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options,o=this.document[0];return this.relativeContainer=null,n.containment?"window"===n.containment?(this.containment=[t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,t(window).scrollLeft()+t(window).width()-this.helperProportions.width-this.margins.left,t(window).scrollTop()+(t(window).height()||o.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):"document"===n.containment?(this.containment=[0,0,t(o).width()-this.helperProportions.width-this.margins.left,(t(o).height()||o.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):n.containment.constructor===Array?(this.containment=n.containment,void 0):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=t(n.containment),s=i[0],s&&(e=/(scroll|auto)/.test(i.css("overflow")),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relativeContainer=i),void 0):(this.containment=null,void 0)},_convertPositionTo:function(t,e){e||(e=this.position);var i="absolute"===t?1:-1,s=this._isRootNode(this.scrollParent[0]);return{top:e.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.offset.scroll.top:s?0:this.offset.scroll.top)*i,left:e.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.offset.scroll.left:s?0:this.offset.scroll.left)*i}},_generatePosition:function(t,e){var i,s,n,o,a=this.options,r=this._isRootNode(this.scrollParent[0]),h=t.pageX,l=t.pageY;return r&&this.offset.scroll||(this.offset.scroll={top:this.scrollParent.scrollTop(),left:this.scrollParent.scrollLeft()}),e&&(this.containment&&(this.relativeContainer?(s=this.relativeContainer.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,t.pageX-this.offset.click.left<i[0]&&(h=i[0]+this.offset.click.left),t.pageY-this.offset.click.top<i[1]&&(l=i[1]+this.offset.click.top),t.pageX-this.offset.click.left>i[2]&&(h=i[2]+this.offset.click.left),t.pageY-this.offset.click.top>i[3]&&(l=i[3]+this.offset.click.top)),a.grid&&(n=a.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/a.grid[1])*a.grid[1]:this.originalPageY,l=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-a.grid[1]:n+a.grid[1]:n,o=a.grid[0]?this.originalPageX+Math.round((h-this.originalPageX)/a.grid[0])*a.grid[0]:this.originalPageX,h=i?o-this.offset.click.left>=i[0]||o-this.offset.click.left>i[2]?o:o-this.offset.click.left>=i[0]?o-a.grid[0]:o+a.grid[0]:o),"y"===a.axis&&(h=this.originalPageX),"x"===a.axis&&(l=this.originalPageY)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:r?0:this.offset.scroll.top),left:h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:r?0:this.offset.scroll.left)}},_clear:function(){this._removeClass(this.helper,"ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s,this],!0),/^(drag|start|stop)/.test(e)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i,s){var n=t.extend({},i,{item:s.element});s.sortables=[],t(s.options.connectToSortable).each(function(){var i=t(this).sortable("instance");i&&!i.options.disabled&&(s.sortables.push(i),i.refreshPositions(),i._trigger("activate",e,n))})},stop:function(e,i,s){var n=t.extend({},i,{item:s.element});s.cancelHelperRemoval=!1,t.each(s.sortables,function(){var t=this;t.isOver?(t.isOver=0,s.cancelHelperRemoval=!0,t.cancelHelperRemoval=!1,t._storedCSS={position:t.placeholder.css("position"),top:t.placeholder.css("top"),left:t.placeholder.css("left")},t._mouseStop(e),t.options.helper=t.options._helper):(t.cancelHelperRemoval=!0,t._trigger("deactivate",e,n))})},drag:function(e,i,s){t.each(s.sortables,function(){var n=!1,o=this;o.positionAbs=s.positionAbs,o.helperProportions=s.helperProportions,o.offset.click=s.offset.click,o._intersectsWith(o.containerCache)&&(n=!0,t.each(s.sortables,function(){return this.positionAbs=s.positionAbs,this.helperProportions=s.helperProportions,this.offset.click=s.offset.click,this!==o&&this._intersectsWith(this.containerCache)&&t.contains(o.element[0],this.element[0])&&(n=!1),n})),n?(o.isOver||(o.isOver=1,s._parent=i.helper.parent(),o.currentItem=i.helper.appendTo(o.element).data("ui-sortable-item",!0),o.options._helper=o.options.helper,o.options.helper=function(){return i.helper[0]},e.target=o.currentItem[0],o._mouseCapture(e,!0),o._mouseStart(e,!0,!0),o.offset.click.top=s.offset.click.top,o.offset.click.left=s.offset.click.left,o.offset.parent.left-=s.offset.parent.left-o.offset.parent.left,o.offset.parent.top-=s.offset.parent.top-o.offset.parent.top,s._trigger("toSortable",e),s.dropped=o.element,t.each(s.sortables,function(){this.refreshPositions()}),s.currentItem=s.element,o.fromOutside=s),o.currentItem&&(o._mouseDrag(e),i.position=o.position)):o.isOver&&(o.isOver=0,o.cancelHelperRemoval=!0,o.options._revert=o.options.revert,o.options.revert=!1,o._trigger("out",e,o._uiHash(o)),o._mouseStop(e,!0),o.options.revert=o.options._revert,o.options.helper=o.options._helper,o.placeholder&&o.placeholder.remove(),i.helper.appendTo(s._parent),s._refreshOffsets(e),i.position=s._generatePosition(e,!0),s._trigger("fromSortable",e),s.dropped=!1,t.each(s.sortables,function(){this.refreshPositions()}))})}}),t.ui.plugin.add("draggable","cursor",{start:function(e,i,s){var n=t("body"),o=s.options;n.css("cursor")&&(o._cursor=n.css("cursor")),n.css("cursor",o.cursor)},stop:function(e,i,s){var n=s.options;n._cursor&&t("body").css("cursor",n._cursor)}}),t.ui.plugin.add("draggable","opacity",{start:function(e,i,s){var n=t(i.helper),o=s.options;n.css("opacity")&&(o._opacity=n.css("opacity")),n.css("opacity",o.opacity)},stop:function(e,i,s){var n=s.options;n._opacity&&t(i.helper).css("opacity",n._opacity)}}),t.ui.plugin.add("draggable","scroll",{start:function(t,e,i){i.scrollParentNotHidden||(i.scrollParentNotHidden=i.helper.scrollParent(!1)),i.scrollParentNotHidden[0]!==i.document[0]&&"HTML"!==i.scrollParentNotHidden[0].tagName&&(i.overflowOffset=i.scrollParentNotHidden.offset())},drag:function(e,i,s){var n=s.options,o=!1,a=s.scrollParentNotHidden[0],r=s.document[0];a!==r&&"HTML"!==a.tagName?(n.axis&&"x"===n.axis||(s.overflowOffset.top+a.offsetHeight-e.pageY<n.scrollSensitivity?a.scrollTop=o=a.scrollTop+n.scrollSpeed:e.pageY-s.overflowOffset.top<n.scrollSensitivity&&(a.scrollTop=o=a.scrollTop-n.scrollSpeed)),n.axis&&"y"===n.axis||(s.overflowOffset.left+a.offsetWidth-e.pageX<n.scrollSensitivity?a.scrollLeft=o=a.scrollLeft+n.scrollSpeed:e.pageX-s.overflowOffset.left<n.scrollSensitivity&&(a.scrollLeft=o=a.scrollLeft-n.scrollSpeed))):(n.axis&&"x"===n.axis||(e.pageY-t(r).scrollTop()<n.scrollSensitivity?o=t(r).scrollTop(t(r).scrollTop()-n.scrollSpeed):t(window).height()-(e.pageY-t(r).scrollTop())<n.scrollSensitivity&&(o=t(r).scrollTop(t(r).scrollTop()+n.scrollSpeed))),n.axis&&"y"===n.axis||(e.pageX-t(r).scrollLeft()<n.scrollSensitivity?o=t(r).scrollLeft(t(r).scrollLeft()-n.scrollSpeed):t(window).width()-(e.pageX-t(r).scrollLeft())<n.scrollSensitivity&&(o=t(r).scrollLeft(t(r).scrollLeft()+n.scrollSpeed)))),o!==!1&&t.ui.ddmanager&&!n.dropBehaviour&&t.ui.ddmanager.prepareOffsets(s,e)}}),t.ui.plugin.add("draggable","snap",{start:function(e,i,s){var n=s.options;s.snapElements=[],t(n.snap.constructor!==String?n.snap.items||":data(ui-draggable)":n.snap).each(function(){var e=t(this),i=e.offset();this!==s.element[0]&&s.snapElements.push({item:this,width:e.outerWidth(),height:e.outerHeight(),top:i.top,left:i.left})})},drag:function(e,i,s){var n,o,a,r,h,l,c,u,d,p,f=s.options,g=f.snapTolerance,m=i.offset.left,_=m+s.helperProportions.width,v=i.offset.top,b=v+s.helperProportions.height;for(d=s.snapElements.length-1;d>=0;d--)h=s.snapElements[d].left-s.margins.left,l=h+s.snapElements[d].width,c=s.snapElements[d].top-s.margins.top,u=c+s.snapElements[d].height,h-g>_||m>l+g||c-g>b||v>u+g||!t.contains(s.snapElements[d].item.ownerDocument,s.snapElements[d].item)?(s.snapElements[d].snapping&&s.options.snap.release&&s.options.snap.release.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[d].item})),s.snapElements[d].snapping=!1):("inner"!==f.snapMode&&(n=g>=Math.abs(c-b),o=g>=Math.abs(u-v),a=g>=Math.abs(h-_),r=g>=Math.abs(l-m),n&&(i.position.top=s._convertPositionTo("relative",{top:c-s.helperProportions.height,left:0}).top),o&&(i.position.top=s._convertPositionTo("relative",{top:u,left:0}).top),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h-s.helperProportions.width}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l}).left)),p=n||o||a||r,"outer"!==f.snapMode&&(n=g>=Math.abs(c-v),o=g>=Math.abs(u-b),a=g>=Math.abs(h-m),r=g>=Math.abs(l-_),n&&(i.position.top=s._convertPositionTo("relative",{top:c,left:0}).top),o&&(i.position.top=s._convertPositionTo("relative",{top:u-s.helperProportions.height,left:0}).top),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l-s.helperProportions.width}).left)),!s.snapElements[d].snapping&&(n||o||a||r||p)&&s.options.snap.snap&&s.options.snap.snap.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[d].item})),s.snapElements[d].snapping=n||o||a||r||p)}}),t.ui.plugin.add("draggable","stack",{start:function(e,i,s){var n,o=s.options,a=t.makeArray(t(o.stack)).sort(function(e,i){return(parseInt(t(e).css("zIndex"),10)||0)-(parseInt(t(i).css("zIndex"),10)||0)});a.length&&(n=parseInt(t(a[0]).css("zIndex"),10)||0,t(a).each(function(e){t(this).css("zIndex",n+e)}),this.css("zIndex",n+a.length))}}),t.ui.plugin.add("draggable","zIndex",{start:function(e,i,s){var n=t(i.helper),o=s.options;n.css("zIndex")&&(o._zIndex=n.css("zIndex")),n.css("zIndex",o.zIndex)},stop:function(e,i,s){var n=s.options;n._zIndex&&t(i.helper).css("zIndex",n._zIndex)}}),t.ui.draggable,t.widget("ui.resizable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,classes:{"ui-resizable-se":"ui-icon ui-icon-gripsmall-diagonal-se"},containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(t){return parseFloat(t)||0},_isNumber:function(t){return!isNaN(parseFloat(t))},_hasScroll:function(e,i){if("hidden"===t(e).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",n=!1;return e[s]>0?!0:(e[s]=1,n=e[s]>0,e[s]=0,n)},_create:function(){var e,i=this.options,s=this;this._addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!i.aspectRatio,aspectRatio:i.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:i.helper||i.ghost||i.animate?i.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(t("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,e={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(e),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(e),this._proportionallyResize()),this._setupHandles(),i.autoHide&&t(this.element).on("mouseenter",function(){i.disabled||(s._removeClass("ui-resizable-autohide"),s._handles.show())}).on("mouseleave",function(){i.disabled||s.resizing||(s._addClass("ui-resizable-autohide"),s._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeData("resizable").removeData("ui-resizable").off(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles();break;default:}},_setupHandles:function(){var e,i,s,n,o,a=this.options,r=this;if(this.handles=a.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=t(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),s=this.handles.split(","),this.handles={},i=0;s.length>i;i++)e=t.trim(s[i]),n="ui-resizable-"+e,o=t("<div>"),this._addClass(o,"ui-resizable-handle "+n),o.css({zIndex:a.zIndex}),this.handles[e]=".ui-resizable-"+e,this.element.append(o);this._renderAxis=function(e){var i,s,n,o;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String?this.handles[i]=this.element.children(this.handles[i]).first().show():(this.handles[i].jquery||this.handles[i].nodeType)&&(this.handles[i]=t(this.handles[i]),this._on(this.handles[i],{mousedown:r._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(s=t(this.handles[i],this.element),o=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(n,o),this._proportionallyResize()),this._handles=this._handles.add(this.handles[i])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){r.resizing||(this.className&&(o=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),r.axis=o&&o[1]?o[1]:"se")}),a.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._handles.remove()},_mouseCapture:function(e){var i,s,n=!1;for(i in this.handles)s=t(this.handles[i])[0],(s===e.target||t.contains(s,e.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(e){var i,s,n,o=this.options,a=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),o.containment&&(i+=t(o.containment).scrollLeft()||0,s+=t(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:a.width(),height:a.height()},this.originalSize=this._helper?{width:a.outerWidth(),height:a.outerHeight()}:{width:a.width(),height:a.height()},this.sizeDiff={width:a.outerWidth()-a.width(),height:a.outerHeight()-a.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:e.pageX,top:e.pageY},this.aspectRatio="number"==typeof o.aspectRatio?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,n=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===n?this.axis+"-resize":n),this._addClass("ui-resizable-resizing"),this._propagate("start",e),!0},_mouseDrag:function(e){var i,s,n=this.originalMousePosition,o=this.axis,a=e.pageX-n.left||0,r=e.pageY-n.top||0,h=this._change[o];return this._updatePrevProperties(),h?(i=h.apply(this,[e,a,r]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",e,this.ui()),this._applyChanges()),!1):!1},_mouseStop:function(e){this.resizing=!1;var i,s,n,o,a,r,h,l=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&this._hasScroll(i[0],"left")?0:c.sizeDiff.height,o=s?0:c.sizeDiff.width,a={width:c.helper.width()-o,height:c.helper.height()-n},r=parseFloat(c.element.css("left"))+(c.position.left-c.originalPosition.left)||null,h=parseFloat(c.element.css("top"))+(c.position.top-c.originalPosition.top)||null,l.animate||this.element.css(t.extend(a,{top:h,left:r})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!l.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s,n,o,a=this.options;o={minWidth:this._isNumber(a.minWidth)?a.minWidth:0,maxWidth:this._isNumber(a.maxWidth)?a.maxWidth:1/0,minHeight:this._isNumber(a.minHeight)?a.minHeight:0,maxHeight:this._isNumber(a.maxHeight)?a.maxHeight:1/0},(this._aspectRatio||t)&&(e=o.minHeight*this.aspectRatio,s=o.minWidth/this.aspectRatio,i=o.maxHeight*this.aspectRatio,n=o.maxWidth/this.aspectRatio,e>o.minWidth&&(o.minWidth=e),s>o.minHeight&&(o.minHeight=s),o.maxWidth>i&&(o.maxWidth=i),o.maxHeight>n&&(o.maxHeight=n)),this._vBoundaries=o},_updateCache:function(t){this.offset=this.helper.offset(),this._isNumber(t.left)&&(this.position.left=t.left),this._isNumber(t.top)&&(this.position.top=t.top),this._isNumber(t.height)&&(this.size.height=t.height),this._isNumber(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,i=this.size,s=this.axis;return this._isNumber(t.height)?t.width=t.height*this.aspectRatio:this._isNumber(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===s&&(t.left=e.left+(i.width-t.width),t.top=null),"nw"===s&&(t.top=e.top+(i.height-t.height),t.left=e.left+(i.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,i=this.axis,s=this._isNumber(t.width)&&e.maxWidth&&e.maxWidth<t.width,n=this._isNumber(t.height)&&e.maxHeight&&e.maxHeight<t.height,o=this._isNumber(t.width)&&e.minWidth&&e.minWidth>t.width,a=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,r=this.originalPosition.left+this.originalSize.width,h=this.originalPosition.top+this.originalSize.height,l=/sw|nw|w/.test(i),c=/nw|ne|n/.test(i);return o&&(t.width=e.minWidth),a&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),n&&(t.height=e.maxHeight),o&&l&&(t.left=r-e.minWidth),s&&l&&(t.left=r-e.maxWidth),a&&c&&(t.top=h-e.minHeight),n&&c&&(t.top=h-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],n=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];4>e;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(n[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;this._proportionallyResizeElements.length>e;e++)t=this._proportionallyResizeElements[e],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(t)),t.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("<div style='overflow:hidden;'></div>"),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize,s=this.originalPosition;return{left:s.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).resizable("instance"),s=i.options,n=i._proportionallyResizeElements,o=n.length&&/textarea/i.test(n[0].nodeName),a=o&&i._hasScroll(n[0],"left")?0:i.sizeDiff.height,r=o?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-a},l=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,c=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(h,c&&l?{top:c,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};n&&n.length&&t(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var e,i,s,n,o,a,r,h=t(this).resizable("instance"),l=h.options,c=h.element,u=l.containment,d=u instanceof t?u.get(0):/parent/.test(u)?c.parent().get(0):u;d&&(h.containerElement=t(d),/document/.test(u)||u===document?(h.containerOffset={left:0,top:0},h.containerPosition={left:0,top:0},h.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(e=t(d),i=[],t(["Top","Right","Left","Bottom"]).each(function(t,s){i[t]=h._num(e.css("padding"+s))}),h.containerOffset=e.offset(),h.containerPosition=e.position(),h.containerSize={height:e.innerHeight()-i[3],width:e.innerWidth()-i[1]},s=h.containerOffset,n=h.containerSize.height,o=h.containerSize.width,a=h._hasScroll(d,"left")?d.scrollWidth:o,r=h._hasScroll(d)?d.scrollHeight:n,h.parentData={element:d,left:s.left,top:s.top,width:a,height:r}))},resize:function(e){var i,s,n,o,a=t(this).resizable("instance"),r=a.options,h=a.containerOffset,l=a.position,c=a._aspectRatio||e.shiftKey,u={top:0,left:0},d=a.containerElement,p=!0;d[0]!==document&&/static/.test(d.css("position"))&&(u=h),l.left<(a._helper?h.left:0)&&(a.size.width=a.size.width+(a._helper?a.position.left-h.left:a.position.left-u.left),c&&(a.size.height=a.size.width/a.aspectRatio,p=!1),a.position.left=r.helper?h.left:0),l.top<(a._helper?h.top:0)&&(a.size.height=a.size.height+(a._helper?a.position.top-h.top:a.position.top),c&&(a.size.width=a.size.height*a.aspectRatio,p=!1),a.position.top=a._helper?h.top:0),n=a.containerElement.get(0)===a.element.parent().get(0),o=/relative|absolute/.test(a.containerElement.css("position")),n&&o?(a.offset.left=a.parentData.left+a.position.left,a.offset.top=a.parentData.top+a.position.top):(a.offset.left=a.element.offset().left,a.offset.top=a.element.offset().top),i=Math.abs(a.sizeDiff.width+(a._helper?a.offset.left-u.left:a.offset.left-h.left)),s=Math.abs(a.sizeDiff.height+(a._helper?a.offset.top-u.top:a.offset.top-h.top)),i+a.size.width>=a.parentData.width&&(a.size.width=a.parentData.width-i,c&&(a.size.height=a.size.width/a.aspectRatio,p=!1)),s+a.size.height>=a.parentData.height&&(a.size.height=a.parentData.height-s,c&&(a.size.width=a.size.height*a.aspectRatio,p=!1)),p||(a.position.left=a.prevPosition.left,a.position.top=a.prevPosition.top,a.size.width=a.prevSize.width,a.size.height=a.prevSize.height)},stop:function(){var e=t(this).resizable("instance"),i=e.options,s=e.containerOffset,n=e.containerPosition,o=e.containerElement,a=t(e.helper),r=a.offset(),h=a.outerWidth()-e.sizeDiff.width,l=a.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l}),e._helper&&!i.animate&&/static/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).resizable("instance"),i=e.options;t(i.alsoResize).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseFloat(e.width()),height:parseFloat(e.height()),left:parseFloat(e.css("left")),top:parseFloat(e.css("top"))})})},resize:function(e,i){var s=t(this).resizable("instance"),n=s.options,o=s.originalSize,a=s.originalPosition,r={height:s.size.height-o.height||0,width:s.size.width-o.width||0,top:s.position.top-a.top||0,left:s.position.left-a.left||0};t(n.alsoResize).each(function(){var e=t(this),s=t(this).data("ui-resizable-alsoresize"),n={},o=e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(o,function(t,e){var i=(s[e]||0)+(r[e]||0);i&&i>=0&&(n[e]=i||null)}),e.css(n)})},stop:function(){t(this).removeData("ui-resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).resizable("instance"),i=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:i.height,width:i.width,margin:0,left:0,top:0}),e._addClass(e.ghost,"ui-resizable-ghost"),t.uiBackCompat!==!1&&"string"==typeof e.options.ghost&&e.ghost.addClass(this.options.ghost),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).resizable("instance");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).resizable("instance");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e,i=t(this).resizable("instance"),s=i.options,n=i.size,o=i.originalSize,a=i.originalPosition,r=i.axis,h="number"==typeof s.grid?[s.grid,s.grid]:s.grid,l=h[0]||1,c=h[1]||1,u=Math.round((n.width-o.width)/l)*l,d=Math.round((n.height-o.height)/c)*c,p=o.width+u,f=o.height+d,g=s.maxWidth&&p>s.maxWidth,m=s.maxHeight&&f>s.maxHeight,_=s.minWidth&&s.minWidth>p,v=s.minHeight&&s.minHeight>f;s.grid=h,_&&(p+=l),v&&(f+=c),g&&(p-=l),m&&(f-=c),/^(se|s|e)$/.test(r)?(i.size.width=p,i.size.height=f):/^(ne)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.top=a.top-d):/^(sw)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.left=a.left-u):((0>=f-c||0>=p-l)&&(e=i._getPaddingPlusBorderDimensions(this)),f-c>0?(i.size.height=f,i.position.top=a.top-d):(f=c-e.height,i.size.height=f,i.position.top=a.top+o.height-f),p-l>0?(i.size.width=p,i.position.left=a.left-u):(p=l-e.width,i.size.width=p,i.position.left=a.left+o.width-p))}}),t.ui.resizable,t.widget("ui.dialog",{version:"1.12.1",options:{appendTo:"body",autoOpen:!0,buttons:[],classes:{"ui-dialog":"ui-corner-all","ui-dialog-titlebar":"ui-corner-all"},closeOnEscape:!0,closeText:"Close",draggable:!0,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(e){var i=t(this).css(e).offset().top;0>i&&t(this).css("top",e.top-i)}},resizable:!0,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},sizeRelatedOptions:{buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},resizableRelatedOptions:{maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height},this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.originalTitle=this.element.attr("title"),null==this.options.title&&null!=this.originalTitle&&(this.options.title=this.originalTitle),this.options.disabled&&(this.options.disabled=!1),this._createWrapper(),this.element.show().removeAttr("title").appendTo(this.uiDialog),this._addClass("ui-dialog-content","ui-widget-content"),this._createTitlebar(),this._createButtonPane(),this.options.draggable&&t.fn.draggable&&this._makeDraggable(),this.options.resizable&&t.fn.resizable&&this._makeResizable(),this._isOpen=!1,this._trackFocus()},_init:function(){this.options.autoOpen&&this.open()},_appendTo:function(){var e=this.options.appendTo;return e&&(e.jquery||e.nodeType)?t(e):this.document.find(e||"body").eq(0)},_destroy:function(){var t,e=this.originalPosition;this._untrackInstance(),this._destroyOverlay(),this.element.removeUniqueId().css(this.originalCss).detach(),this.uiDialog.remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),t=e.parent.children().eq(e.index),t.length&&t[0]!==this.element[0]?t.before(this.element):e.parent.append(this.element)},widget:function(){return this.uiDialog
+},disable:t.noop,enable:t.noop,close:function(e){var i=this;this._isOpen&&this._trigger("beforeClose",e)!==!1&&(this._isOpen=!1,this._focusedElement=null,this._destroyOverlay(),this._untrackInstance(),this.opener.filter(":focusable").trigger("focus").length||t.ui.safeBlur(t.ui.safeActiveElement(this.document[0])),this._hide(this.uiDialog,this.options.hide,function(){i._trigger("close",e)}))},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(e,i){var s=!1,n=this.uiDialog.siblings(".ui-front:visible").map(function(){return+t(this).css("z-index")}).get(),o=Math.max.apply(null,n);return o>=+this.uiDialog.css("z-index")&&(this.uiDialog.css("z-index",o+1),s=!0),s&&!i&&this._trigger("focus",e),s},open:function(){var e=this;return this._isOpen?(this._moveToTop()&&this._focusTabbable(),void 0):(this._isOpen=!0,this.opener=t(t.ui.safeActiveElement(this.document[0])),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this.overlay&&this.overlay.css("z-index",this.uiDialog.css("z-index")-1),this._show(this.uiDialog,this.options.show,function(){e._focusTabbable(),e._trigger("focus")}),this._makeFocusTarget(),this._trigger("open"),void 0)},_focusTabbable:function(){var t=this._focusedElement;t||(t=this.element.find("[autofocus]")),t.length||(t=this.element.find(":tabbable")),t.length||(t=this.uiDialogButtonPane.find(":tabbable")),t.length||(t=this.uiDialogTitlebarClose.filter(":tabbable")),t.length||(t=this.uiDialog),t.eq(0).trigger("focus")},_keepFocus:function(e){function i(){var e=t.ui.safeActiveElement(this.document[0]),i=this.uiDialog[0]===e||t.contains(this.uiDialog[0],e);i||this._focusTabbable()}e.preventDefault(),i.call(this),this._delay(i)},_createWrapper:function(){this.uiDialog=t("<div>").hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo()),this._addClass(this.uiDialog,"ui-dialog","ui-widget ui-widget-content ui-front"),this._on(this.uiDialog,{keydown:function(e){if(this.options.closeOnEscape&&!e.isDefaultPrevented()&&e.keyCode&&e.keyCode===t.ui.keyCode.ESCAPE)return e.preventDefault(),this.close(e),void 0;if(e.keyCode===t.ui.keyCode.TAB&&!e.isDefaultPrevented()){var i=this.uiDialog.find(":tabbable"),s=i.filter(":first"),n=i.filter(":last");e.target!==n[0]&&e.target!==this.uiDialog[0]||e.shiftKey?e.target!==s[0]&&e.target!==this.uiDialog[0]||!e.shiftKey||(this._delay(function(){n.trigger("focus")}),e.preventDefault()):(this._delay(function(){s.trigger("focus")}),e.preventDefault())}},mousedown:function(t){this._moveToTop(t)&&this._focusTabbable()}}),this.element.find("[aria-describedby]").length||this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})},_createTitlebar:function(){var e;this.uiDialogTitlebar=t("<div>"),this._addClass(this.uiDialogTitlebar,"ui-dialog-titlebar","ui-widget-header ui-helper-clearfix"),this._on(this.uiDialogTitlebar,{mousedown:function(e){t(e.target).closest(".ui-dialog-titlebar-close")||this.uiDialog.trigger("focus")}}),this.uiDialogTitlebarClose=t("<button type='button'></button>").button({label:t("<a>").text(this.options.closeText).html(),icon:"ui-icon-closethick",showLabel:!1}).appendTo(this.uiDialogTitlebar),this._addClass(this.uiDialogTitlebarClose,"ui-dialog-titlebar-close"),this._on(this.uiDialogTitlebarClose,{click:function(t){t.preventDefault(),this.close(t)}}),e=t("<span>").uniqueId().prependTo(this.uiDialogTitlebar),this._addClass(e,"ui-dialog-title"),this._title(e),this.uiDialogTitlebar.prependTo(this.uiDialog),this.uiDialog.attr({"aria-labelledby":e.attr("id")})},_title:function(t){this.options.title?t.text(this.options.title):t.html("&#160;")},_createButtonPane:function(){this.uiDialogButtonPane=t("<div>"),this._addClass(this.uiDialogButtonPane,"ui-dialog-buttonpane","ui-widget-content ui-helper-clearfix"),this.uiButtonSet=t("<div>").appendTo(this.uiDialogButtonPane),this._addClass(this.uiButtonSet,"ui-dialog-buttonset"),this._createButtons()},_createButtons:function(){var e=this,i=this.options.buttons;return this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),t.isEmptyObject(i)||t.isArray(i)&&!i.length?(this._removeClass(this.uiDialog,"ui-dialog-buttons"),void 0):(t.each(i,function(i,s){var n,o;s=t.isFunction(s)?{click:s,text:i}:s,s=t.extend({type:"button"},s),n=s.click,o={icon:s.icon,iconPosition:s.iconPosition,showLabel:s.showLabel,icons:s.icons,text:s.text},delete s.click,delete s.icon,delete s.iconPosition,delete s.showLabel,delete s.icons,"boolean"==typeof s.text&&delete s.text,t("<button></button>",s).button(o).appendTo(e.uiButtonSet).on("click",function(){n.apply(e.element[0],arguments)})}),this._addClass(this.uiDialog,"ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog),void 0)},_makeDraggable:function(){function e(t){return{position:t.position,offset:t.offset}}var i=this,s=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(s,n){i._addClass(t(this),"ui-dialog-dragging"),i._blockFrames(),i._trigger("dragStart",s,e(n))},drag:function(t,s){i._trigger("drag",t,e(s))},stop:function(n,o){var a=o.offset.left-i.document.scrollLeft(),r=o.offset.top-i.document.scrollTop();s.position={my:"left top",at:"left"+(a>=0?"+":"")+a+" "+"top"+(r>=0?"+":"")+r,of:i.window},i._removeClass(t(this),"ui-dialog-dragging"),i._unblockFrames(),i._trigger("dragStop",n,e(o))}})},_makeResizable:function(){function e(t){return{originalPosition:t.originalPosition,originalSize:t.originalSize,position:t.position,size:t.size}}var i=this,s=this.options,n=s.resizable,o=this.uiDialog.css("position"),a="string"==typeof n?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:s.maxWidth,maxHeight:s.maxHeight,minWidth:s.minWidth,minHeight:this._minHeight(),handles:a,start:function(s,n){i._addClass(t(this),"ui-dialog-resizing"),i._blockFrames(),i._trigger("resizeStart",s,e(n))},resize:function(t,s){i._trigger("resize",t,e(s))},stop:function(n,o){var a=i.uiDialog.offset(),r=a.left-i.document.scrollLeft(),h=a.top-i.document.scrollTop();s.height=i.uiDialog.height(),s.width=i.uiDialog.width(),s.position={my:"left top",at:"left"+(r>=0?"+":"")+r+" "+"top"+(h>=0?"+":"")+h,of:i.window},i._removeClass(t(this),"ui-dialog-resizing"),i._unblockFrames(),i._trigger("resizeStop",n,e(o))}}).css("position",o)},_trackFocus:function(){this._on(this.widget(),{focusin:function(e){this._makeFocusTarget(),this._focusedElement=t(e.target)}})},_makeFocusTarget:function(){this._untrackInstance(),this._trackingInstances().unshift(this)},_untrackInstance:function(){var e=this._trackingInstances(),i=t.inArray(this,e);-1!==i&&e.splice(i,1)},_trackingInstances:function(){var t=this.document.data("ui-dialog-instances");return t||(t=[],this.document.data("ui-dialog-instances",t)),t},_minHeight:function(){var t=this.options;return"auto"===t.height?t.minHeight:Math.min(t.minHeight,t.height)},_position:function(){var t=this.uiDialog.is(":visible");t||this.uiDialog.show(),this.uiDialog.position(this.options.position),t||this.uiDialog.hide()},_setOptions:function(e){var i=this,s=!1,n={};t.each(e,function(t,e){i._setOption(t,e),t in i.sizeRelatedOptions&&(s=!0),t in i.resizableRelatedOptions&&(n[t]=e)}),s&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",n)},_setOption:function(e,i){var s,n,o=this.uiDialog;"disabled"!==e&&(this._super(e,i),"appendTo"===e&&this.uiDialog.appendTo(this._appendTo()),"buttons"===e&&this._createButtons(),"closeText"===e&&this.uiDialogTitlebarClose.button({label:t("<a>").text(""+this.options.closeText).html()}),"draggable"===e&&(s=o.is(":data(ui-draggable)"),s&&!i&&o.draggable("destroy"),!s&&i&&this._makeDraggable()),"position"===e&&this._position(),"resizable"===e&&(n=o.is(":data(ui-resizable)"),n&&!i&&o.resizable("destroy"),n&&"string"==typeof i&&o.resizable("option","handles",i),n||i===!1||this._makeResizable()),"title"===e&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")))},_size:function(){var t,e,i,s=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),s.minWidth>s.width&&(s.width=s.minWidth),t=this.uiDialog.css({height:"auto",width:s.width}).outerHeight(),e=Math.max(0,s.minHeight-t),i="number"==typeof s.maxHeight?Math.max(0,s.maxHeight-t):"none","auto"===s.height?this.element.css({minHeight:e,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,s.height-t)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var e=t(this);return t("<div>").css({position:"absolute",width:e.outerWidth(),height:e.outerHeight()}).appendTo(e.parent()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(e){return t(e.target).closest(".ui-dialog").length?!0:!!t(e.target).closest(".ui-datepicker").length},_createOverlay:function(){if(this.options.modal){var e=!0;this._delay(function(){e=!1}),this.document.data("ui-dialog-overlays")||this._on(this.document,{focusin:function(t){e||this._allowInteraction(t)||(t.preventDefault(),this._trackingInstances()[0]._focusTabbable())}}),this.overlay=t("<div>").appendTo(this._appendTo()),this._addClass(this.overlay,null,"ui-widget-overlay ui-front"),this._on(this.overlay,{mousedown:"_keepFocus"}),this.document.data("ui-dialog-overlays",(this.document.data("ui-dialog-overlays")||0)+1)}},_destroyOverlay:function(){if(this.options.modal&&this.overlay){var t=this.document.data("ui-dialog-overlays")-1;t?this.document.data("ui-dialog-overlays",t):(this._off(this.document,"focusin"),this.document.removeData("ui-dialog-overlays")),this.overlay.remove(),this.overlay=null}}}),t.uiBackCompat!==!1&&t.widget("ui.dialog",t.ui.dialog,{options:{dialogClass:""},_createWrapper:function(){this._super(),this.uiDialog.addClass(this.options.dialogClass)},_setOption:function(t,e){"dialogClass"===t&&this.uiDialog.removeClass(this.options.dialogClass).addClass(e),this._superApply(arguments)}}),t.ui.dialog,t.widget("ui.droppable",{version:"1.12.1",widgetEventPrefix:"drop",options:{accept:"*",addClasses:!0,greedy:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var e,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=t.isFunction(s)?s:function(t){return t.is(s)},this.proportions=function(){return arguments.length?(e=arguments[0],void 0):e?e:e={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}},this._addToManager(i.scope),i.addClasses&&this._addClass("ui-droppable")},_addToManager:function(e){t.ui.ddmanager.droppables[e]=t.ui.ddmanager.droppables[e]||[],t.ui.ddmanager.droppables[e].push(this)},_splice:function(t){for(var e=0;t.length>e;e++)t[e]===this&&t.splice(e,1)},_destroy:function(){var e=t.ui.ddmanager.droppables[this.options.scope];this._splice(e)},_setOption:function(e,i){if("accept"===e)this.accept=t.isFunction(i)?i:function(t){return t.is(i)};else if("scope"===e){var s=t.ui.ddmanager.droppables[this.options.scope];this._splice(s),this._addToManager(i)}this._super(e,i)},_activate:function(e){var i=t.ui.ddmanager.current;this._addActiveClass(),i&&this._trigger("activate",e,this.ui(i))},_deactivate:function(e){var i=t.ui.ddmanager.current;this._removeActiveClass(),i&&this._trigger("deactivate",e,this.ui(i))},_over:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._addHoverClass(),this._trigger("over",e,this.ui(i)))},_out:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._removeHoverClass(),this._trigger("out",e,this.ui(i)))},_drop:function(e,i){var s=i||t.ui.ddmanager.current,n=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var i=t(this).droppable("instance");return i.options.greedy&&!i.options.disabled&&i.options.scope===s.options.scope&&i.accept.call(i.element[0],s.currentItem||s.element)&&v(s,t.extend(i,{offset:i.element.offset()}),i.options.tolerance,e)?(n=!0,!1):void 0}),n?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this._removeActiveClass(),this._removeHoverClass(),this._trigger("drop",e,this.ui(s)),this.element):!1):!1},ui:function(t){return{draggable:t.currentItem||t.element,helper:t.helper,position:t.position,offset:t.positionAbs}},_addHoverClass:function(){this._addClass("ui-droppable-hover")},_removeHoverClass:function(){this._removeClass("ui-droppable-hover")},_addActiveClass:function(){this._addClass("ui-droppable-active")},_removeActiveClass:function(){this._removeClass("ui-droppable-active")}});var v=t.ui.intersect=function(){function t(t,e,i){return t>=e&&e+i>t}return function(e,i,s,n){if(!i.offset)return!1;var o=(e.positionAbs||e.position.absolute).left+e.margins.left,a=(e.positionAbs||e.position.absolute).top+e.margins.top,r=o+e.helperProportions.width,h=a+e.helperProportions.height,l=i.offset.left,c=i.offset.top,u=l+i.proportions().width,d=c+i.proportions().height;switch(s){case"fit":return o>=l&&u>=r&&a>=c&&d>=h;case"intersect":return o+e.helperProportions.width/2>l&&u>r-e.helperProportions.width/2&&a+e.helperProportions.height/2>c&&d>h-e.helperProportions.height/2;case"pointer":return t(n.pageY,c,i.proportions().height)&&t(n.pageX,l,i.proportions().width);case"touch":return(a>=c&&d>=a||h>=c&&d>=h||c>a&&h>d)&&(o>=l&&u>=o||r>=l&&u>=r||l>o&&r>u);default:return!1}}}();t.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,i){var s,n,o=t.ui.ddmanager.droppables[e.options.scope]||[],a=i?i.type:null,r=(e.currentItem||e.element).find(":data(ui-droppable)").addBack();t:for(s=0;o.length>s;s++)if(!(o[s].options.disabled||e&&!o[s].accept.call(o[s].element[0],e.currentItem||e.element))){for(n=0;r.length>n;n++)if(r[n]===o[s].element[0]){o[s].proportions().height=0;continue t}o[s].visible="none"!==o[s].element.css("display"),o[s].visible&&("mousedown"===a&&o[s]._activate.call(o[s],i),o[s].offset=o[s].element.offset(),o[s].proportions({width:o[s].element[0].offsetWidth,height:o[s].element[0].offsetHeight}))}},drop:function(e,i){var s=!1;return t.each((t.ui.ddmanager.droppables[e.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&v(e,this,this.options.tolerance,i)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],e.currentItem||e.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(e,i){e.element.parentsUntil("body").on("scroll.droppable",function(){e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)})},drag:function(e,i){e.options.refreshPositions&&t.ui.ddmanager.prepareOffsets(e,i),t.each(t.ui.ddmanager.droppables[e.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,o,a=v(e,this,this.options.tolerance,i),r=!a&&this.isover?"isout":a&&!this.isover?"isover":null;r&&(this.options.greedy&&(n=this.options.scope,o=this.element.parents(":data(ui-droppable)").filter(function(){return t(this).droppable("instance").options.scope===n}),o.length&&(s=t(o[0]).droppable("instance"),s.greedyChild="isover"===r)),s&&"isover"===r&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[r]=!0,this["isout"===r?"isover":"isout"]=!1,this["isover"===r?"_over":"_out"].call(this,i),s&&"isout"===r&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(e,i){e.element.parentsUntil("body").off("scroll.droppable"),e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)}},t.uiBackCompat!==!1&&t.widget("ui.droppable",t.ui.droppable,{options:{hoverClass:!1,activeClass:!1},_addActiveClass:function(){this._super(),this.options.activeClass&&this.element.addClass(this.options.activeClass)},_removeActiveClass:function(){this._super(),this.options.activeClass&&this.element.removeClass(this.options.activeClass)},_addHoverClass:function(){this._super(),this.options.hoverClass&&this.element.addClass(this.options.hoverClass)},_removeHoverClass:function(){this._super(),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass)}}),t.ui.droppable,t.widget("ui.progressbar",{version:"1.12.1",options:{classes:{"ui-progressbar":"ui-corner-all","ui-progressbar-value":"ui-corner-left","ui-progressbar-complete":"ui-corner-right"},max:100,value:0,change:null,complete:null},min:0,_create:function(){this.oldValue=this.options.value=this._constrainedValue(),this.element.attr({role:"progressbar","aria-valuemin":this.min}),this._addClass("ui-progressbar","ui-widget ui-widget-content"),this.valueDiv=t("<div>").appendTo(this.element),this._addClass(this.valueDiv,"ui-progressbar-value","ui-widget-header"),this._refreshValue()},_destroy:function(){this.element.removeAttr("role aria-valuemin aria-valuemax aria-valuenow"),this.valueDiv.remove()},value:function(t){return void 0===t?this.options.value:(this.options.value=this._constrainedValue(t),this._refreshValue(),void 0)},_constrainedValue:function(t){return void 0===t&&(t=this.options.value),this.indeterminate=t===!1,"number"!=typeof t&&(t=0),this.indeterminate?!1:Math.min(this.options.max,Math.max(this.min,t))},_setOptions:function(t){var e=t.value;delete t.value,this._super(t),this.options.value=this._constrainedValue(e),this._refreshValue()},_setOption:function(t,e){"max"===t&&(e=Math.max(this.min,e)),this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var e=this.options.value,i=this._percentage();this.valueDiv.toggle(this.indeterminate||e>this.min).width(i.toFixed(0)+"%"),this._toggleClass(this.valueDiv,"ui-progressbar-complete",null,e===this.options.max)._toggleClass("ui-progressbar-indeterminate",null,this.indeterminate),this.indeterminate?(this.element.removeAttr("aria-valuenow"),this.overlayDiv||(this.overlayDiv=t("<div>").appendTo(this.valueDiv),this._addClass(this.overlayDiv,"ui-progressbar-overlay"))):(this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":e}),this.overlayDiv&&(this.overlayDiv.remove(),this.overlayDiv=null)),this.oldValue!==e&&(this.oldValue=e,this._trigger("change")),e===this.options.max&&this._trigger("complete")}}),t.widget("ui.selectable",t.ui.mouse,{version:"1.12.1",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var e=this;this._addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){e.elementPos=t(e.element[0]).offset(),e.selectees=t(e.options.filter,e.element[0]),e._addClass(e.selectees,"ui-selectee"),e.selectees.each(function(){var i=t(this),s=i.offset(),n={left:s.left-e.elementPos.left,top:s.top-e.elementPos.top};t.data(this,"selectable-item",{element:this,$element:i,left:n.left,top:n.top,right:n.left+i.outerWidth(),bottom:n.top+i.outerHeight(),startselected:!1,selected:i.hasClass("ui-selected"),selecting:i.hasClass("ui-selecting"),unselecting:i.hasClass("ui-unselecting")})})},this.refresh(),this._mouseInit(),this.helper=t("<div>"),this._addClass(this.helper,"ui-selectable-helper")},_destroy:function(){this.selectees.removeData("selectable-item"),this._mouseDestroy()},_mouseStart:function(e){var i=this,s=this.options;this.opos=[e.pageX,e.pageY],this.elementPos=t(this.element[0]).offset(),this.options.disabled||(this.selectees=t(s.filter,this.element[0]),this._trigger("start",e),t(s.appendTo).append(this.helper),this.helper.css({left:e.pageX,top:e.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=t.data(this,"selectable-item");s.startselected=!0,e.metaKey||e.ctrlKey||(i._removeClass(s.$element,"ui-selected"),s.selected=!1,i._addClass(s.$element,"ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",e,{unselecting:s.element}))}),t(e.target).parents().addBack().each(function(){var s,n=t.data(this,"selectable-item");return n?(s=!e.metaKey&&!e.ctrlKey||!n.$element.hasClass("ui-selected"),i._removeClass(n.$element,s?"ui-unselecting":"ui-selected")._addClass(n.$element,s?"ui-selecting":"ui-unselecting"),n.unselecting=!s,n.selecting=s,n.selected=s,s?i._trigger("selecting",e,{selecting:n.element}):i._trigger("unselecting",e,{unselecting:n.element}),!1):void 0}))},_mouseDrag:function(e){if(this.dragged=!0,!this.options.disabled){var i,s=this,n=this.options,o=this.opos[0],a=this.opos[1],r=e.pageX,h=e.pageY;return o>r&&(i=r,r=o,o=i),a>h&&(i=h,h=a,a=i),this.helper.css({left:o,top:a,width:r-o,height:h-a}),this.selectees.each(function(){var i=t.data(this,"selectable-item"),l=!1,c={};i&&i.element!==s.element[0]&&(c.left=i.left+s.elementPos.left,c.right=i.right+s.elementPos.left,c.top=i.top+s.elementPos.top,c.bottom=i.bottom+s.elementPos.top,"touch"===n.tolerance?l=!(c.left>r||o>c.right||c.top>h||a>c.bottom):"fit"===n.tolerance&&(l=c.left>o&&r>c.right&&c.top>a&&h>c.bottom),l?(i.selected&&(s._removeClass(i.$element,"ui-selected"),i.selected=!1),i.unselecting&&(s._removeClass(i.$element,"ui-unselecting"),i.unselecting=!1),i.selecting||(s._addClass(i.$element,"ui-selecting"),i.selecting=!0,s._trigger("selecting",e,{selecting:i.element}))):(i.selecting&&((e.metaKey||e.ctrlKey)&&i.startselected?(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,s._addClass(i.$element,"ui-selected"),i.selected=!0):(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,i.startselected&&(s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",e,{unselecting:i.element}))),i.selected&&(e.metaKey||e.ctrlKey||i.startselected||(s._removeClass(i.$element,"ui-selected"),i.selected=!1,s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",e,{unselecting:i.element})))))}),!1}},_mouseStop:function(e){var i=this;return this.dragged=!1,t(".ui-unselecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",e,{unselected:s.element})}),t(".ui-selecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-selecting")._addClass(s.$element,"ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",e,{selected:s.element})}),this._trigger("stop",e),this.helper.remove(),!1}}),t.widget("ui.selectmenu",[t.ui.formResetMixin,{version:"1.12.1",defaultElement:"<select>",options:{appendTo:null,classes:{"ui-selectmenu-button-open":"ui-corner-top","ui-selectmenu-button-closed":"ui-corner-all"},disabled:null,icons:{button:"ui-icon-triangle-1-s"},position:{my:"left top",at:"left bottom",collision:"none"},width:!1,change:null,close:null,focus:null,open:null,select:null},_create:function(){var e=this.element.uniqueId().attr("id");this.ids={element:e,button:e+"-button",menu:e+"-menu"},this._drawButton(),this._drawMenu(),this._bindFormResetHandler(),this._rendered=!1,this.menuItems=t()},_drawButton:function(){var e,i=this,s=this._parseOption(this.element.find("option:selected"),this.element[0].selectedIndex);this.labels=this.element.labels().attr("for",this.ids.button),this._on(this.labels,{click:function(t){this.button.focus(),t.preventDefault()}}),this.element.hide(),this.button=t("<span>",{tabindex:this.options.disabled?-1:0,id:this.ids.button,role:"combobox","aria-expanded":"false","aria-autocomplete":"list","aria-owns":this.ids.menu,"aria-haspopup":"true",title:this.element.attr("title")}).insertAfter(this.element),this._addClass(this.button,"ui-selectmenu-button ui-selectmenu-button-closed","ui-button ui-widget"),e=t("<span>").appendTo(this.button),this._addClass(e,"ui-selectmenu-icon","ui-icon "+this.options.icons.button),this.buttonItem=this._renderButtonItem(s).appendTo(this.button),this.options.width!==!1&&this._resizeButton(),this._on(this.button,this._buttonEvents),this.button.one("focusin",function(){i._rendered||i._refreshMenu()})},_drawMenu:function(){var e=this;this.menu=t("<ul>",{"aria-hidden":"true","aria-labelledby":this.ids.button,id:this.ids.menu}),this.menuWrap=t("<div>").append(this.menu),this._addClass(this.menuWrap,"ui-selectmenu-menu","ui-front"),this.menuWrap.appendTo(this._appendTo()),this.menuInstance=this.menu.menu({classes:{"ui-menu":"ui-corner-bottom"},role:"listbox",select:function(t,i){t.preventDefault(),e._setSelection(),e._select(i.item.data("ui-selectmenu-item"),t)},focus:function(t,i){var s=i.item.data("ui-selectmenu-item");null!=e.focusIndex&&s.index!==e.focusIndex&&(e._trigger("focus",t,{item:s}),e.isOpen||e._select(s,t)),e.focusIndex=s.index,e.button.attr("aria-activedescendant",e.menuItems.eq(s.index).attr("id"))}}).menu("instance"),this.menuInstance._off(this.menu,"mouseleave"),this.menuInstance._closeOnDocumentClick=function(){return!1},this.menuInstance._isDivider=function(){return!1}},refresh:function(){this._refreshMenu(),this.buttonItem.replaceWith(this.buttonItem=this._renderButtonItem(this._getSelectedItem().data("ui-selectmenu-item")||{})),null===this.options.width&&this._resizeButton()},_refreshMenu:function(){var t,e=this.element.find("option");this.menu.empty(),this._parseOptions(e),this._renderMenu(this.menu,this.items),this.menuInstance.refresh(),this.menuItems=this.menu.find("li").not(".ui-selectmenu-optgroup").find(".ui-menu-item-wrapper"),this._rendered=!0,e.length&&(t=this._getSelectedItem(),this.menuInstance.focus(null,t),this._setAria(t.data("ui-selectmenu-item")),this._setOption("disabled",this.element.prop("disabled")))},open:function(t){this.options.disabled||(this._rendered?(this._removeClass(this.menu.find(".ui-state-active"),null,"ui-state-active"),this.menuInstance.focus(null,this._getSelectedItem())):this._refreshMenu(),this.menuItems.length&&(this.isOpen=!0,this._toggleAttr(),this._resizeMenu(),this._position(),this._on(this.document,this._documentClick),this._trigger("open",t)))},_position:function(){this.menuWrap.position(t.extend({of:this.button},this.options.position))},close:function(t){this.isOpen&&(this.isOpen=!1,this._toggleAttr(),this.range=null,this._off(this.document),this._trigger("close",t))},widget:function(){return this.button},menuWidget:function(){return this.menu},_renderButtonItem:function(e){var i=t("<span>");return this._setText(i,e.label),this._addClass(i,"ui-selectmenu-text"),i},_renderMenu:function(e,i){var s=this,n="";t.each(i,function(i,o){var a;o.optgroup!==n&&(a=t("<li>",{text:o.optgroup}),s._addClass(a,"ui-selectmenu-optgroup","ui-menu-divider"+(o.element.parent("optgroup").prop("disabled")?" ui-state-disabled":"")),a.appendTo(e),n=o.optgroup),s._renderItemData(e,o)})},_renderItemData:function(t,e){return this._renderItem(t,e).data("ui-selectmenu-item",e)},_renderItem:function(e,i){var s=t("<li>"),n=t("<div>",{title:i.element.attr("title")});return i.disabled&&this._addClass(s,null,"ui-state-disabled"),this._setText(n,i.label),s.append(n).appendTo(e)},_setText:function(t,e){e?t.text(e):t.html("&#160;")},_move:function(t,e){var i,s,n=".ui-menu-item";this.isOpen?i=this.menuItems.eq(this.focusIndex).parent("li"):(i=this.menuItems.eq(this.element[0].selectedIndex).parent("li"),n+=":not(.ui-state-disabled)"),s="first"===t||"last"===t?i["first"===t?"prevAll":"nextAll"](n).eq(-1):i[t+"All"](n).eq(0),s.length&&this.menuInstance.focus(e,s)},_getSelectedItem:function(){return this.menuItems.eq(this.element[0].selectedIndex).parent("li")},_toggle:function(t){this[this.isOpen?"close":"open"](t)},_setSelection:function(){var t;this.range&&(window.getSelection?(t=window.getSelection(),t.removeAllRanges(),t.addRange(this.range)):this.range.select(),this.button.focus())},_documentClick:{mousedown:function(e){this.isOpen&&(t(e.target).closest(".ui-selectmenu-menu, #"+t.ui.escapeSelector(this.ids.button)).length||this.close(e))}},_buttonEvents:{mousedown:function(){var t;window.getSelection?(t=window.getSelection(),t.rangeCount&&(this.range=t.getRangeAt(0))):this.range=document.selection.createRange()},click:function(t){this._setSelection(),this._toggle(t)},keydown:function(e){var i=!0;switch(e.keyCode){case t.ui.keyCode.TAB:case t.ui.keyCode.ESCAPE:this.close(e),i=!1;break;case t.ui.keyCode.ENTER:this.isOpen&&this._selectFocusedItem(e);break;case t.ui.keyCode.UP:e.altKey?this._toggle(e):this._move("prev",e);break;case t.ui.keyCode.DOWN:e.altKey?this._toggle(e):this._move("next",e);break;case t.ui.keyCode.SPACE:this.isOpen?this._selectFocusedItem(e):this._toggle(e);break;case t.ui.keyCode.LEFT:this._move("prev",e);break;case t.ui.keyCode.RIGHT:this._move("next",e);break;case t.ui.keyCode.HOME:case t.ui.keyCode.PAGE_UP:this._move("first",e);break;case t.ui.keyCode.END:case t.ui.keyCode.PAGE_DOWN:this._move("last",e);break;default:this.menu.trigger(e),i=!1}i&&e.preventDefault()}},_selectFocusedItem:function(t){var e=this.menuItems.eq(this.focusIndex).parent("li");e.hasClass("ui-state-disabled")||this._select(e.data("ui-selectmenu-item"),t)},_select:function(t,e){var i=this.element[0].selectedIndex;this.element[0].selectedIndex=t.index,this.buttonItem.replaceWith(this.buttonItem=this._renderButtonItem(t)),this._setAria(t),this._trigger("select",e,{item:t}),t.index!==i&&this._trigger("change",e,{item:t}),this.close(e)},_setAria:function(t){var e=this.menuItems.eq(t.index).attr("id");this.button.attr({"aria-labelledby":e,"aria-activedescendant":e}),this.menu.attr("aria-activedescendant",e)},_setOption:function(t,e){if("icons"===t){var i=this.button.find("span.ui-icon");this._removeClass(i,null,this.options.icons.button)._addClass(i,null,e.button)}this._super(t,e),"appendTo"===t&&this.menuWrap.appendTo(this._appendTo()),"width"===t&&this._resizeButton()},_setOptionDisabled:function(t){this._super(t),this.menuInstance.option("disabled",t),this.button.attr("aria-disabled",t),this._toggleClass(this.button,null,"ui-state-disabled",t),this.element.prop("disabled",t),t?(this.button.attr("tabindex",-1),this.close()):this.button.attr("tabindex",0)},_appendTo:function(){var e=this.options.appendTo;return e&&(e=e.jquery||e.nodeType?t(e):this.document.find(e).eq(0)),e&&e[0]||(e=this.element.closest(".ui-front, dialog")),e.length||(e=this.document[0].body),e},_toggleAttr:function(){this.button.attr("aria-expanded",this.isOpen),this._removeClass(this.button,"ui-selectmenu-button-"+(this.isOpen?"closed":"open"))._addClass(this.button,"ui-selectmenu-button-"+(this.isOpen?"open":"closed"))._toggleClass(this.menuWrap,"ui-selectmenu-open",null,this.isOpen),this.menu.attr("aria-hidden",!this.isOpen)},_resizeButton:function(){var t=this.options.width;return t===!1?(this.button.css("width",""),void 0):(null===t&&(t=this.element.show().outerWidth(),this.element.hide()),this.button.outerWidth(t),void 0)},_resizeMenu:function(){this.menu.outerWidth(Math.max(this.button.outerWidth(),this.menu.width("").outerWidth()+1))},_getCreateOptions:function(){var t=this._super();return t.disabled=this.element.prop("disabled"),t},_parseOptions:function(e){var i=this,s=[];e.each(function(e,n){s.push(i._parseOption(t(n),e))}),this.items=s},_parseOption:function(t,e){var i=t.parent("optgroup");return{element:t,index:e,value:t.val(),label:t.text(),optgroup:i.attr("label")||"",disabled:i.prop("disabled")||t.prop("disabled")}},_destroy:function(){this._unbindFormResetHandler(),this.menuWrap.remove(),this.button.remove(),this.element.show(),this.element.removeUniqueId(),this.labels.attr("for",this.ids.element)}}]),t.widget("ui.slider",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"slide",options:{animate:!1,classes:{"ui-slider":"ui-corner-all","ui-slider-handle":"ui-corner-all","ui-slider-range":"ui-corner-all ui-widget-header"},distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null,change:null,slide:null,start:null,stop:null},numPages:5,_create:function(){this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this._calculateNewMax(),this._addClass("ui-slider ui-slider-"+this.orientation,"ui-widget ui-widget-content"),this._refresh(),this._animateOff=!1
+},_refresh:function(){this._createRange(),this._createHandles(),this._setupEvents(),this._refreshValue()},_createHandles:function(){var e,i,s=this.options,n=this.element.find(".ui-slider-handle"),o="<span tabindex='0'></span>",a=[];for(i=s.values&&s.values.length||1,n.length>i&&(n.slice(i).remove(),n=n.slice(0,i)),e=n.length;i>e;e++)a.push(o);this.handles=n.add(t(a.join("")).appendTo(this.element)),this._addClass(this.handles,"ui-slider-handle","ui-state-default"),this.handle=this.handles.eq(0),this.handles.each(function(e){t(this).data("ui-slider-handle-index",e).attr("tabIndex",0)})},_createRange:function(){var e=this.options;e.range?(e.range===!0&&(e.values?e.values.length&&2!==e.values.length?e.values=[e.values[0],e.values[0]]:t.isArray(e.values)&&(e.values=e.values.slice(0)):e.values=[this._valueMin(),this._valueMin()]),this.range&&this.range.length?(this._removeClass(this.range,"ui-slider-range-min ui-slider-range-max"),this.range.css({left:"",bottom:""})):(this.range=t("<div>").appendTo(this.element),this._addClass(this.range,"ui-slider-range")),("min"===e.range||"max"===e.range)&&this._addClass(this.range,"ui-slider-range-"+e.range)):(this.range&&this.range.remove(),this.range=null)},_setupEvents:function(){this._off(this.handles),this._on(this.handles,this._handleEvents),this._hoverable(this.handles),this._focusable(this.handles)},_destroy:function(){this.handles.remove(),this.range&&this.range.remove(),this._mouseDestroy()},_mouseCapture:function(e){var i,s,n,o,a,r,h,l,c=this,u=this.options;return u.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),i={x:e.pageX,y:e.pageY},s=this._normValueFromMouse(i),n=this._valueMax()-this._valueMin()+1,this.handles.each(function(e){var i=Math.abs(s-c.values(e));(n>i||n===i&&(e===c._lastChangedValue||c.values(e)===u.min))&&(n=i,o=t(this),a=e)}),r=this._start(e,a),r===!1?!1:(this._mouseSliding=!0,this._handleIndex=a,this._addClass(o,null,"ui-state-active"),o.trigger("focus"),h=o.offset(),l=!t(e.target).parents().addBack().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:e.pageX-h.left-o.width()/2,top:e.pageY-h.top-o.height()/2-(parseInt(o.css("borderTopWidth"),10)||0)-(parseInt(o.css("borderBottomWidth"),10)||0)+(parseInt(o.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(e,a,s),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(t){var e={x:t.pageX,y:t.pageY},i=this._normValueFromMouse(e);return this._slide(t,this._handleIndex,i),!1},_mouseStop:function(t){return this._removeClass(this.handles,null,"ui-state-active"),this._mouseSliding=!1,this._stop(t,this._handleIndex),this._change(t,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation="vertical"===this.options.orientation?"vertical":"horizontal"},_normValueFromMouse:function(t){var e,i,s,n,o;return"horizontal"===this.orientation?(e=this.elementSize.width,i=t.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(e=this.elementSize.height,i=t.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),s=i/e,s>1&&(s=1),0>s&&(s=0),"vertical"===this.orientation&&(s=1-s),n=this._valueMax()-this._valueMin(),o=this._valueMin()+s*n,this._trimAlignValue(o)},_uiHash:function(t,e,i){var s={handle:this.handles[t],handleIndex:t,value:void 0!==e?e:this.value()};return this._hasMultipleValues()&&(s.value=void 0!==e?e:this.values(t),s.values=i||this.values()),s},_hasMultipleValues:function(){return this.options.values&&this.options.values.length},_start:function(t,e){return this._trigger("start",t,this._uiHash(e))},_slide:function(t,e,i){var s,n,o=this.value(),a=this.values();this._hasMultipleValues()&&(n=this.values(e?0:1),o=this.values(e),2===this.options.values.length&&this.options.range===!0&&(i=0===e?Math.min(n,i):Math.max(n,i)),a[e]=i),i!==o&&(s=this._trigger("slide",t,this._uiHash(e,i,a)),s!==!1&&(this._hasMultipleValues()?this.values(e,i):this.value(i)))},_stop:function(t,e){this._trigger("stop",t,this._uiHash(e))},_change:function(t,e){this._keySliding||this._mouseSliding||(this._lastChangedValue=e,this._trigger("change",t,this._uiHash(e)))},value:function(t){return arguments.length?(this.options.value=this._trimAlignValue(t),this._refreshValue(),this._change(null,0),void 0):this._value()},values:function(e,i){var s,n,o;if(arguments.length>1)return this.options.values[e]=this._trimAlignValue(i),this._refreshValue(),this._change(null,e),void 0;if(!arguments.length)return this._values();if(!t.isArray(arguments[0]))return this._hasMultipleValues()?this._values(e):this.value();for(s=this.options.values,n=arguments[0],o=0;s.length>o;o+=1)s[o]=this._trimAlignValue(n[o]),this._change(null,o);this._refreshValue()},_setOption:function(e,i){var s,n=0;switch("range"===e&&this.options.range===!0&&("min"===i?(this.options.value=this._values(0),this.options.values=null):"max"===i&&(this.options.value=this._values(this.options.values.length-1),this.options.values=null)),t.isArray(this.options.values)&&(n=this.options.values.length),this._super(e,i),e){case"orientation":this._detectOrientation(),this._removeClass("ui-slider-horizontal ui-slider-vertical")._addClass("ui-slider-"+this.orientation),this._refreshValue(),this.options.range&&this._refreshRange(i),this.handles.css("horizontal"===i?"bottom":"left","");break;case"value":this._animateOff=!0,this._refreshValue(),this._change(null,0),this._animateOff=!1;break;case"values":for(this._animateOff=!0,this._refreshValue(),s=n-1;s>=0;s--)this._change(null,s);this._animateOff=!1;break;case"step":case"min":case"max":this._animateOff=!0,this._calculateNewMax(),this._refreshValue(),this._animateOff=!1;break;case"range":this._animateOff=!0,this._refresh(),this._animateOff=!1}},_setOptionDisabled:function(t){this._super(t),this._toggleClass(null,"ui-state-disabled",!!t)},_value:function(){var t=this.options.value;return t=this._trimAlignValue(t)},_values:function(t){var e,i,s;if(arguments.length)return e=this.options.values[t],e=this._trimAlignValue(e);if(this._hasMultipleValues()){for(i=this.options.values.slice(),s=0;i.length>s;s+=1)i[s]=this._trimAlignValue(i[s]);return i}return[]},_trimAlignValue:function(t){if(this._valueMin()>=t)return this._valueMin();if(t>=this._valueMax())return this._valueMax();var e=this.options.step>0?this.options.step:1,i=(t-this._valueMin())%e,s=t-i;return 2*Math.abs(i)>=e&&(s+=i>0?e:-e),parseFloat(s.toFixed(5))},_calculateNewMax:function(){var t=this.options.max,e=this._valueMin(),i=this.options.step,s=Math.round((t-e)/i)*i;t=s+e,t>this.options.max&&(t-=i),this.max=parseFloat(t.toFixed(this._precision()))},_precision:function(){var t=this._precisionOf(this.options.step);return null!==this.options.min&&(t=Math.max(t,this._precisionOf(this.options.min))),t},_precisionOf:function(t){var e=""+t,i=e.indexOf(".");return-1===i?0:e.length-i-1},_valueMin:function(){return this.options.min},_valueMax:function(){return this.max},_refreshRange:function(t){"vertical"===t&&this.range.css({width:"",left:""}),"horizontal"===t&&this.range.css({height:"",bottom:""})},_refreshValue:function(){var e,i,s,n,o,a=this.options.range,r=this.options,h=this,l=this._animateOff?!1:r.animate,c={};this._hasMultipleValues()?this.handles.each(function(s){i=100*((h.values(s)-h._valueMin())/(h._valueMax()-h._valueMin())),c["horizontal"===h.orientation?"left":"bottom"]=i+"%",t(this).stop(1,1)[l?"animate":"css"](c,r.animate),h.options.range===!0&&("horizontal"===h.orientation?(0===s&&h.range.stop(1,1)[l?"animate":"css"]({left:i+"%"},r.animate),1===s&&h.range[l?"animate":"css"]({width:i-e+"%"},{queue:!1,duration:r.animate})):(0===s&&h.range.stop(1,1)[l?"animate":"css"]({bottom:i+"%"},r.animate),1===s&&h.range[l?"animate":"css"]({height:i-e+"%"},{queue:!1,duration:r.animate}))),e=i}):(s=this.value(),n=this._valueMin(),o=this._valueMax(),i=o!==n?100*((s-n)/(o-n)):0,c["horizontal"===this.orientation?"left":"bottom"]=i+"%",this.handle.stop(1,1)[l?"animate":"css"](c,r.animate),"min"===a&&"horizontal"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({width:i+"%"},r.animate),"max"===a&&"horizontal"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({width:100-i+"%"},r.animate),"min"===a&&"vertical"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({height:i+"%"},r.animate),"max"===a&&"vertical"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({height:100-i+"%"},r.animate))},_handleEvents:{keydown:function(e){var i,s,n,o,a=t(e.target).data("ui-slider-handle-index");switch(e.keyCode){case t.ui.keyCode.HOME:case t.ui.keyCode.END:case t.ui.keyCode.PAGE_UP:case t.ui.keyCode.PAGE_DOWN:case t.ui.keyCode.UP:case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:case t.ui.keyCode.LEFT:if(e.preventDefault(),!this._keySliding&&(this._keySliding=!0,this._addClass(t(e.target),null,"ui-state-active"),i=this._start(e,a),i===!1))return}switch(o=this.options.step,s=n=this._hasMultipleValues()?this.values(a):this.value(),e.keyCode){case t.ui.keyCode.HOME:n=this._valueMin();break;case t.ui.keyCode.END:n=this._valueMax();break;case t.ui.keyCode.PAGE_UP:n=this._trimAlignValue(s+(this._valueMax()-this._valueMin())/this.numPages);break;case t.ui.keyCode.PAGE_DOWN:n=this._trimAlignValue(s-(this._valueMax()-this._valueMin())/this.numPages);break;case t.ui.keyCode.UP:case t.ui.keyCode.RIGHT:if(s===this._valueMax())return;n=this._trimAlignValue(s+o);break;case t.ui.keyCode.DOWN:case t.ui.keyCode.LEFT:if(s===this._valueMin())return;n=this._trimAlignValue(s-o)}this._slide(e,a,n)},keyup:function(e){var i=t(e.target).data("ui-slider-handle-index");this._keySliding&&(this._keySliding=!1,this._stop(e,i),this._change(e,i),this._removeClass(t(e.target),null,"ui-state-active"))}}}),t.widget("ui.sortable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(t,e,i){return t>=e&&e+i>t},_isFloating:function(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))},_create:function(){this.containerCache={},this._addClass("ui-sortable"),this.refresh(),this.offset=this.element.offset(),this._mouseInit(),this._setHandleClassName(),this.ready=!0},_setOption:function(t,e){this._super(t,e),"handle"===t&&this._setHandleClassName()},_setHandleClassName:function(){var e=this;this._removeClass(this.element.find(".ui-sortable-handle"),"ui-sortable-handle"),t.each(this.items,function(){e._addClass(this.instance.options.handle?this.item.find(this.instance.options.handle):this.item,"ui-sortable-handle")})},_destroy:function(){this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):void 0}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("<style>*{ cursor: "+a.cursor+" !important; }</style>").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this._addClass(this.helper,"ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY<a.scrollSensitivity?this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop+a.scrollSpeed:e.pageY-this.overflowOffset.top<a.scrollSensitivity&&(this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop-a.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-e.pageX<a.scrollSensitivity?this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft+a.scrollSpeed:e.pageX-this.overflowOffset.left<a.scrollSensitivity&&(this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft-a.scrollSpeed)):(e.pageY-this.document.scrollTop()<a.scrollSensitivity?r=this.document.scrollTop(this.document.scrollTop()-a.scrollSpeed):this.window.height()-(e.pageY-this.document.scrollTop())<a.scrollSensitivity&&(r=this.document.scrollTop(this.document.scrollTop()+a.scrollSpeed)),e.pageX-this.document.scrollLeft()<a.scrollSensitivity?r=this.document.scrollLeft(this.document.scrollLeft()-a.scrollSpeed):this.window.width()-(e.pageX-this.document.scrollLeft())<a.scrollSensitivity&&(r=this.document.scrollLeft(this.document.scrollLeft()+a.scrollSpeed))),r!==!1&&t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e)),this.positionAbs=this._convertPositionTo("absolute"),this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),i=this.items.length-1;i>=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp(new t.Event("mouseup",{target:null})),"original"===this.options.helper?(this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,h=r+t.height,l=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+l>r&&h>s+l,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var e,i,s="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top,t.height),n="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left,t.width),o=s&&n;return o?(e=this._getDragVerticalDirection(),i=this._getDragHorizontalDirection(),this.floating?"right"===i||"down"===e?2:1:e&&("down"===e?2:1)):!1},_intersectsWithSides:function(t){var e=this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),s=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&i||"left"===n&&!i:s&&("down"===s&&e||"up"===s&&!e)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){r.push(this)}var s,n,o,a,r=[],h=[],l=this._connectWith();if(l&&e)for(s=l.length-1;s>=0;s--)for(o=t(l[s],this.document[0]),n=o.length-1;n>=0;n--)a=t.data(o[n],this.widgetFullName),a&&a!==this&&!a.options.disabled&&h.push([t.isFunction(a.options.items)?a.options.items.call(a.element):t(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a]);for(h.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return t(r)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,h,l,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i],this.document[0]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,l=r.length;l>s;s++)h=t(r[s]),h.data(this.widgetName+"-item",a),c.push({item:h,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.floating=this.items.length?"x"===this.options.axis||this._isFloating(this.items[0].item):!1,this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]);return e._addClass(n,"ui-sortable-placeholder",i||e.currentItem[0].className)._removeClass(n,"ui-sortable-helper"),"tbody"===s?e._createTrPlaceholder(e.currentItem.find("tr").eq(0),t("<tr>",e.document[0]).appendTo(n)):"tr"===s?e._createTrPlaceholder(e.currentItem,n):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_createTrPlaceholder:function(e,i){var s=this;e.children().each(function(){t("<td>&#160;</td>",s.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(e){var i,s,n,o,a,r,h,l,c,u,d=null,p=null;for(i=this.containers.length-1;i>=0;i--)if(!t.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(d&&t.contains(this.containers[i].element[0],d.element[0]))continue;d=this.containers[i],p=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",e,this._uiHash(this)),this.containers[i].containerCache.over=0);if(d)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1);else{for(n=1e4,o=null,c=d.floating||this._isFloating(this.currentItem),a=c?"left":"top",r=c?"width":"height",u=c?"pageX":"pageY",s=this.items.length-1;s>=0;s--)t.contains(this.containers[p].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(h=this.items[s].item.offset()[a],l=!1,e[u]-h>this.items[s][r]/2&&(l=!0),n>Math.abs(e[u]-h)&&(n=Math.abs(e[u]-h),o=this.items[s],this.direction=l?"up":"down"));if(!o&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[p])return this.currentContainer.containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash()),this.currentContainer.containerCache.over=1),void 0;o?this._rearrange(e,o,null,!0):this._rearrange(e,null,this.containers[p].element,!0),this._trigger("change",e,this._uiHash()),this.containers[p]._trigger("change",e,this._uiHash(this)),this.currentContainer=this.containers[p],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===n.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===n.containment?this.document.height()||document.body.parentNode.scrollHeight:this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.left<this.containment[0]&&(o=this.containment[0]+this.offset.click.left),e.pageY-this.offset.click.top<this.containment[1]&&(a=this.containment[1]+this.offset.click.top),e.pageX-this.offset.click.left>this.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;
+this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}}),t.widget("ui.spinner",{version:"1.12.1",defaultElement:"<input>",widgetEventPrefix:"spin",options:{classes:{"ui-spinner":"ui-corner-all","ui-spinner-down":"ui-corner-br","ui-spinner-up":"ui-corner-tr"},culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var e=this._super(),i=this.element;return t.each(["min","max","step"],function(t,s){var n=i.attr(s);null!=n&&n.length&&(e[s]=n)}),e},_events:{keydown:function(t){this._start(t)&&this._keydown(t)&&t.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",t),void 0)},mousewheel:function(t,e){if(e){if(!this.spinning&&!this._start(t))return!1;this._spin((e>0?1:-1)*this.options.step,t),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(t)},100),t.preventDefault()}},"mousedown .ui-spinner-button":function(e){function i(){var e=this.element[0]===t.ui.safeActiveElement(this.document[0]);e||(this.element.trigger("focus"),this.previous=s,this._delay(function(){this.previous=s}))}var s;s=this.element[0]===t.ui.safeActiveElement(this.document[0])?this.previous:this.element.val(),e.preventDefault(),i.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,i.call(this)}),this._start(e)!==!1&&this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(e){return t(e.currentTarget).hasClass("ui-state-active")?this._start(e)===!1?!1:(this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e),void 0):void 0},"mouseleave .ui-spinner-button":"_stop"},_enhance:function(){this.uiSpinner=this.element.attr("autocomplete","off").wrap("<span>").parent().append("<a></a><a></a>")},_draw:function(){this._enhance(),this._addClass(this.uiSpinner,"ui-spinner","ui-widget ui-widget-content"),this._addClass("ui-spinner-input"),this.element.attr("role","spinbutton"),this.buttons=this.uiSpinner.children("a").attr("tabIndex",-1).attr("aria-hidden",!0).button({classes:{"ui-button":""}}),this._removeClass(this.buttons,"ui-corner-all"),this._addClass(this.buttons.first(),"ui-spinner-button ui-spinner-up"),this._addClass(this.buttons.last(),"ui-spinner-button ui-spinner-down"),this.buttons.first().button({icon:this.options.icons.up,showLabel:!1}),this.buttons.last().button({icon:this.options.icons.down,showLabel:!1}),this.buttons.height()>Math.ceil(.5*this.uiSpinner.height())&&this.uiSpinner.height()>0&&this.uiSpinner.height(this.uiSpinner.height())},_keydown:function(e){var i=this.options,s=t.ui.keyCode;switch(e.keyCode){case s.UP:return this._repeat(null,1,e),!0;case s.DOWN:return this._repeat(null,-1,e),!0;case s.PAGE_UP:return this._repeat(null,i.page,e),!0;case s.PAGE_DOWN:return this._repeat(null,-i.page,e),!0}return!1},_start:function(t){return this.spinning||this._trigger("start",t)!==!1?(this.counter||(this.counter=1),this.spinning=!0,!0):!1},_repeat:function(t,e,i){t=t||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,e,i)},t),this._spin(e*this.options.step,i)},_spin:function(t,e){var i=this.value()||0;this.counter||(this.counter=1),i=this._adjustValue(i+t*this._increment(this.counter)),this.spinning&&this._trigger("spin",e,{value:i})===!1||(this._value(i),this.counter++)},_increment:function(e){var i=this.options.incremental;return i?t.isFunction(i)?i(e):Math.floor(e*e*e/5e4-e*e/500+17*e/200+1):1},_precision:function(){var t=this._precisionOf(this.options.step);return null!==this.options.min&&(t=Math.max(t,this._precisionOf(this.options.min))),t},_precisionOf:function(t){var e=""+t,i=e.indexOf(".");return-1===i?0:e.length-i-1},_adjustValue:function(t){var e,i,s=this.options;return e=null!==s.min?s.min:0,i=t-e,i=Math.round(i/s.step)*s.step,t=e+i,t=parseFloat(t.toFixed(this._precision())),null!==s.max&&t>s.max?s.max:null!==s.min&&s.min>t?s.min:t},_stop:function(t){this.spinning&&(clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",t))},_setOption:function(t,e){var i,s,n;return"culture"===t||"numberFormat"===t?(i=this._parse(this.element.val()),this.options[t]=e,this.element.val(this._format(i)),void 0):(("max"===t||"min"===t||"step"===t)&&"string"==typeof e&&(e=this._parse(e)),"icons"===t&&(s=this.buttons.first().find(".ui-icon"),this._removeClass(s,null,this.options.icons.up),this._addClass(s,null,e.up),n=this.buttons.last().find(".ui-icon"),this._removeClass(n,null,this.options.icons.down),this._addClass(n,null,e.down)),this._super(t,e),void 0)},_setOptionDisabled:function(t){this._super(t),this._toggleClass(this.uiSpinner,null,"ui-state-disabled",!!t),this.element.prop("disabled",!!t),this.buttons.button(t?"disable":"enable")},_setOptions:r(function(t){this._super(t)}),_parse:function(t){return"string"==typeof t&&""!==t&&(t=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(t,10,this.options.culture):+t),""===t||isNaN(t)?null:t},_format:function(t){return""===t?"":window.Globalize&&this.options.numberFormat?Globalize.format(t,this.options.numberFormat,this.options.culture):t},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},isValid:function(){var t=this.value();return null===t?!1:t===this._adjustValue(t)},_value:function(t,e){var i;""!==t&&(i=this._parse(t),null!==i&&(e||(i=this._adjustValue(i)),t=this._format(i))),this.element.val(t),this._refresh()},_destroy:function(){this.element.prop("disabled",!1).removeAttr("autocomplete role aria-valuemin aria-valuemax aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:r(function(t){this._stepUp(t)}),_stepUp:function(t){this._start()&&(this._spin((t||1)*this.options.step),this._stop())},stepDown:r(function(t){this._stepDown(t)}),_stepDown:function(t){this._start()&&(this._spin((t||1)*-this.options.step),this._stop())},pageUp:r(function(t){this._stepUp((t||1)*this.options.page)}),pageDown:r(function(t){this._stepDown((t||1)*this.options.page)}),value:function(t){return arguments.length?(r(this._value).call(this,t),void 0):this._parse(this.element.val())},widget:function(){return this.uiSpinner}}),t.uiBackCompat!==!1&&t.widget("ui.spinner",t.ui.spinner,{_enhance:function(){this.uiSpinner=this.element.attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml())},_uiSpinnerHtml:function(){return"<span>"},_buttonHtml:function(){return"<a></a><a></a>"}}),t.ui.spinner,t.widget("ui.tabs",{version:"1.12.1",delay:300,options:{active:null,classes:{"ui-tabs":"ui-corner-all","ui-tabs-nav":"ui-corner-all","ui-tabs-panel":"ui-corner-bottom","ui-tabs-tab":"ui-corner-top"},collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_isLocal:function(){var t=/#.*$/;return function(e){var i,s;i=e.href.replace(t,""),s=location.href.replace(t,"");try{i=decodeURIComponent(i)}catch(n){}try{s=decodeURIComponent(s)}catch(n){}return e.hash.length>1&&i===s}}(),_create:function(){var e=this,i=this.options;this.running=!1,this._addClass("ui-tabs","ui-widget ui-widget-content"),this._toggleClass("ui-tabs-collapsible",null,i.collapsible),this._processTabs(),i.active=this._initialActive(),t.isArray(i.disabled)&&(i.disabled=t.unique(i.disabled.concat(t.map(this.tabs.filter(".ui-state-disabled"),function(t){return e.tabs.index(t)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(i.active):t(),this._refresh(),this.active.length&&this.load(i.active)},_initialActive:function(){var e=this.options.active,i=this.options.collapsible,s=location.hash.substring(1);return null===e&&(s&&this.tabs.each(function(i,n){return t(n).attr("aria-controls")===s?(e=i,!1):void 0}),null===e&&(e=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===e||-1===e)&&(e=this.tabs.length?0:!1)),e!==!1&&(e=this.tabs.index(this.tabs.eq(e)),-1===e&&(e=i?!1:0)),!i&&e===!1&&this.anchors.length&&(e=0),e},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):t()}},_tabKeydown:function(e){var i=t(t.ui.safeActiveElement(this.document[0])).closest("li"),s=this.tabs.index(i),n=!0;if(!this._handlePageNav(e)){switch(e.keyCode){case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:s++;break;case t.ui.keyCode.UP:case t.ui.keyCode.LEFT:n=!1,s--;break;case t.ui.keyCode.END:s=this.anchors.length-1;break;case t.ui.keyCode.HOME:s=0;break;case t.ui.keyCode.SPACE:return e.preventDefault(),clearTimeout(this.activating),this._activate(s),void 0;case t.ui.keyCode.ENTER:return e.preventDefault(),clearTimeout(this.activating),this._activate(s===this.options.active?!1:s),void 0;default:return}e.preventDefault(),clearTimeout(this.activating),s=this._focusNextTab(s,n),e.ctrlKey||e.metaKey||(i.attr("aria-selected","false"),this.tabs.eq(s).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",s)},this.delay))}},_panelKeydown:function(e){this._handlePageNav(e)||e.ctrlKey&&e.keyCode===t.ui.keyCode.UP&&(e.preventDefault(),this.active.trigger("focus"))},_handlePageNav:function(e){return e.altKey&&e.keyCode===t.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):e.altKey&&e.keyCode===t.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):void 0},_findNextTab:function(e,i){function s(){return e>n&&(e=0),0>e&&(e=n),e}for(var n=this.tabs.length-1;-1!==t.inArray(s(),this.options.disabled);)e=i?e+1:e-1;return e},_focusNextTab:function(t,e){return t=this._findNextTab(t,e),this.tabs.eq(t).trigger("focus"),t},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):(this._super(t,e),"collapsible"===t&&(this._toggleClass("ui-tabs-collapsible",null,e),e||this.options.active!==!1||this._activate(0)),"event"===t&&this._setupEvents(e),"heightStyle"===t&&this._setupHeightStyle(e),void 0)},_sanitizeSelector:function(t){return t?t.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var e=this.options,i=this.tablist.children(":has(a[href])");e.disabled=t.map(i.filter(".ui-state-disabled"),function(t){return i.index(t)}),this._processTabs(),e.active!==!1&&this.anchors.length?this.active.length&&!t.contains(this.tablist[0],this.active[0])?this.tabs.length===e.disabled.length?(e.active=!1,this.active=t()):this._activate(this._findNextTab(Math.max(0,e.active-1),!1)):e.active=this.tabs.index(this.active):(e.active=!1,this.active=t()),this._refresh()},_refresh:function(){this._setOptionDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-hidden":"true"}),this.active.length?(this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}),this._addClass(this.active,"ui-tabs-active","ui-state-active"),this._getPanelForTab(this.active).show().attr({"aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var e=this,i=this.tabs,s=this.anchors,n=this.panels;this.tablist=this._getList().attr("role","tablist"),this._addClass(this.tablist,"ui-tabs-nav","ui-helper-reset ui-helper-clearfix ui-widget-header"),this.tablist.on("mousedown"+this.eventNamespace,"> li",function(e){t(this).is(".ui-state-disabled")&&e.preventDefault()}).on("focus"+this.eventNamespace,".ui-tabs-anchor",function(){t(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this.tabs=this.tablist.find("> li:has(a[href])").attr({role:"tab",tabIndex:-1}),this._addClass(this.tabs,"ui-tabs-tab","ui-state-default"),this.anchors=this.tabs.map(function(){return t("a",this)[0]}).attr({role:"presentation",tabIndex:-1}),this._addClass(this.anchors,"ui-tabs-anchor"),this.panels=t(),this.anchors.each(function(i,s){var n,o,a,r=t(s).uniqueId().attr("id"),h=t(s).closest("li"),l=h.attr("aria-controls");e._isLocal(s)?(n=s.hash,a=n.substring(1),o=e.element.find(e._sanitizeSelector(n))):(a=h.attr("aria-controls")||t({}).uniqueId()[0].id,n="#"+a,o=e.element.find(n),o.length||(o=e._createPanel(a),o.insertAfter(e.panels[i-1]||e.tablist)),o.attr("aria-live","polite")),o.length&&(e.panels=e.panels.add(o)),l&&h.data("ui-tabs-aria-controls",l),h.attr({"aria-controls":a,"aria-labelledby":r}),o.attr("aria-labelledby",r)}),this.panels.attr("role","tabpanel"),this._addClass(this.panels,"ui-tabs-panel","ui-widget-content"),i&&(this._off(i.not(this.tabs)),this._off(s.not(this.anchors)),this._off(n.not(this.panels)))},_getList:function(){return this.tablist||this.element.find("ol, ul").eq(0)},_createPanel:function(e){return t("<div>").attr("id",e).data("ui-tabs-destroy",!0)},_setOptionDisabled:function(e){var i,s,n;for(t.isArray(e)&&(e.length?e.length===this.anchors.length&&(e=!0):e=!1),n=0;s=this.tabs[n];n++)i=t(s),e===!0||-1!==t.inArray(n,e)?(i.attr("aria-disabled","true"),this._addClass(i,null,"ui-state-disabled")):(i.removeAttr("aria-disabled"),this._removeClass(i,null,"ui-state-disabled"));this.options.disabled=e,this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,e===!0)},_setupEvents:function(e){var i={};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(!0,this.anchors,{click:function(t){t.preventDefault()}}),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(e){var i,s=this.element.parent();"fill"===e?(i=s.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var e=t(this),s=e.css("position");"absolute"!==s&&"fixed"!==s&&(i-=e.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=t(this).outerHeight(!0)}),this.panels.each(function(){t(this).height(Math.max(0,i-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===e&&(i=0,this.panels.each(function(){i=Math.max(i,t(this).height("").height())}).height(i))},_eventHandler:function(e){var i=this.options,s=this.active,n=t(e.currentTarget),o=n.closest("li"),a=o[0]===s[0],r=a&&i.collapsible,h=r?t():this._getPanelForTab(o),l=s.length?this._getPanelForTab(s):t(),c={oldTab:s,oldPanel:l,newTab:r?t():o,newPanel:h};e.preventDefault(),o.hasClass("ui-state-disabled")||o.hasClass("ui-tabs-loading")||this.running||a&&!i.collapsible||this._trigger("beforeActivate",e,c)===!1||(i.active=r?!1:this.tabs.index(o),this.active=a?t():o,this.xhr&&this.xhr.abort(),l.length||h.length||t.error("jQuery UI Tabs: Mismatching fragment identifier."),h.length&&this.load(this.tabs.index(o),e),this._toggle(e,c))},_toggle:function(e,i){function s(){o.running=!1,o._trigger("activate",e,i)}function n(){o._addClass(i.newTab.closest("li"),"ui-tabs-active","ui-state-active"),a.length&&o.options.show?o._show(a,o.options.show,s):(a.show(),s())}var o=this,a=i.newPanel,r=i.oldPanel;this.running=!0,r.length&&this.options.hide?this._hide(r,this.options.hide,function(){o._removeClass(i.oldTab.closest("li"),"ui-tabs-active","ui-state-active"),n()}):(this._removeClass(i.oldTab.closest("li"),"ui-tabs-active","ui-state-active"),r.hide(),n()),r.attr("aria-hidden","true"),i.oldTab.attr({"aria-selected":"false","aria-expanded":"false"}),a.length&&r.length?i.oldTab.attr("tabIndex",-1):a.length&&this.tabs.filter(function(){return 0===t(this).attr("tabIndex")}).attr("tabIndex",-1),a.attr("aria-hidden","false"),i.newTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_activate:function(e){var i,s=this._findActive(e);s[0]!==this.active[0]&&(s.length||(s=this.active),i=s.find(".ui-tabs-anchor")[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return e===!1?t():this.tabs.eq(e)},_getIndex:function(e){return"string"==typeof e&&(e=this.anchors.index(this.anchors.filter("[href$='"+t.ui.escapeSelector(e)+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.tablist.removeAttr("role").off(this.eventNamespace),this.anchors.removeAttr("role tabIndex").removeUniqueId(),this.tabs.add(this.panels).each(function(){t.data(this,"ui-tabs-destroy")?t(this).remove():t(this).removeAttr("role tabIndex aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded")}),this.tabs.each(function(){var e=t(this),i=e.data("ui-tabs-aria-controls");i?e.attr("aria-controls",i).removeData("ui-tabs-aria-controls"):e.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(e){var i=this.options.disabled;i!==!1&&(void 0===e?i=!1:(e=this._getIndex(e),i=t.isArray(i)?t.map(i,function(t){return t!==e?t:null}):t.map(this.tabs,function(t,i){return i!==e?i:null})),this._setOptionDisabled(i))},disable:function(e){var i=this.options.disabled;if(i!==!0){if(void 0===e)i=!0;else{if(e=this._getIndex(e),-1!==t.inArray(e,i))return;i=t.isArray(i)?t.merge([e],i).sort():[e]}this._setOptionDisabled(i)}},load:function(e,i){e=this._getIndex(e);var s=this,n=this.tabs.eq(e),o=n.find(".ui-tabs-anchor"),a=this._getPanelForTab(n),r={tab:n,panel:a},h=function(t,e){"abort"===e&&s.panels.stop(!1,!0),s._removeClass(n,"ui-tabs-loading"),a.removeAttr("aria-busy"),t===s.xhr&&delete s.xhr};this._isLocal(o[0])||(this.xhr=t.ajax(this._ajaxSettings(o,i,r)),this.xhr&&"canceled"!==this.xhr.statusText&&(this._addClass(n,"ui-tabs-loading"),a.attr("aria-busy","true"),this.xhr.done(function(t,e,n){setTimeout(function(){a.html(t),s._trigger("load",i,r),h(n,e)},1)}).fail(function(t,e){setTimeout(function(){h(t,e)},1)})))},_ajaxSettings:function(e,i,s){var n=this;return{url:e.attr("href").replace(/#.*$/,""),beforeSend:function(e,o){return n._trigger("beforeLoad",i,t.extend({jqXHR:e,ajaxSettings:o},s))}}},_getPanelForTab:function(e){var i=t(e).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+i))}}),t.uiBackCompat!==!1&&t.widget("ui.tabs",t.ui.tabs,{_processTabs:function(){this._superApply(arguments),this._addClass(this.tabs,"ui-tab")}}),t.ui.tabs,t.widget("ui.tooltip",{version:"1.12.1",options:{classes:{"ui-tooltip":"ui-corner-all ui-widget-shadow"},content:function(){var e=t(this).attr("title")||"";return t("<a>").text(e).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,track:!1,close:null,open:null},_addDescribedBy:function(e,i){var s=(e.attr("aria-describedby")||"").split(/\s+/);s.push(i),e.data("ui-tooltip-id",i).attr("aria-describedby",t.trim(s.join(" ")))},_removeDescribedBy:function(e){var i=e.data("ui-tooltip-id"),s=(e.attr("aria-describedby")||"").split(/\s+/),n=t.inArray(i,s);-1!==n&&s.splice(n,1),e.removeData("ui-tooltip-id"),s=t.trim(s.join(" ")),s?e.attr("aria-describedby",s):e.removeAttr("aria-describedby")},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.liveRegion=t("<div>").attr({role:"log","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this.disabledTitles=t([])},_setOption:function(e,i){var s=this;this._super(e,i),"content"===e&&t.each(this.tooltips,function(t,e){s._updateContent(e.element)})},_setOptionDisabled:function(t){this[t?"_disable":"_enable"]()},_disable:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur");n.target=n.currentTarget=s.element[0],e.close(n,!0)}),this.disabledTitles=this.disabledTitles.add(this.element.find(this.options.items).addBack().filter(function(){var e=t(this);return e.is("[title]")?e.data("ui-tooltip-title",e.attr("title")).removeAttr("title"):void 0}))},_enable:function(){this.disabledTitles.each(function(){var e=t(this);e.data("ui-tooltip-title")&&e.attr("title",e.data("ui-tooltip-title"))}),this.disabledTitles=t([])},open:function(e){var i=this,s=t(e?e.target:this.element).closest(this.options.items);s.length&&!s.data("ui-tooltip-id")&&(s.attr("title")&&s.data("ui-tooltip-title",s.attr("title")),s.data("ui-tooltip-open",!0),e&&"mouseover"===e.type&&s.parents().each(function(){var e,s=t(this);s.data("ui-tooltip-open")&&(e=t.Event("blur"),e.target=e.currentTarget=this,i.close(e,!0)),s.attr("title")&&(s.uniqueId(),i.parents[this.id]={element:this,title:s.attr("title")},s.attr("title",""))}),this._registerCloseHandlers(e,s),this._updateContent(s,e))},_updateContent:function(t,e){var i,s=this.options.content,n=this,o=e?e.type:null;return"string"==typeof s||s.nodeType||s.jquery?this._open(e,t,s):(i=s.call(t[0],function(i){n._delay(function(){t.data("ui-tooltip-open")&&(e&&(e.type=o),this._open(e,t,i))})}),i&&this._open(e,t,i),void 0)},_open:function(e,i,s){function n(t){l.of=t,a.is(":hidden")||a.position(l)}var o,a,r,h,l=t.extend({},this.options.position);if(s){if(o=this._find(i))return o.tooltip.find(".ui-tooltip-content").html(s),void 0;i.is("[title]")&&(e&&"mouseover"===e.type?i.attr("title",""):i.removeAttr("title")),o=this._tooltip(i),a=o.tooltip,this._addDescribedBy(i,a.attr("id")),a.find(".ui-tooltip-content").html(s),this.liveRegion.children().hide(),h=t("<div>").html(a.find(".ui-tooltip-content").html()),h.removeAttr("name").find("[name]").removeAttr("name"),h.removeAttr("id").find("[id]").removeAttr("id"),h.appendTo(this.liveRegion),this.options.track&&e&&/^mouse/.test(e.type)?(this._on(this.document,{mousemove:n}),n(e)):a.position(t.extend({of:i},this.options.position)),a.hide(),this._show(a,this.options.show),this.options.track&&this.options.show&&this.options.show.delay&&(r=this.delayedShow=setInterval(function(){a.is(":visible")&&(n(l.of),clearInterval(r))},t.fx.interval)),this._trigger("open",e,{tooltip:a})}},_registerCloseHandlers:function(e,i){var s={keyup:function(e){if(e.keyCode===t.ui.keyCode.ESCAPE){var s=t.Event(e);s.currentTarget=i[0],this.close(s,!0)}}};i[0]!==this.element[0]&&(s.remove=function(){this._removeTooltip(this._find(i).tooltip)}),e&&"mouseover"!==e.type||(s.mouseleave="close"),e&&"focusin"!==e.type||(s.focusout="close"),this._on(!0,i,s)},close:function(e){var i,s=this,n=t(e?e.currentTarget:this.element),o=this._find(n);return o?(i=o.tooltip,o.closing||(clearInterval(this.delayedShow),n.data("ui-tooltip-title")&&!n.attr("title")&&n.attr("title",n.data("ui-tooltip-title")),this._removeDescribedBy(n),o.hiding=!0,i.stop(!0),this._hide(i,this.options.hide,function(){s._removeTooltip(t(this))}),n.removeData("ui-tooltip-open"),this._off(n,"mouseleave focusout keyup"),n[0]!==this.element[0]&&this._off(n,"remove"),this._off(this.document,"mousemove"),e&&"mouseleave"===e.type&&t.each(this.parents,function(e,i){t(i.element).attr("title",i.title),delete s.parents[e]}),o.closing=!0,this._trigger("close",e,{tooltip:i}),o.hiding||(o.closing=!1)),void 0):(n.removeData("ui-tooltip-open"),void 0)},_tooltip:function(e){var i=t("<div>").attr("role","tooltip"),s=t("<div>").appendTo(i),n=i.uniqueId().attr("id");return this._addClass(s,"ui-tooltip-content"),this._addClass(i,"ui-tooltip","ui-widget ui-widget-content"),i.appendTo(this._appendTo(e)),this.tooltips[n]={element:e,tooltip:i}},_find:function(t){var e=t.data("ui-tooltip-id");return e?this.tooltips[e]:null},_removeTooltip:function(t){t.remove(),delete this.tooltips[t.attr("id")]},_appendTo:function(t){var e=t.closest(".ui-front, dialog");return e.length||(e=this.document[0].body),e},_destroy:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur"),o=s.element;n.target=n.currentTarget=o[0],e.close(n,!0),t("#"+i).remove(),o.data("ui-tooltip-title")&&(o.attr("title")||o.attr("title",o.data("ui-tooltip-title")),o.removeData("ui-tooltip-title"))}),this.liveRegion.remove()}}),t.uiBackCompat!==!1&&t.widget("ui.tooltip",t.ui.tooltip,{options:{tooltipClass:null},_tooltip:function(){var t=this._superApply(arguments);return this.options.tooltipClass&&t.tooltip.addClass(this.options.tooltipClass),t}}),t.ui.tooltip}); \ No newline at end of file
diff --git a/platform/www/lib/scripts/jquery/jquery.cookie.js b/platform/www/lib/scripts/jquery/jquery.cookie.js
new file mode 100644
index 0000000..c7f3a59
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery.cookie.js
@@ -0,0 +1,117 @@
+/*!
+ * jQuery Cookie Plugin v1.4.1
+ * https://github.com/carhartl/jquery-cookie
+ *
+ * Copyright 2013 Klaus Hartl
+ * Released under the MIT license
+ */
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD
+ define(['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // CommonJS
+ factory(require('jquery'));
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+}(function ($) {
+
+ var pluses = /\+/g;
+
+ function encode(s) {
+ return config.raw ? s : encodeURIComponent(s);
+ }
+
+ function decode(s) {
+ return config.raw ? s : decodeURIComponent(s);
+ }
+
+ function stringifyCookieValue(value) {
+ return encode(config.json ? JSON.stringify(value) : String(value));
+ }
+
+ function parseCookieValue(s) {
+ if (s.indexOf('"') === 0) {
+ // This is a quoted cookie as according to RFC2068, unescape...
+ s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
+ }
+
+ try {
+ // Replace server-side written pluses with spaces.
+ // If we can't decode the cookie, ignore it, it's unusable.
+ // If we can't parse the cookie, ignore it, it's unusable.
+ s = decodeURIComponent(s.replace(pluses, ' '));
+ return config.json ? JSON.parse(s) : s;
+ } catch(e) {}
+ }
+
+ function read(s, converter) {
+ var value = config.raw ? s : parseCookieValue(s);
+ return $.isFunction(converter) ? converter(value) : value;
+ }
+
+ var config = $.cookie = function (key, value, options) {
+
+ // Write
+
+ if (value !== undefined && !$.isFunction(value)) {
+ options = $.extend({}, config.defaults, options);
+
+ if (typeof options.expires === 'number') {
+ var days = options.expires, t = options.expires = new Date();
+ t.setTime(+t + days * 864e+5);
+ }
+
+ return (document.cookie = [
+ encode(key), '=', stringifyCookieValue(value),
+ options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
+ options.path ? '; path=' + options.path : '',
+ options.domain ? '; domain=' + options.domain : '',
+ options.secure ? '; secure' : ''
+ ].join(''));
+ }
+
+ // Read
+
+ var result = key ? undefined : {};
+
+ // To prevent the for loop in the first place assign an empty array
+ // in case there are no cookies at all. Also prevents odd result when
+ // calling $.cookie().
+ var cookies = document.cookie ? document.cookie.split('; ') : [];
+
+ for (var i = 0, l = cookies.length; i < l; i++) {
+ var parts = cookies[i].split('=');
+ var name = decode(parts.shift());
+ var cookie = parts.join('=');
+
+ if (key && key === name) {
+ // If second argument (value) is a function it's a converter...
+ result = read(cookie, value);
+ break;
+ }
+
+ // Prevent storing a cookie that we couldn't decode.
+ if (!key && (cookie = read(cookie)) !== undefined) {
+ result[name] = cookie;
+ }
+ }
+
+ return result;
+ };
+
+ config.defaults = {};
+
+ $.removeCookie = function (key, options) {
+ if ($.cookie(key) === undefined) {
+ return false;
+ }
+
+ // Must not alter options, thus extending a fresh object...
+ $.cookie(key, '', $.extend({}, options, { expires: -1 }));
+ return !$.cookie(key);
+ };
+
+}));
diff --git a/platform/www/lib/scripts/jquery/jquery.min.js b/platform/www/lib/scripts/jquery/jquery.min.js
new file mode 100644
index 0000000..b061403
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/jquery.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}S.fn=S.prototype={jquery:f,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(n){return this.pushStack(S.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},S.extend=S.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(S.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||S.isPlainObject(n)?n:{},i=!1,a[t]=S.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},S.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=v.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){b(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(p(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(p(Object(e))?S.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(p(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:y}),"function"==typeof Symbol&&(S.fn[Symbol.iterator]=t[Symbol.iterator]),S.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var d=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S="sizzle"+1*new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),D=function(e,t){return e===t&&(l=!0),0},j={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",I="(?:\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",W="\\["+M+"*("+I+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+I+"))|)"+M+"*\\]",F=":("+I+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+W+")*)|.*)\\)|)",B=new RegExp(M+"+","g"),$=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=new RegExp("^"+M+"*,"+M+"*"),z=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="<a id='"+S+"'></a><select id='"+S+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0<se(t,C,null,[e]).length},se.contains=function(e,t){return(e.ownerDocument||e)!=C&&T(e),y(e,t)},se.attr=function(e,t){(e.ownerDocument||e)!=C&&T(e);var n=b.attrHandle[t.toLowerCase()],r=n&&j.call(b.attrHandle,t.toLowerCase())?n(e,t,!E):void 0;return void 0!==r?r:d.attributes||!E?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},se.escape=function(e){return(e+"").replace(re,ie)},se.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},se.uniqueSort=function(e){var t,n=[],r=0,i=0;if(l=!d.detectDuplicates,u=!d.sortStable&&e.slice(0),e.sort(D),l){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return u=null,e},o=se.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else while(t=e[r++])n+=o(t);return n},(b=se.selectors={cacheLength:50,createPseudo:le,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1<t.indexOf(i):"$="===r?i&&t.slice(-i.length)===i:"~="===r?-1<(" "+t.replace(B," ")+" ").indexOf(i):"|="===r&&(t===i||t.slice(0,i.length+1)===i+"-"))}},CHILD:function(h,e,t,g,v){var y="nth"!==h.slice(0,3),m="last"!==h.slice(-4),x="of-type"===e;return 1===g&&0===v?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u,l=y!==m?"nextSibling":"previousSibling",c=e.parentNode,f=x&&e.nodeName.toLowerCase(),p=!n&&!x,d=!1;if(c){if(y){while(l){a=e;while(a=a[l])if(x?a.nodeName.toLowerCase()===f:1===a.nodeType)return!1;u=l="only"===h&&!u&&"nextSibling"}return!0}if(u=[m?c.firstChild:c.lastChild],m&&p){d=(s=(r=(i=(o=(a=c)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1])&&r[2],a=s&&c.childNodes[s];while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if(1===a.nodeType&&++d&&a===e){i[h]=[k,s,d];break}}else if(p&&(d=s=(r=(i=(o=(a=e)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1]),!1===d)while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if((x?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++d&&(p&&((i=(o=a[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[k,d]),a===e))break;return(d-=v)===g||d%g==0&&0<=d/g}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||se.error("unsupported pseudo: "+e);return a[S]?a(o):1<a.length?(t=[e,e,"",o],b.setFilters.hasOwnProperty(e.toLowerCase())?le(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=P(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:le(function(e){var r=[],i=[],s=f(e.replace($,"$1"));return s[S]?le(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:le(function(t){return function(e){return 0<se(t,e).length}}),contains:le(function(t){return t=t.replace(te,ne),function(e){return-1<(e.textContent||o(e)).indexOf(t)}}),lang:le(function(n){return V.test(n||"")||se.error("unsupported lang: "+n),n=n.replace(te,ne).toLowerCase(),function(e){var t;do{if(t=E?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(t=t.toLowerCase())===n||0===t.indexOf(n+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=n.location&&n.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===a},focus:function(e){return e===C.activeElement&&(!C.hasFocus||C.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:ve(function(){return[0]}),last:ve(function(e,t){return[t-1]}),eq:ve(function(e,t,n){return[n<0?n+t:n]}),even:ve(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:ve(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:ve(function(e,t,n){for(var r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:ve(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=de(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=he(e);function me(){}function xe(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function be(s,e,t){var u=e.dir,l=e.next,c=l||u,f=t&&"parentNode"===c,p=r++;return e.first?function(e,t,n){while(e=e[u])if(1===e.nodeType||f)return s(e,t,n);return!1}:function(e,t,n){var r,i,o,a=[k,p];if(n){while(e=e[u])if((1===e.nodeType||f)&&s(e,t,n))return!0}else while(e=e[u])if(1===e.nodeType||f)if(i=(o=e[S]||(e[S]={}))[e.uniqueID]||(o[e.uniqueID]={}),l&&l===e.nodeName.toLowerCase())e=e[u]||e;else{if((r=i[c])&&r[0]===k&&r[1]===p)return a[2]=r[2];if((i[c]=a)[2]=s(e,t,n))return!0}return!1}}function we(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Te(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Ce(d,h,g,v,y,e){return v&&!v[S]&&(v=Ce(v)),y&&!y[S]&&(y=Ce(y,e)),le(function(e,t,n,r){var i,o,a,s=[],u=[],l=t.length,c=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)se(e,t[r],n);return n}(h||"*",n.nodeType?[n]:n,[]),f=!d||!e&&h?c:Te(c,s,d,n,r),p=g?y||(e?d:l||v)?[]:t:f;if(g&&g(f,p,n,r),v){i=Te(p,u),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(p[u[o]]=!(f[u[o]]=a))}if(e){if(y||d){if(y){i=[],o=p.length;while(o--)(a=p[o])&&i.push(f[o]=a);y(null,p=[],i,r)}o=p.length;while(o--)(a=p[o])&&-1<(i=y?P(e,a):s[o])&&(e[i]=!(t[i]=a))}}else p=Te(p===t?p.splice(l,p.length):p),y?y(null,t,p,r):H.apply(t,p)})}function Ee(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[" "],s=o?1:0,u=be(function(e){return e===i},a,!0),l=be(function(e){return-1<P(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!==w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=b.relative[e[s].type])c=[be(we(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[S]){for(n=++s;n<r;n++)if(b.relative[e[n].type])break;return Ce(1<s&&we(c),1<s&&xe(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace($,"$1"),t,s<n&&Ee(e.slice(s,n)),n<r&&Ee(e=e.slice(n)),n<r&&xe(e))}c.push(t)}return we(c)}return me.prototype=b.filters=b.pseudos,b.setFilters=new me,h=se.tokenize=function(e,t){var n,r,i,o,a,s,u,l=x[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=b.preFilter;while(a){for(o in n&&!(r=_.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=z.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace($," ")}),a=a.slice(n.length)),b.filter)!(r=G[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?se.error(e):x(e,s).slice(0)},f=se.compile=function(e,t){var n,v,y,m,x,r,i=[],o=[],a=A[e+" "];if(!a){t||(t=h(e)),n=t.length;while(n--)(a=Ee(t[n]))[S]?i.push(a):o.push(a);(a=A(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l="0",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG("*",i),h=k+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t==C||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){a=0,t||o.ownerDocument==C||(T(o),n=!E);while(s=v[a++])if(s(o,t||C,n)){r.push(o);break}i&&(k=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=q.call(r));f=Te(f)}H.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&se.uniqueSort(r)}return i&&(k=h,w=p),c},m?le(r):r))).selector=e}return a},g=se.select=function(e,t,n,r){var i,o,a,s,u,l="function"==typeof e&&e,c=!r&&h(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&"ID"===(a=o[0]).type&&9===t.nodeType&&E&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(te,ne),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=G.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],b.relative[s=a.type])break;if((u=b.find[s])&&(r=u(a.matches[0].replace(te,ne),ee.test(o[0].type)&&ye(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&xe(o)))return H.apply(n,r),n;break}}}return(l||f(e,c))(r,t,!E,n,!t||ee.test(e)&&ye(t.parentNode)||t),n},d.sortStable=S.split("").sort(D).join("")===S,d.detectDuplicates=!!l,T(),d.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(C.createElement("fieldset"))}),ce(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||fe("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),d.attributes&&ce(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||fe("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ce(function(e){return null==e.getAttribute("disabled")})||fe(R,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),se}(C);S.find=d,S.expr=d.selectors,S.expr[":"]=S.expr.pseudos,S.uniqueSort=S.unique=d.uniqueSort,S.text=d.getText,S.isXMLDoc=d.isXML,S.contains=d.contains,S.escapeSelector=d.escape;var h=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&S(e).is(n))break;r.push(e)}return r},T=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},k=S.expr.match.needsContext;function A(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var N=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1<i.call(n,e)!==r}):S.filter(n,e,r)}S.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?S.find.matchesSelector(r,e)?[r]:[]:S.find.matches(e,S.grep(t,function(e){return 1===e.nodeType}))},S.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(S(e).filter(function(){for(t=0;t<r;t++)if(S.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)S.find(e,i[t],n);return 1<r?S.uniqueSort(n):n},filter:function(e){return this.pushStack(D(this,e||[],!1))},not:function(e){return this.pushStack(D(this,e||[],!0))},is:function(e){return!!D(this,"string"==typeof e&&k.test(e)?S(e):e||[],!1).length}});var j,q=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(S.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&S(e);if(!k.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&S.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?S.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?i.call(S(e),this[0]):i.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(S.uniqueSort(S.merge(this.get(),S(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),S.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return h(e,"parentNode")},parentsUntil:function(e,t,n){return h(e,"parentNode",n)},next:function(e){return O(e,"nextSibling")},prev:function(e){return O(e,"previousSibling")},nextAll:function(e){return h(e,"nextSibling")},prevAll:function(e){return h(e,"previousSibling")},nextUntil:function(e,t,n){return h(e,"nextSibling",n)},prevUntil:function(e,t,n){return h(e,"previousSibling",n)},siblings:function(e){return T((e.parentNode||{}).firstChild,e)},children:function(e){return T(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(A(e,"template")&&(e=e.content||e),S.merge([],e.childNodes))}},function(r,i){S.fn[r]=function(e,t){var n=S.map(this,i,e);return"Until"!==r.slice(-5)&&(t=e),t&&"string"==typeof t&&(n=S.filter(t,n)),1<this.length&&(H[r]||S.uniqueSort(n),L.test(r)&&n.reverse()),this.pushStack(n)}});var P=/[^\x20\t\r\n\f]+/g;function R(e){return e}function M(e){throw e}function I(e,t,n,r){var i;try{e&&m(i=e.promise)?i.call(e).done(t).fail(n):e&&m(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}S.Callbacks=function(r){var e,n;r="string"==typeof r?(e=r,n={},S.each(e.match(P)||[],function(e,t){n[t]=!0}),n):S.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:"")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){S.each(e,function(e,t){m(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&"string"!==w(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return S.each(arguments,function(e,t){var n;while(-1<(n=S.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<S.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t="",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},S.extend({Deferred:function(e){var o=[["notify","progress",S.Callbacks("memory"),S.Callbacks("memory"),2],["resolve","done",S.Callbacks("once memory"),S.Callbacks("once memory"),0,"resolved"],["reject","fail",S.Callbacks("once memory"),S.Callbacks("once memory"),1,"rejected"]],i="pending",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},"catch":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return S.Deferred(function(r){S.each(o,function(e,t){var n=m(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&m(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+"With"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError("Thenable self-resolution");t=e&&("object"==typeof e||"function"==typeof e)&&e.then,m(t)?s?t.call(e,l(u,o,R,s),l(u,o,M,s)):(u++,t.call(e,l(u,o,R,s),l(u,o,M,s),l(u,o,R,o.notifyWith))):(a!==R&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){S.Deferred.exceptionHook&&S.Deferred.exceptionHook(e,t.stackTrace),u<=i+1&&(a!==M&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(S.Deferred.getStackHook&&(t.stackTrace=S.Deferred.getStackHook()),C.setTimeout(t))}}return S.Deferred(function(e){o[0][3].add(l(0,e,m(r)?r:R,e.notifyWith)),o[1][3].add(l(0,e,m(t)?t:R)),o[2][3].add(l(0,e,m(n)?n:M))}).promise()},promise:function(e){return null!=e?S.extend(e,a):a}},s={};return S.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+"With"](this===s?void 0:this,arguments),this},s[t[0]+"With"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=s.call(arguments),o=S.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?s.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(I(e,o.done(a(t)).resolve,o.reject,!n),"pending"===o.state()||m(i[t]&&i[t].then)))return o.then();while(t--)I(i[t],a(t),o.reject);return o.promise()}});var W=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;S.Deferred.exceptionHook=function(e,t){C.console&&C.console.warn&&e&&W.test(e.name)&&C.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},S.readyException=function(e){C.setTimeout(function(){throw e})};var F=S.Deferred();function B(){E.removeEventListener("DOMContentLoaded",B),C.removeEventListener("load",B),S.ready()}S.fn.ready=function(e){return F.then(e)["catch"](function(e){S.readyException(e)}),this},S.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--S.readyWait:S.isReady)||(S.isReady=!0)!==e&&0<--S.readyWait||F.resolveWith(E,[S])}}),S.ready.then=F.then,"complete"===E.readyState||"loading"!==E.readyState&&!E.documentElement.doScroll?C.setTimeout(S.ready):(E.addEventListener("DOMContentLoaded",B),C.addEventListener("load",B));var $=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===w(n))for(s in i=!0,n)$(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,m(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(S(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},_=/^-ms-/,z=/-([a-z])/g;function U(e,t){return t.toUpperCase()}function X(e){return e.replace(_,"ms-").replace(z,U)}var V=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function G(){this.expando=S.expando+G.uid++}G.uid=1,G.prototype={cache:function(e){var t=e[this.expando];return t||(t={},V(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[X(t)]=n;else for(r in t)i[X(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][X(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(X):(t=X(t))in r?[t]:t.match(P)||[]).length;while(n--)delete r[t[n]]}(void 0===t||S.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!S.isEmptyObject(t)}};var Y=new G,Q=new G,J=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,K=/[A-Z]/g;function Z(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(K,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===(i=n)||"false"!==i&&("null"===i?null:i===+i+""?+i:J.test(i)?JSON.parse(i):i)}catch(e){}Q.set(e,t,n)}else n=void 0;return n}S.extend({hasData:function(e){return Q.hasData(e)||Y.hasData(e)},data:function(e,t,n){return Q.access(e,t,n)},removeData:function(e,t){Q.remove(e,t)},_data:function(e,t,n){return Y.access(e,t,n)},_removeData:function(e,t){Y.remove(e,t)}}),S.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=Q.get(o),1===o.nodeType&&!Y.get(o,"hasDataAttrs"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf("data-")&&(r=X(r.slice(5)),Z(o,r,i[r]));Y.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof n?this.each(function(){Q.set(this,n)}):$(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=Q.get(o,n))?t:void 0!==(t=Z(o,n))?t:void 0;this.each(function(){Q.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){Q.remove(this,e)})}}),S.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Y.get(e,t),n&&(!r||Array.isArray(n)?r=Y.access(e,t,S.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=S.queue(e,t),r=n.length,i=n.shift(),o=S._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){S.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Y.get(e,n)||Y.access(e,n,{empty:S.Callbacks("once memory").add(function(){Y.remove(e,[t+"queue",n])})})}}),S.fn.extend({queue:function(t,n){var e=2;return"string"!=typeof t&&(n=t,t="fx",e--),arguments.length<e?S.queue(this[0],t):void 0===n?this:this.each(function(){var e=S.queue(this,t,n);S._queueHooks(this,t),"fx"===t&&"inprogress"!==e[0]&&S.dequeue(this,t)})},dequeue:function(e){return this.each(function(){S.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=S.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=Y.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var ee=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,te=new RegExp("^(?:([+-])=|)("+ee+")([a-z%]*)$","i"),ne=["Top","Right","Bottom","Left"],re=E.documentElement,ie=function(e){return S.contains(e.ownerDocument,e)},oe={composed:!0};re.getRootNode&&(ie=function(e){return S.contains(e.ownerDocument,e)||e.getRootNode(oe)===e.ownerDocument});var ae=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&ie(e)&&"none"===S.css(e,"display")};function se(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return S.css(e,t,"")},u=s(),l=n&&n[3]||(S.cssNumber[t]?"":"px"),c=e.nodeType&&(S.cssNumber[t]||"px"!==l&&+u)&&te.exec(S.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)S.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,S.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var ue={};function le(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?("none"===n&&(l[c]=Y.get(r,"display")||null,l[c]||(r.style.display="")),""===r.style.display&&ae(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ue[s])||(o=a.body.appendChild(a.createElement(s)),u=S.css(o,"display"),o.parentNode.removeChild(o),"none"===u&&(u="block"),ue[s]=u)))):"none"!==n&&(l[c]="none",Y.set(r,"display",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}S.fn.extend({show:function(){return le(this,!0)},hide:function(){return le(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?S(this).show():S(this).hide()})}});var ce,fe,pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="<textarea>x</textarea>",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="<option></option>",y.option=!!ce.lastChild;var ge={thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n<r;n++)Y.set(e[n],"globalEval",!t||Y.get(t[n],"globalEval"))}ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td,y.option||(ge.optgroup=ge.option=[1,"<select multiple='multiple'>","</select>"]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===w(o))S.merge(p,o.nodeType?[o]:o);else if(me.test(o)){a=a||f.appendChild(t.createElement("div")),s=(de.exec(o)||["",""])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+S.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;S.merge(p,a.childNodes),(a=f.firstChild).textContent=""}else p.push(t.createTextNode(o));f.textContent="",d=0;while(o=p[d++])if(r&&-1<S.inArray(o,r))i&&i.push(o);else if(l=ie(o),a=ve(f.appendChild(o),"script"),l&&ye(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}var be=/^key/,we=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Te=/^([^.]*)(?:\.(.+)|)/;function Ce(){return!0}function Ee(){return!1}function Se(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function ke(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)ke(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ee;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return S().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=S.guid++)),e.each(function(){S.event.add(this,t,i,r,n)})}function Ae(e,i,o){o?(Y.set(e,i,!1),S.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Y.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(S.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Y.set(this,i,r),t=o(this,i),this[i](),r!==(n=Y.get(this,i))||t?Y.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Y.set(this,i,{value:S.event.trigger(S.extend(r[0],S.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Y.get(e,i)&&S.event.add(e,i,Ce)}S.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.get(t);if(V(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&S.find.matchesSelector(re,i),n.guid||(n.guid=S.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof S&&S.event.triggered!==e.type?S.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(P)||[""]).length;while(l--)d=g=(s=Te.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=S.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=S.event.special[d]||{},c=S.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&S.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),S.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.hasData(e)&&Y.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(P)||[""]).length;while(l--)if(d=g=(s=Te.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=S.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||S.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)S.event.remove(e,d+t[l],n,r,!0);S.isEmptyObject(u)&&Y.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=S.event.fix(e),l=(Y.get(this,"events")||Object.create(null))[u.type]||[],c=S.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=S.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((S.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?-1<S(i,this).index(l):S.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(S.Event.prototype,t,{enumerable:!0,configurable:!0,get:m(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[S.expando]?e:new S.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&Ae(t,"click",Ce),!1},trigger:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&Ae(t,"click"),!0},_default:function(e){var t=e.target;return pe.test(t.type)&&t.click&&A(t,"input")&&Y.get(t,"click")||A(t,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},S.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},S.Event=function(e,t){if(!(this instanceof S.Event))return new S.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ce:Ee,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&S.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[S.expando]=!0},S.Event.prototype={constructor:S.Event,isDefaultPrevented:Ee,isPropagationStopped:Ee,isImmediatePropagationStopped:Ee,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ce,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ce,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ce,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},S.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&be.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&we.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},S.event.addProp),S.each({focus:"focusin",blur:"focusout"},function(e,t){S.event.special[e]={setup:function(){return Ae(this,e,Se),!1},trigger:function(){return Ae(this,e),!0},delegateType:t}}),S.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,i){S.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||S.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),S.fn.extend({on:function(e,t,n,r){return ke(this,e,t,n,r)},one:function(e,t,n,r){return ke(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,S(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=Ee),this.each(function(){S.event.remove(this,e,n,t)})}});var Ne=/<script|<style|<link/i,De=/checked\s*(?:[^=]|=\s*.checked.)/i,je=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n<r;n++)S.event.add(t,i,s[i][n]);Q.hasData(e)&&(o=Q.access(e),a=S.extend({},o),Q.set(t,a))}}function Pe(n,r,i,o){r=g(r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=m(d);if(h||1<f&&"string"==typeof d&&!y.checkClone&&De.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),Pe(t,r,i,o)});if(f&&(t=(e=xe(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=S.map(ve(e,"script"),Le)).length;c<f;c++)u=e,c!==p&&(u=S.clone(u,!0,!0),s&&S.merge(a,ve(u,"script"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,S.map(a,He),c=0;c<s;c++)u=a[c],he.test(u.type||"")&&!Y.access(u,"globalEval")&&S.contains(l,u)&&(u.src&&"module"!==(u.type||"").toLowerCase()?S._evalUrl&&!u.noModule&&S._evalUrl(u.src,{nonce:u.nonce||u.getAttribute("nonce")},l):b(u.textContent.replace(je,""),u,l))}return n}function Re(e,t,n){for(var r,i=t?S.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||S.cleanData(ve(r)),r.parentNode&&(n&&ie(r)&&ye(ve(r,"script")),r.parentNode.removeChild(r));return e}S.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=ie(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||S.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r<i;r++)s=o[r],u=a[r],void 0,"input"===(l=u.nodeName.toLowerCase())&&pe.test(s.type)?u.checked=s.checked:"input"!==l&&"textarea"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||ve(e),a=a||ve(c),r=0,i=o.length;r<i;r++)Oe(o[r],a[r]);else Oe(e,c);return 0<(a=ve(c,"script")).length&&ye(a,!f&&ve(e,"script")),c},cleanData:function(e){for(var t,n,r,i=S.event.special,o=0;void 0!==(n=e[o]);o++)if(V(n)){if(t=n[Y.expando]){if(t.events)for(r in t.events)i[r]?S.event.remove(n,r):S.removeEvent(n,r,t.handle);n[Y.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),S.fn.extend({detach:function(e){return Re(this,e,!0)},remove:function(e){return Re(this,e)},text:function(e){return $(this,function(e){return void 0===e?S.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Pe(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||qe(this,e).appendChild(e)})},prepend:function(){return Pe(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=qe(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(S.cleanData(ve(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return S.clone(this,e,t)})},html:function(e){return $(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ne.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=S.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(S.cleanData(ve(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return Pe(this,arguments,function(e){var t=this.parentNode;S.inArray(this,n)<0&&(S.cleanData(ve(this)),t&&t.replaceChild(e,this))},n)}}),S.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,a){S.fn[e]=function(e){for(var t,n=[],r=S(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),S(r[o])[a](t),u.apply(n,t.get());return this.pushStack(n)}});var Me=new RegExp("^("+ee+")(?!px)[a-z%]+$","i"),Ie=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=C),t.getComputedStyle(e)},We=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Fe=new RegExp(ne.join("|"),"i");function Be(e,t,n){var r,i,o,a,s=e.style;return(n=n||Ie(e))&&(""!==(a=n.getPropertyValue(t)||n[t])||ie(e)||(a=S.style(e,t)),!y.pixelBoxStyles()&&Me.test(a)&&Fe.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function $e(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",l.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",re.appendChild(u).appendChild(l);var e=C.getComputedStyle(l);n="1%"!==e.top,s=12===t(e.marginLeft),l.style.right="60%",o=36===t(e.right),r=36===t(e.width),l.style.position="absolute",i=12===t(l.offsetWidth/3),re.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=E.createElement("div"),l=E.createElement("div");l.style&&(l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",y.clearCloneStyle="content-box"===l.style.backgroundClip,S.extend(y,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=E.createElement("table"),t=E.createElement("tr"),n=E.createElement("div"),e.style.cssText="position:absolute;left:-11111px",t.style.height="1px",n.style.height="9px",re.appendChild(e).appendChild(t).appendChild(n),r=C.getComputedStyle(t),a=3<parseInt(r.height),re.removeChild(e)),a}}))}();var _e=["Webkit","Moz","ms"],ze=E.createElement("div").style,Ue={};function Xe(e){var t=S.cssProps[e]||Ue[e];return t||(e in ze?e:Ue[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=_e.length;while(n--)if((e=_e[n]+t)in ze)return e}(e)||e)}var Ve=/^(none|table(?!-c[ea]).+)/,Ge=/^--/,Ye={position:"absolute",visibility:"hidden",display:"block"},Qe={letterSpacing:"0",fontWeight:"400"};function Je(e,t,n){var r=te.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function Ke(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(u+=S.css(e,n+ne[a],!0,i)),r?("content"===n&&(u-=S.css(e,"padding"+ne[a],!0,i)),"margin"!==n&&(u-=S.css(e,"border"+ne[a]+"Width",!0,i))):(u+=S.css(e,"padding"+ne[a],!0,i),"padding"!==n?u+=S.css(e,"border"+ne[a]+"Width",!0,i):s+=S.css(e,"border"+ne[a]+"Width",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function Ze(e,t,n){var r=Ie(e),i=(!y.boxSizingReliable()||n)&&"border-box"===S.css(e,"boxSizing",!1,r),o=i,a=Be(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(Me.test(a)){if(!n)return a;a="auto"}return(!y.boxSizingReliable()&&i||!y.reliableTrDimensions()&&A(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===S.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===S.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+Ke(e,t,n||(i?"border":"content"),o,r,a)+"px"}function et(e,t,n,r,i){return new et.prototype.init(e,t,n,r,i)}S.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Be(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=X(t),u=Ge.test(t),l=e.style;if(u||(t=Xe(s)),a=S.cssHooks[t]||S.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=te.exec(n))&&i[1]&&(n=se(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(S.cssNumber[s]?"":"px")),y.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=X(t);return Ge.test(t)||(t=Xe(s)),(a=S.cssHooks[t]||S.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Be(e,t,r)),"normal"===i&&t in Qe&&(i=Qe[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),S.each(["height","width"],function(e,u){S.cssHooks[u]={get:function(e,t,n){if(t)return!Ve.test(S.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?Ze(e,u,n):We(e,Ye,function(){return Ze(e,u,n)})},set:function(e,t,n){var r,i=Ie(e),o=!y.scrollboxSize()&&"absolute"===i.position,a=(o||n)&&"border-box"===S.css(e,"boxSizing",!1,i),s=n?Ke(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e["offset"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-Ke(e,u,"border",!1,i)-.5)),s&&(r=te.exec(t))&&"px"!==(r[3]||"px")&&(e.style[u]=t,t=S.css(e,u)),Je(0,t,s)}}}),S.cssHooks.marginLeft=$e(y.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Be(e,"marginLeft"))||e.getBoundingClientRect().left-We(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),S.each({margin:"",padding:"",border:"Width"},function(i,o){S.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r="string"==typeof e?e.split(" "):[e];t<4;t++)n[i+ne[t]+o]=r[t]||r[t-2]||r[0];return n}},"margin"!==i&&(S.cssHooks[i+o].set=Je)}),S.fn.extend({css:function(e,t){return $(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Ie(e),i=t.length;a<i;a++)o[t[a]]=S.css(e,t[a],!1,r);return o}return void 0!==n?S.style(e,t,n):S.css(e,t)},e,t,1<arguments.length)}}),((S.Tween=et).prototype={constructor:et,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||S.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(S.cssNumber[n]?"":"px")},cur:function(){var e=et.propHooks[this.prop];return e&&e.get?e.get(this):et.propHooks._default.get(this)},run:function(e){var t,n=et.propHooks[this.prop];return this.options.duration?this.pos=t=S.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):et.propHooks._default.set(this),this}}).init.prototype=et.prototype,(et.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=S.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){S.fx.step[e.prop]?S.fx.step[e.prop](e):1!==e.elem.nodeType||!S.cssHooks[e.prop]&&null==e.elem.style[Xe(e.prop)]?e.elem[e.prop]=e.now:S.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=et.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},S.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},S.fx=et.prototype.init,S.fx.step={};var tt,nt,rt,it,ot=/^(?:toggle|show|hide)$/,at=/queueHooks$/;function st(){nt&&(!1===E.hidden&&C.requestAnimationFrame?C.requestAnimationFrame(st):C.setTimeout(st,S.fx.interval),S.fx.tick())}function ut(){return C.setTimeout(function(){tt=void 0}),tt=Date.now()}function lt(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=ne[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function ct(e,t,n){for(var r,i=(ft.tweeners[t]||[]).concat(ft.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function ft(o,e,t){var n,a,r=0,i=ft.prefilters.length,s=S.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=tt||ut(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;r<i;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),n<1&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:S.extend({},e),opts:S.extend(!0,{specialEasing:{},easing:S.easing._default},t),originalProperties:e,originalOptions:t,startTime:tt||ut(),duration:t.duration,tweens:[],createTween:function(e,t){var n=S.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;t<n;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for(!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=X(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=S.cssHooks[r])&&"expand"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing);r<i;r++)if(n=ft.prefilters[r].call(l,o,c,l.opts))return m(n.stop)&&(S._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return S.map(c,ct,l),m(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),S.fx.timer(S.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}S.Animation=S.extend(ft,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return se(n.elem,e,te.exec(t),n),n}]},tweener:function(e,t){m(e)?(t=e,e=["*"]):e=e.match(P);for(var n,r=0,i=e.length;r<i;r++)n=e[r],ft.tweeners[n]=ft.tweeners[n]||[],ft.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&ae(e),v=Y.get(e,"fxshow");for(r in n.queue||(null==(a=S._queueHooks(e,"fx")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,S.queue(e,"fx").length||a.empty.fire()})})),t)if(i=t[r],ot.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||S.style(e,r)}if((u=!S.isEmptyObject(t))||!S.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=Y.get(e,"display")),"none"===(c=S.css(e,"display"))&&(l?c=l:(le([e],!0),l=e.style.display||l,c=S.css(e,"display"),le([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===S.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?"hidden"in v&&(g=v.hidden):v=Y.access(e,"fxshow",{display:l}),o&&(v.hidden=!g),g&&le([e],!0),p.done(function(){for(r in g||le([e]),Y.remove(e,"fxshow"),d)S.style(e,r,d[r])})),u=ct(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?ft.prefilters.unshift(e):ft.prefilters.push(e)}}),S.speed=function(e,t,n){var r=e&&"object"==typeof e?S.extend({},e):{complete:n||!n&&t||m(e)&&e,duration:e,easing:n&&t||t&&!m(t)&&t};return S.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in S.fx.speeds?r.duration=S.fx.speeds[r.duration]:r.duration=S.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){m(r.old)&&r.old.call(this),r.queue&&S.dequeue(this,r.queue)},r},S.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ae).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=S.isEmptyObject(t),o=S.speed(e,n,r),a=function(){var e=ft(this,S.extend({},t),o);(i||Y.get(this,"finish"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return"string"!=typeof i&&(o=e,e=i,i=void 0),e&&this.queue(i||"fx",[]),this.each(function(){var e=!0,t=null!=i&&i+"queueHooks",n=S.timers,r=Y.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&at.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1));!e&&o||S.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||"fx"),this.each(function(){var e,t=Y.get(this),n=t[a+"queue"],r=t[a+"queueHooks"],i=S.timers,o=n?n.length:0;for(t.finish=!0,S.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;e<o;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),S.each(["toggle","show","hide"],function(e,r){var i=S.fn[r];S.fn[r]=function(e,t,n){return null==e||"boolean"==typeof e?i.apply(this,arguments):this.animate(lt(r,!0),e,t,n)}}),S.each({slideDown:lt("show"),slideUp:lt("hide"),slideToggle:lt("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,r){S.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),S.timers=[],S.fx.tick=function(){var e,t=0,n=S.timers;for(tt=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||S.fx.stop(),tt=void 0},S.fx.timer=function(e){S.timers.push(e),S.fx.start()},S.fx.interval=13,S.fx.start=function(){nt||(nt=!0,st())},S.fx.stop=function(){nt=null},S.fx.speeds={slow:600,fast:200,_default:400},S.fn.delay=function(r,e){return r=S.fx&&S.fx.speeds[r]||r,e=e||"fx",this.queue(e,function(e,t){var n=C.setTimeout(e,r);t.stop=function(){C.clearTimeout(n)}})},rt=E.createElement("input"),it=E.createElement("select").appendChild(E.createElement("option")),rt.type="checkbox",y.checkOn=""!==rt.value,y.optSelected=it.selected,(rt=E.createElement("input")).value="t",rt.type="radio",y.radioValue="t"===rt.value;var pt,dt=S.expr.attrHandle;S.fn.extend({attr:function(e,t){return $(this,S.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){S.removeAttr(this,e)})}}),S.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?S.prop(e,t,n):(1===o&&S.isXMLDoc(e)||(i=S.attrHooks[t.toLowerCase()]||(S.expr.match.bool.test(t)?pt:void 0)),void 0!==n?null===n?void S.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=S.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!y.radioValue&&"radio"===t&&A(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(P);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),pt={set:function(e,t,n){return!1===t?S.removeAttr(e,n):e.setAttribute(n,n),n}},S.each(S.expr.match.bool.source.match(/\w+/g),function(e,t){var a=dt[t]||S.find.attr;dt[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=dt[o],dt[o]=r,r=null!=a(e,t,n)?o:null,dt[o]=i),r}});var ht=/^(?:input|select|textarea|button)$/i,gt=/^(?:a|area)$/i;function vt(e){return(e.match(P)||[]).join(" ")}function yt(e){return e.getAttribute&&e.getAttribute("class")||""}function mt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(P)||[]}S.fn.extend({prop:function(e,t){return $(this,S.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[S.propFix[e]||e]})}}),S.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&S.isXMLDoc(e)||(t=S.propFix[t]||t,i=S.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=S.find.attr(e,"tabindex");return t?parseInt(t,10):ht.test(e.nodeName)||gt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),y.optSelected||(S.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),S.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){S.propFix[this.toLowerCase()]=this}),S.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).addClass(t.call(this,e,yt(this)))});if((e=mt(t)).length)while(n=this[u++])if(i=yt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=e[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).removeClass(t.call(this,e,yt(this)))});if(!arguments.length)return this.attr("class","");if((e=mt(t)).length)while(n=this[u++])if(i=yt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=e[a++])while(-1<r.indexOf(" "+o+" "))r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(i,t){var o=typeof i,a="string"===o||Array.isArray(i);return"boolean"==typeof t&&a?t?this.addClass(i):this.removeClass(i):m(i)?this.each(function(e){S(this).toggleClass(i.call(this,e,yt(this),t),t)}):this.each(function(){var e,t,n,r;if(a){t=0,n=S(this),r=mt(i);while(e=r[t++])n.hasClass(e)?n.removeClass(e):n.addClass(e)}else void 0!==i&&"boolean"!==o||((e=yt(this))&&Y.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===i?"":Y.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&-1<(" "+vt(yt(n))+" ").indexOf(t))return!0;return!1}});var xt=/\r/g;S.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=m(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,S(this).val()):n)?t="":"number"==typeof t?t+="":Array.isArray(t)&&(t=S.map(t,function(e){return null==e?"":e+""})),(r=S.valHooks[this.type]||S.valHooks[this.nodeName.toLowerCase()])&&"set"in r&&void 0!==r.set(this,t,"value")||(this.value=t))})):t?(r=S.valHooks[t.type]||S.valHooks[t.nodeName.toLowerCase()])&&"get"in r&&void 0!==(e=r.get(t,"value"))?e:"string"==typeof(e=t.value)?e.replace(xt,""):null==e?"":e:void 0}}),S.extend({valHooks:{option:{get:function(e){var t=S.find.attr(e,"value");return null!=t?t:vt(S.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!A(n.parentNode,"optgroup"))){if(t=S(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=S.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<S.inArray(S.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),S.each(["radio","checkbox"],function(){S.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<S.inArray(S(e).val(),t)}},y.checkOn||(S.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),y.focusin="onfocusin"in C;var bt=/^(?:focusinfocus|focusoutblur)$/,wt=function(e){e.stopPropagation()};S.extend(S.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||E],d=v.call(e,"type")?e.type:e,h=v.call(e,"namespace")?e.namespace.split("."):[];if(o=f=a=n=n||E,3!==n.nodeType&&8!==n.nodeType&&!bt.test(d+S.event.triggered)&&(-1<d.indexOf(".")&&(d=(h=d.split(".")).shift(),h.sort()),u=d.indexOf(":")<0&&"on"+d,(e=e[S.expando]?e:new S.Event(d,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:S.makeArray(t,[e]),c=S.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!x(n)){for(s=c.delegateType||d,bt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||E)&&p.push(a.defaultView||a.parentWindow||C)}i=0;while((o=p[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||d,(l=(Y.get(o,"events")||Object.create(null))[e.type]&&Y.get(o,"handle"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&V(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!V(n)||u&&m(n[d])&&!x(n)&&((a=n[u])&&(n[u]=null),S.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,wt),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,wt),S.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=S.extend(new S.Event,n,{type:e,isSimulated:!0});S.event.trigger(r,null,t)}}),S.fn.extend({trigger:function(e,t){return this.each(function(){S.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return S.event.trigger(e,t,n,!0)}}),y.focusin||S.each({focus:"focusin",blur:"focusout"},function(n,r){var i=function(e){S.event.simulate(r,e.target,S.event.fix(e))};S.event.special[r]={setup:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r);t||e.addEventListener(n,i,!0),Y.access(e,r,(t||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r)-1;t?Y.access(e,r,t):(e.removeEventListener(n,i,!0),Y.remove(e,r))}}});var Tt=C.location,Ct={guid:Date.now()},Et=/\?/;S.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new C.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||S.error("Invalid XML: "+e),t};var St=/\[\]$/,kt=/\r?\n/g,At=/^(?:submit|button|image|reset|file)$/i,Nt=/^(?:input|select|textarea|keygen)/i;function Dt(n,e,r,i){var t;if(Array.isArray(e))S.each(e,function(e,t){r||St.test(n)?i(n,t):Dt(n+"["+("object"==typeof t&&null!=t?e:"")+"]",t,r,i)});else if(r||"object"!==w(e))i(n,e);else for(t in e)Dt(n+"["+t+"]",e[t],r,i)}S.param=function(e,t){var n,r=[],i=function(e,t){var n=m(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!S.isPlainObject(e))S.each(e,function(){i(this.name,this.value)});else for(n in e)Dt(n,e[n],t,i);return r.join("&")},S.fn.extend({serialize:function(){return S.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=S.prop(this,"elements");return e?S.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!S(this).is(":disabled")&&Nt.test(this.nodeName)&&!At.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=S(this).val();return null==n?null:Array.isArray(n)?S.map(n,function(e){return{name:t.name,value:e.replace(kt,"\r\n")}}):{name:t.name,value:n.replace(kt,"\r\n")}}).get()}});var jt=/%20/g,qt=/#.*$/,Lt=/([?&])_=[^&]*/,Ht=/^(.*?):[ \t]*([^\r\n]*)$/gm,Ot=/^(?:GET|HEAD)$/,Pt=/^\/\//,Rt={},Mt={},It="*/".concat("*"),Wt=E.createElement("a");function Ft(o){return function(e,t){"string"!=typeof e&&(t=e,e="*");var n,r=0,i=e.toLowerCase().match(P)||[];if(m(t))while(n=i[r++])"+"===n[0]?(n=n.slice(1)||"*",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function Bt(t,i,o,a){var s={},u=t===Mt;function l(e){var r;return s[e]=!0,S.each(t[e]||[],function(e,t){var n=t(i,o,a);return"string"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}return l(i.dataTypes[0])||!s["*"]&&l("*")}function $t(e,t){var n,r,i=S.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&S.extend(!0,e,r),e}Wt.href=Tt.href,S.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Tt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Tt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":It,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":S.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?$t($t(e,S.ajaxSettings),t):$t(S.ajaxSettings,e)},ajaxPrefilter:Ft(Rt),ajaxTransport:Ft(Mt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=S.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?S(y):S.event,x=S.Deferred(),b=S.Callbacks("once memory"),w=v.statusCode||{},a={},s={},u="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n){n={};while(t=Ht.exec(p))n[t[1].toLowerCase()+" "]=(n[t[1].toLowerCase()+" "]||[]).concat(t[2])}t=n[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||Tt.href)+"").replace(Pt,Tt.protocol+"//"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||"*").toLowerCase().match(P)||[""],null==v.crossDomain){r=E.createElement("a");try{r.href=v.url,r.href=r.href,v.crossDomain=Wt.protocol+"//"+Wt.host!=r.protocol+"//"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&"string"!=typeof v.data&&(v.data=S.param(v.data,v.traditional)),Bt(Rt,v,t,T),h)return T;for(i in(g=S.event&&v.global)&&0==S.active++&&S.event.trigger("ajaxStart"),v.type=v.type.toUpperCase(),v.hasContent=!Ot.test(v.type),f=v.url.replace(qt,""),v.hasContent?v.data&&v.processData&&0===(v.contentType||"").indexOf("application/x-www-form-urlencoded")&&(v.data=v.data.replace(jt,"+")):(o=v.url.slice(f.length),v.data&&(v.processData||"string"==typeof v.data)&&(f+=(Et.test(f)?"&":"?")+v.data,delete v.data),!1===v.cache&&(f=f.replace(Lt,"$1"),o=(Et.test(f)?"&":"?")+"_="+Ct.guid+++o),v.url=f+o),v.ifModified&&(S.lastModified[f]&&T.setRequestHeader("If-Modified-Since",S.lastModified[f]),S.etag[f]&&T.setRequestHeader("If-None-Match",S.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader("Content-Type",v.contentType),T.setRequestHeader("Accept",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+("*"!==v.dataTypes[0]?", "+It+"; q=0.01":""):v.accepts["*"]),v.headers)T.setRequestHeader(i,v.headers[i]);if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u="abort",b.add(v.complete),T.done(v.success),T.fail(v.error),c=Bt(Mt,v,t,T)){if(T.readyState=1,g&&m.trigger("ajaxSend",[T,v]),h)return T;v.async&&0<v.timeout&&(d=C.setTimeout(function(){T.abort("timeout")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,"No Transport");function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&C.clearTimeout(d),c=void 0,p=r||"",T.readyState=0<e?4:0,i=200<=e&&e<300||304===e,n&&(s=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(v,T,n)),!i&&-1<S.inArray("script",v.dataTypes)&&(v.converters["text script"]=function(){}),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader("Last-Modified"))&&(S.lastModified[f]=u),(u=T.getResponseHeader("etag"))&&(S.etag[f]=u)),204===e||"HEAD"===v.type?l="nocontent":304===e?l="notmodified":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l="error",e<0&&(e=0))),T.status=e,T.statusText=(t||l)+"",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?"ajaxSuccess":"ajaxError",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger("ajaxComplete",[T,v]),--S.active||S.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return S.get(e,t,n,"json")},getScript:function(e,t){return S.get(e,void 0,t,"script")}}),S.each(["get","post"],function(e,i){S[i]=function(e,t,n,r){return m(t)&&(r=r||n,n=t,t=void 0),S.ajax(S.extend({url:e,type:i,dataType:r,data:t,success:n},S.isPlainObject(e)&&e))}}),S.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),S._evalUrl=function(e,t,n){return S.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){S.globalEval(e,t,n)}})},S.fn.extend({wrapAll:function(e){var t;return this[0]&&(m(e)&&(e=e.call(this[0])),t=S(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return m(n)?this.each(function(e){S(this).wrapInner(n.call(this,e))}):this.each(function(){var e=S(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=m(t);return this.each(function(e){S(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not("body").each(function(){S(this).replaceWith(this.childNodes)}),this}}),S.expr.pseudos.hidden=function(e){return!S.expr.pseudos.visible(e)},S.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},S.ajaxSettings.xhr=function(){try{return new C.XMLHttpRequest}catch(e){}};var _t={0:200,1223:204},zt=S.ajaxSettings.xhr();y.cors=!!zt&&"withCredentials"in zt,y.ajax=zt=!!zt,S.ajaxTransport(function(i){var o,a;if(y.cors||zt&&!i.crossDomain)return{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,"abort"===e?r.abort():"error"===e?"number"!=typeof r.status?t(0,"error"):t(r.status,r.statusText):t(_t[r.status]||r.status,r.statusText,"text"!==(r.responseType||"text")||"string"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o("error"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&C.setTimeout(function(){o&&a()})},o=o("abort");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}}),S.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),S.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return S.globalEval(e),e}}}),S.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),S.ajaxTransport("script",function(n){var r,i;if(n.crossDomain||n.scriptAttrs)return{send:function(e,t){r=S("<script>").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="<form></form><form></form>",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1<s&&(r=vt(e.slice(s)),e=e.slice(0,s)),m(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),0<a.length&&S.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?S("<div>").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var Gt=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;S.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),m(e))return r=s.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(s.call(arguments)))}).guid=e.guid=e.guid||S.guid++,i},S.holdReady=function(e){e?S.readyWait++:S.ready(!0)},S.isArray=Array.isArray,S.parseJSON=JSON.parse,S.nodeName=A,S.isFunction=m,S.isWindow=x,S.camelCase=X,S.type=w,S.now=Date.now,S.isNumeric=function(e){var t=S.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},S.trim=function(e){return null==e?"":(e+"").replace(Gt,"")},"function"==typeof define&&define.amd&&define("jquery",[],function(){return S});var Yt=C.jQuery,Qt=C.$;return S.noConflict=function(e){return C.$===S&&(C.$=Qt),e&&C.jQuery===S&&(C.jQuery=Yt),S},"undefined"==typeof e&&(C.jQuery=C.$=S),S});
diff --git a/platform/www/lib/scripts/jquery/update.sh b/platform/www/lib/scripts/jquery/update.sh
new file mode 100755
index 0000000..10c29c4
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/update.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env sh
+#
+# This script loads the latest jQuery and jQuery-UI 1.* versions from jQuery's CDN
+#
+# It also loads the 'smoothness' jQuery-UI theme and all referenced images.
+#
+# @author Andreas Gohr <andi@splitbrain.org>
+# @author Stefan Grönke <stefan@gronke.net>
+# @link http://code.jquery.com/
+
+# load version infor from external file
+source ./versions
+JQUI_HOST="https://code.jquery.com/ui/$JQUI_VERSION"
+JQUI_GIT="https://raw.githubusercontent.com/jquery/jquery-ui/$JQUI_VERSION/ui"
+
+# load jQuery
+wget -nv https://code.jquery.com/jquery-${JQ_VERSION}.min.js -O jquery.min.js
+# load jQuery-UI
+wget -nv "$JQUI_HOST/jquery-ui.min.js" -O jquery-ui.min.js
+
+# load the smoothness theme
+mkdir -p jquery-ui-theme/images
+wget -nv -qO- "$JQUI_HOST/themes/smoothness/jquery-ui.css" | sed "s/font-family:[^;]*;//" > jquery-ui-theme/smoothness.css
+images=`gawk 'match($0, /url\("?(images\/[^\)"]+)"?\)/, m) { print m[1] }' jquery-ui-theme/smoothness.css`
+for img in $images
+do
+ wget -nv "$JQUI_HOST/themes/smoothness/$img" -O jquery-ui-theme/$img
+done
+
+# load the localization data for jquery ui
+for LNG in ../../../inc/lang/*
+do
+ CODE=`basename $LNG`
+ wget -nv "$JQUI_GIT/i18n/datepicker-$CODE.js" -O $LNG/jquery.ui.datepicker.js
+ if [ ! -s "$LNG/jquery.ui.datepicker.js" ]; then
+ rm -f $LNG/jquery.ui.datepicker.js
+ fi
+done
+
+# some custom language codes
+wget -nv "$JQUI_GIT/i18n/datepicker-de.js" -O ../../../inc/lang/de-informal/jquery.ui.datepicker.js
+wget -nv "$JQUI_GIT/i18n/datepicker-pt-BR.js" -O ../../../inc/lang/pt-br/jquery.ui.datepicker.js
+wget -nv "$JQUI_GIT/i18n/datepicker-zh-CN.js" -O ../../../inc/lang/zh/jquery.ui.datepicker.js
+wget -nv "$JQUI_GIT/i18n/datepicker-zh-TW.js" -O ../../../inc/lang/zh-tw/jquery.ui.datepicker.js
+wget -nv "$JQUI_GIT/i18n/datepicker-cy-GB.js" -O ../../../inc/lang/cy/jquery.ui.datepicker.js
+
+# strip source maps
+sed -i '/sourceMappingURL/d' *.min.js
diff --git a/platform/www/lib/scripts/jquery/versions b/platform/www/lib/scripts/jquery/versions
new file mode 100644
index 0000000..6ff062a
--- /dev/null
+++ b/platform/www/lib/scripts/jquery/versions
@@ -0,0 +1,3 @@
+# this is loaded from the update.sh script and our PHP code
+JQ_VERSION=3.5.1
+JQUI_VERSION=1.12.1
diff --git a/platform/www/lib/scripts/linkwiz.js b/platform/www/lib/scripts/linkwiz.js
new file mode 100644
index 0000000..d82ca96
--- /dev/null
+++ b/platform/www/lib/scripts/linkwiz.js
@@ -0,0 +1,339 @@
+/**
+ * The Link Wizard
+ *
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ * @author Pierre Spring <pierre.spring@caillou.ch>
+ */
+var dw_linkwiz = {
+ $wiz: null,
+ $entry: null,
+ result: null,
+ timer: null,
+ textArea: null,
+ selected: null,
+ selection: null,
+
+ /**
+ * Initialize the dw_linkwizard by creating the needed HTML
+ * and attaching the eventhandlers
+ */
+ init: function($editor){
+ // position relative to the text area
+ var pos = $editor.position();
+
+ // create HTML Structure
+ if(dw_linkwiz.$wiz)
+ return;
+ dw_linkwiz.$wiz = jQuery(document.createElement('div'))
+ .dialog({
+ autoOpen: false,
+ draggable: true,
+ title: LANG.linkwiz,
+ resizable: false
+ })
+ .html(
+ '<div>'+LANG.linkto+' <input type="text" class="edit" id="link__wiz_entry" autocomplete="off" /></div>'+
+ '<div id="link__wiz_result"></div>'
+ )
+ .parent()
+ .attr('id','link__wiz')
+ .css({
+ 'position': 'absolute',
+ 'top': (pos.top+20)+'px',
+ 'left': (pos.left+80)+'px'
+ })
+ .hide()
+ .appendTo('.dokuwiki:first');
+
+ dw_linkwiz.textArea = $editor[0];
+ dw_linkwiz.result = jQuery('#link__wiz_result')[0];
+
+ // scrollview correction on arrow up/down gets easier
+ jQuery(dw_linkwiz.result).css('position', 'relative');
+
+ dw_linkwiz.$entry = jQuery('#link__wiz_entry');
+ if(JSINFO.namespace){
+ dw_linkwiz.$entry.val(JSINFO.namespace+':');
+ }
+
+ // attach event handlers
+ jQuery('#link__wiz .ui-dialog-titlebar-close').on('click', dw_linkwiz.hide);
+ dw_linkwiz.$entry.keyup(dw_linkwiz.onEntry);
+ jQuery(dw_linkwiz.result).on('click', 'a', dw_linkwiz.onResultClick);
+ },
+
+ /**
+ * handle all keyup events in the entry field
+ */
+ onEntry: function(e){
+ if(e.keyCode == 37 || e.keyCode == 39){ //left/right
+ return true; //ignore
+ }
+ if(e.keyCode == 27){ //Escape
+ dw_linkwiz.hide();
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ if(e.keyCode == 38){ //Up
+ dw_linkwiz.select(dw_linkwiz.selected -1);
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ if(e.keyCode == 40){ //Down
+ dw_linkwiz.select(dw_linkwiz.selected +1);
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ if(e.keyCode == 13){ //Enter
+ if(dw_linkwiz.selected > -1){
+ var $obj = dw_linkwiz.$getResult(dw_linkwiz.selected);
+ if($obj.length > 0){
+ dw_linkwiz.resultClick($obj.find('a')[0]);
+ }
+ }else if(dw_linkwiz.$entry.val()){
+ dw_linkwiz.insertLink(dw_linkwiz.$entry.val());
+ }
+
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ dw_linkwiz.autocomplete();
+ },
+
+ /**
+ * Get one of the results by index
+ *
+ * @param num int result div to return
+ * @returns DOMObject or null
+ */
+ getResult: function(num){
+ DEPRECATED('use dw_linkwiz.$getResult()[0] instead');
+ return dw_linkwiz.$getResult()[0] || null;
+ },
+
+ /**
+ * Get one of the results by index
+ *
+ * @param num int result div to return
+ * @returns jQuery object
+ */
+ $getResult: function(num) {
+ return jQuery(dw_linkwiz.result).find('div').eq(num);
+ },
+
+ /**
+ * Select the given result
+ */
+ select: function(num){
+ if(num < 0){
+ dw_linkwiz.deselect();
+ return;
+ }
+
+ var $obj = dw_linkwiz.$getResult(num);
+ if ($obj.length === 0) {
+ return;
+ }
+
+ dw_linkwiz.deselect();
+ $obj.addClass('selected');
+
+ // make sure the item is viewable in the scroll view
+
+ //getting child position within the parent
+ var childPos = $obj.position().top;
+ //getting difference between the childs top and parents viewable area
+ var yDiff = childPos + $obj.outerHeight() - jQuery(dw_linkwiz.result).innerHeight();
+
+ if (childPos < 0) {
+ //if childPos is above viewable area (that's why it goes negative)
+ jQuery(dw_linkwiz.result)[0].scrollTop += childPos;
+ } else if(yDiff > 0) {
+ // if difference between childs top and parents viewable area is
+ // greater than the height of a childDiv
+ jQuery(dw_linkwiz.result)[0].scrollTop += yDiff;
+ }
+
+ dw_linkwiz.selected = num;
+ },
+
+ /**
+ * deselect a result if any is selected
+ */
+ deselect: function(){
+ if(dw_linkwiz.selected > -1){
+ dw_linkwiz.$getResult(dw_linkwiz.selected).removeClass('selected');
+ }
+ dw_linkwiz.selected = -1;
+ },
+
+ /**
+ * Handle clicks in the result set an dispatch them to
+ * resultClick()
+ */
+ onResultClick: function(e){
+ if(!jQuery(this).is('a')) {
+ return;
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ dw_linkwiz.resultClick(this);
+ return false;
+ },
+
+ /**
+ * Handles the "click" on a given result anchor
+ */
+ resultClick: function(a){
+ dw_linkwiz.$entry.val(a.title);
+ if(a.title == '' || a.title.substr(a.title.length-1) == ':'){
+ dw_linkwiz.autocomplete_exec();
+ }else{
+ if (jQuery(a.nextSibling).is('span')) {
+ dw_linkwiz.insertLink(a.nextSibling.innerHTML);
+ }else{
+ dw_linkwiz.insertLink('');
+ }
+ }
+ },
+
+ /**
+ * Insert the id currently in the entry box to the textarea,
+ * replacing the current selection or at the cursor position.
+ * When no selection is available the given title will be used
+ * as link title instead
+ */
+ insertLink: function(title){
+ var link = dw_linkwiz.$entry.val(),
+ sel, stxt;
+ if(!link) {
+ return;
+ }
+
+ sel = DWgetSelection(dw_linkwiz.textArea);
+ if(sel.start == 0 && sel.end == 0) {
+ sel = dw_linkwiz.selection;
+ }
+
+ stxt = sel.getText();
+
+ // don't include trailing space in selection
+ if(stxt.charAt(stxt.length - 1) == ' '){
+ sel.end--;
+ stxt = sel.getText();
+ }
+
+ if(!stxt && !DOKU_UHC) {
+ stxt=title;
+ }
+
+ // prepend colon inside namespaces for non namespace pages
+ if(dw_linkwiz.textArea.form.id.value.indexOf(':') != -1 &&
+ link.indexOf(':') == -1){
+ link = ':' + link;
+ }
+
+ var so = link.length;
+ var eo = 0;
+ if(dw_linkwiz.val){
+ if(dw_linkwiz.val.open) {
+ so += dw_linkwiz.val.open.length;
+ link = dw_linkwiz.val.open+link;
+ }
+ link += '|';
+ so += 1;
+ if(stxt) {
+ link += stxt;
+ }
+ if(dw_linkwiz.val.close) {
+ link += dw_linkwiz.val.close;
+ eo = dw_linkwiz.val.close.length;
+ }
+ }
+
+ pasteText(sel,link,{startofs: so, endofs: eo});
+ dw_linkwiz.hide();
+
+ // reset the entry to the parent namespace
+ var externallinkpattern = new RegExp('^((f|ht)tps?:)?//', 'i'),
+ entry_value;
+ if (externallinkpattern.test(dw_linkwiz.$entry.val())) {
+ if (JSINFO.namespace) {
+ entry_value = JSINFO.namespace + ':';
+ } else {
+ entry_value = ''; //reset whole external links
+ }
+ } else {
+ entry_value = dw_linkwiz.$entry.val().replace(/[^:]*$/, '')
+ }
+ dw_linkwiz.$entry.val(entry_value);
+ },
+
+ /**
+ * Start the page/namespace lookup timer
+ *
+ * Calls autocomplete_exec when the timer runs out
+ */
+ autocomplete: function(){
+ if(dw_linkwiz.timer !== null){
+ window.clearTimeout(dw_linkwiz.timer);
+ dw_linkwiz.timer = null;
+ }
+
+ dw_linkwiz.timer = window.setTimeout(dw_linkwiz.autocomplete_exec,350);
+ },
+
+ /**
+ * Executes the AJAX call for the page/namespace lookup
+ */
+ autocomplete_exec: function(){
+ var $res = jQuery(dw_linkwiz.result);
+ dw_linkwiz.deselect();
+ $res.html('<img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="" width="16" height="16" />')
+ .load(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ {
+ call: 'linkwiz',
+ q: dw_linkwiz.$entry.val()
+ }
+ );
+ },
+
+ /**
+ * Show the link wizard
+ */
+ show: function(){
+ dw_linkwiz.selection = DWgetSelection(dw_linkwiz.textArea);
+ dw_linkwiz.$wiz.show();
+ dw_linkwiz.$entry.focus();
+ dw_linkwiz.autocomplete();
+
+ // Move the cursor to the end of the input
+ var temp = dw_linkwiz.$entry.val();
+ dw_linkwiz.$entry.val('');
+ dw_linkwiz.$entry.val(temp);
+ },
+
+ /**
+ * Hide the link wizard
+ */
+ hide: function(){
+ dw_linkwiz.$wiz.hide();
+ dw_linkwiz.textArea.focus();
+ },
+
+ /**
+ * Toggle the link wizard
+ */
+ toggle: function(){
+ if(dw_linkwiz.$wiz.css('display') == 'none'){
+ dw_linkwiz.show();
+ }else{
+ dw_linkwiz.hide();
+ }
+ }
+};
diff --git a/platform/www/lib/scripts/locktimer.js b/platform/www/lib/scripts/locktimer.js
new file mode 100644
index 0000000..7bc8a39
--- /dev/null
+++ b/platform/www/lib/scripts/locktimer.js
@@ -0,0 +1,151 @@
+/**
+ * Class managing the timer to display a warning on a expiring lock
+ */
+var dw_locktimer = {
+ timeout: 0,
+ draft: false,
+ timerID: null,
+ lasttime: null,
+ msg: LANG.willexpire,
+ pageid: '',
+ fieldsToSaveAsDraft: [
+ 'input[name=prefix]',
+ 'textarea[name=wikitext]',
+ 'input[name=suffix]',
+ 'input[name=date]',
+ ],
+ callbacks: [],
+
+ /**
+ * Initialize the lock timer
+ *
+ * @param {int} timeout Length of timeout in seconds
+ * @param {bool} draft Whether to save drafts
+ * @param {string} edid Optional; ID of an edit object which has to be present
+ */
+ init: function(timeout,draft,edid){
+ var $edit;
+
+ edid = edid || 'wiki__text';
+
+ $edit = jQuery('#' + edid);
+ if($edit.length === 0 || $edit.attr('readonly')) {
+ return;
+ }
+
+ // init values
+ dw_locktimer.timeout = timeout*1000;
+ dw_locktimer.draft = draft;
+ dw_locktimer.lasttime = new Date();
+
+ dw_locktimer.pageid = jQuery('#dw__editform').find('input[name=id]').val();
+ if(!dw_locktimer.pageid) {
+ return;
+ }
+
+ // register refresh event
+ $edit.keypress(dw_locktimer.refresh);
+ // start timer
+ dw_locktimer.reset();
+ },
+
+ /**
+ * Add another field of the editform to be posted to the server when a draft is saved
+ */
+ addField: function(selector) {
+ dw_locktimer.fieldsToSaveAsDraft.push(selector);
+ },
+
+ /**
+ * Add a callback that is executed when the post request to renew the lock and save the draft returns successfully
+ *
+ * If the user types into the edit-area, then dw_locktimer will regularly send a post request to the DokuWiki server
+ * to extend the page's lock and update the draft. When this request returns successfully, then the draft__status
+ * is updated. This method can be used to add further callbacks to be executed at that moment.
+ *
+ * @param {function} callback the only param is the data returned by the server
+ */
+ addRefreshCallback: function(callback) {
+ dw_locktimer.callbacks.push(callback);
+ },
+
+ /**
+ * (Re)start the warning timer
+ */
+ reset: function(){
+ dw_locktimer.clear();
+ dw_locktimer.timerID = window.setTimeout(dw_locktimer.warning, dw_locktimer.timeout);
+ },
+
+ /**
+ * Display the warning about the expiring lock
+ */
+ warning: function(){
+ dw_locktimer.clear();
+ alert(fixtxt(dw_locktimer.msg));
+ },
+
+ /**
+ * Remove the current warning timer
+ */
+ clear: function(){
+ if(dw_locktimer.timerID !== null){
+ window.clearTimeout(dw_locktimer.timerID);
+ dw_locktimer.timerID = null;
+ }
+ },
+
+ /**
+ * Refresh the lock via AJAX
+ *
+ * Called on keypresses in the edit area
+ */
+ refresh: function(){
+ var now = new Date(),
+ params = 'call=lock&id=' + dw_locktimer.pageid + '&';
+
+ // refresh every half minute only
+ if(now.getTime() - dw_locktimer.lasttime.getTime() <= 30*1000) {
+ return;
+ }
+
+ // POST everything necessary for draft saving
+ if(dw_locktimer.draft && jQuery('#dw__editform').find('textarea[name=wikitext]').length > 0){
+ params += jQuery('#dw__editform').find(dw_locktimer.fieldsToSaveAsDraft.join(', ')).serialize();
+ }
+
+ jQuery.post(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ params,
+ null,
+ 'json'
+ ).done(function dwLocktimerRefreshDoneHandler(data) {
+ dw_locktimer.callbacks.forEach(
+ function (callback) {
+ callback(data);
+ }
+ );
+ });
+ dw_locktimer.lasttime = now;
+ },
+
+ /**
+ * Callback. Resets the warning timer
+ */
+ refreshed: function(data){
+ if (data.errors.length) {
+ data.errors.forEach(function(error) {
+ jQuery('#draft__status').after(
+ jQuery('<div class="error"></div>').text(error)
+ );
+ })
+ }
+
+ jQuery('#draft__status').html(data.draft);
+ if(data.lock !== '1') {
+ return; // locking failed
+ }
+ dw_locktimer.reset();
+ }
+};
+dw_locktimer.callbacks.push(dw_locktimer.refreshed);
diff --git a/platform/www/lib/scripts/media.js b/platform/www/lib/scripts/media.js
new file mode 100644
index 0000000..fda6463
--- /dev/null
+++ b/platform/www/lib/scripts/media.js
@@ -0,0 +1,974 @@
+/**
+ * JavaScript functionality for the media management popup
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Pierre Spring <pierre.spring@caillou.ch>
+ */
+
+var dw_mediamanager = {
+ keepopen: false,
+ hide: false,
+ popup: false,
+ display: false,
+ ext: false,
+ $popup: null,
+
+ // Image insertion opts
+ align: false,
+ link: false,
+ size: false,
+ forbidden_opts: {},
+
+ // File list options
+ view_opts: {list: false, sort: false},
+
+ layout_width: 0,
+
+ // The minimum height of the full-screen mediamanager in px
+ minHeights: {thumbs: 200, rows: 100},
+
+ init: function () {
+ var $content, $tree;
+ $content = jQuery('#media__content');
+ $tree = jQuery('#media__tree');
+ if (!$tree.length) return;
+
+ dw_mediamanager.prepare_content($content);
+
+ dw_mediamanager.attachoptions();
+ dw_mediamanager.initpopup();
+
+ // add the action to autofill the "upload as" field
+ $content
+ .on('change', '#upload__file', dw_mediamanager.suggest)
+ // Attach the image selector action to all links
+ .on('click', 'a.select', dw_mediamanager.select)
+ // Attach deletion confirmation dialog to the delete buttons
+ .on('click', '#media__content a.btn_media_delete', dw_mediamanager.confirmattach)
+ .on('submit', '#mediamanager__done_form', dw_mediamanager.list);
+
+ $tree.dw_tree({
+ toggle_selector: 'img',
+ load_data: function (show_sublist, $clicky) {
+ // get the enclosed link (is always the first one)
+ var $link = $clicky.parent().find('div.li a.idx_dir');
+
+ jQuery.post(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ $link[0].search.substr(1) + '&call=medians',
+ show_sublist,
+ 'html'
+ );
+ },
+
+ toggle_display: function ($clicky, opening) {
+ $clicky.attr('src', DOKU_BASE + 'lib/images/' + (opening ? 'minus' : 'plus') + '.gif');
+ }
+ });
+ $tree.on('click', 'a', dw_mediamanager.list);
+
+ // Init view property
+ dw_mediamanager.set_fileview_list();
+
+ dw_mediamanager.init_options();
+
+ dw_mediamanager.image_diff();
+ dw_mediamanager.init_ajax_uploader();
+
+ // changing opened tab in the file list panel
+ var $page = jQuery('#mediamanager__page');
+ $page.find('div.filelist')
+ .on('click', 'ul.tabs a', dw_mediamanager.list)
+ // loading file details
+ .on('click', 'div.panelContent a', dw_mediamanager.details)
+ // search form
+ .on('submit', '#dw__mediasearch', dw_mediamanager.list)
+ // "upload as" field autofill
+ .on('change', '#upload__file', dw_mediamanager.suggest)
+ // uploaded images
+ .on('click', '.qq-upload-file a', dw_mediamanager.details);
+
+ // changing opened tab in the file details panel
+ $page.find('div.file')
+ .on('click', 'ul.tabs a', dw_mediamanager.details)
+ // "update new version" button
+ .on('submit', '#mediamanager__btn_update', dw_mediamanager.list)
+ // revisions form
+ .on('submit', '#page__revisions', dw_mediamanager.details)
+ .on('click', '#page__revisions a', dw_mediamanager.details)
+ // meta edit form
+ .on('submit', '#mediamanager__save_meta', dw_mediamanager.details)
+ // delete button
+ .on('submit', '#mediamanager__btn_delete', dw_mediamanager.details)
+ // "restore this version" button
+ .on('submit', '#mediamanager__btn_restore', dw_mediamanager.details)
+ // less/more recent buttons in media revisions form
+ .on('submit', '.btn_newer, .btn_older', dw_mediamanager.details);
+
+ dw_mediamanager.update_resizable();
+ dw_mediamanager.layout_width = $page.width();
+ jQuery(window).on('resize', dw_mediamanager.window_resize);
+ },
+
+ init_options: function () {
+ var $options = jQuery('div.filelist div.panelHeader form.options'),
+ $listType, $sortBy, $both;
+ if ($options.length === 0) {
+ return;
+ }
+
+ $listType = $options.find('li.listType');
+ $sortBy = $options.find('li.sortBy');
+ $both = $listType.add($sortBy);
+
+ // Remove the submit button
+ $options.find('button[type=submit]').parent().hide();
+
+ // Prepare HTML for jQuery UI buttonset
+ $both.find('label').each(function () {
+ var $this = jQuery(this);
+ $this.children('input').appendTo($this.parent());
+ });
+
+ // Init buttonset
+ $both.find("input[type='radio']").checkboxradio({icon: false});
+ $both.controlgroup();
+
+ // Change handlers
+ $listType.children('input').change(function () {
+ dw_mediamanager.set_fileview_list();
+ });
+ $sortBy.children('input').change(function (event) {
+ dw_mediamanager.set_fileview_sort();
+ dw_mediamanager.list.call(jQuery('#dw__mediasearch')[0] || this, event);
+ });
+ },
+
+ /**
+ * build the popup window
+ *
+ * @author Dominik Eckelmann <eckelmann@cosmocode.de>
+ */
+ initpopup: function () {
+ var opts, $insp, $insbtn;
+
+ dw_mediamanager.$popup = jQuery(document.createElement('div'))
+ .attr('id', 'media__popup_content')
+ .dialog({
+ autoOpen: false, width: 280, modal: true,
+ draggable: true, title: LANG.mediatitle,
+ resizable: false
+ });
+
+ opts = [
+ {
+ id: 'link', label: LANG.mediatarget,
+ btns: ['lnk', 'direct', 'nolnk', 'displaylnk']
+ },
+ {
+ id: 'align', label: LANG.mediaalign,
+ btns: ['noalign', 'left', 'center', 'right']
+ },
+ {
+ id: 'size', label: LANG.mediasize,
+ btns: ['small', 'medium', 'large', 'original']
+ }
+ ];
+
+ jQuery.each(opts, function (_, opt) {
+ var $p, $l;
+ $p = jQuery(document.createElement('p'))
+ .attr('id', 'media__' + opt.id);
+
+ if (dw_mediamanager.display === "2") {
+ $p.hide();
+ }
+
+ $l = jQuery(document.createElement('label'))
+ .text(opt.label);
+ $p.append($l);
+
+ jQuery.each(opt.btns, function (i, text) {
+ var $btn, $img;
+ $btn = jQuery(document.createElement('button'))
+ .addClass('button')
+ .attr('id', "media__" + opt.id + "btn" + (i + 1))
+ .attr('title', LANG['media' + text])
+ .on('click', bind(dw_mediamanager.setOpt, opt.id));
+
+ $img = jQuery(document.createElement('img'))
+ .attr('src', DOKU_BASE + 'lib/images/media_' + opt.id + '_' + text + '.png');
+
+ $btn.append($img);
+ $p.append($btn);
+ });
+
+ dw_mediamanager.$popup.append($p);
+ });
+
+ // insert button
+ $insp = jQuery(document.createElement('p'));
+ dw_mediamanager.$popup.append($insp);
+
+ $insbtn = jQuery(document.createElement('input'))
+ .attr('id', 'media__sendbtn')
+ .attr('type', 'button')
+ .addClass('button')
+ .val(LANG.mediainsert);
+ $insp.append($insbtn);
+ },
+
+ /**
+ * Insert the clicked image into the opener's textarea
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Dominik Eckelmann <eckelmann@cosmocode.de>
+ * @author Pierre Spring <pierre.spring@caillou.ch>
+ */
+ insert: function (id) {
+ var opts, cb, edid, s;
+
+ // set syntax options
+ dw_mediamanager.$popup.dialog('close');
+
+ opts = '';
+
+ if ({img: 1, swf: 1}[dw_mediamanager.ext] === 1) {
+
+ if (dw_mediamanager.link === '4') {
+ opts = '?linkonly';
+ } else {
+
+ if (dw_mediamanager.link === "3" && dw_mediamanager.ext === 'img') {
+ opts = '?nolink';
+ } else if (dw_mediamanager.link === "2" && dw_mediamanager.ext === 'img') {
+ opts = '?direct';
+ }
+
+ s = parseInt(dw_mediamanager.size, 10);
+ var size = s * 200;
+
+ if (s && s >= 1 && s < 4) {
+ opts += (opts.length) ? '&' : '?';
+ opts += size;
+ if (dw_mediamanager.ext === 'swf') {
+ switch (s) {
+ case 1:
+ opts += 'x62';
+ break;
+ case 2:
+ opts += 'x123';
+ break;
+ case 3:
+ opts += 'x185';
+ break;
+ }
+ }
+ }
+ }
+ }
+ edid = String.prototype.match.call(document.location, /&edid=([^&]+)/);
+ edid = edid ? edid[1] : 'wiki__text';
+ cb = String.prototype.match.call(document.location, /&onselect=([^&]+)/);
+ cb = cb ? cb[1].replace(/[^\w]+/, '') : 'dw_mediamanager_item_select';
+
+ // arguments here only match the dw_mediamanager_item_select function, these will need to change if you override cb with onselect GET param
+ opener[cb](edid, id, opts, dw_mediamanager.align, dw_mediamanager.keepopen);
+ if (!dw_mediamanager.keepopen) {
+ window.close();
+ }
+ opener.focus();
+ return false;
+ },
+
+
+ /**
+ * Prefills the wikiname.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ suggest: function () {
+ var $file, $name, text;
+
+ $file = jQuery(this);
+ $name = jQuery('#upload__name');
+
+ if ($name.val() != '') return;
+
+ if (!$file.length || !$name.length) {
+ return;
+ }
+
+ text = $file.val();
+ text = text.substr(text.lastIndexOf('/') + 1);
+ text = text.substr(text.lastIndexOf('\\') + 1);
+ $name.val(text);
+ },
+
+ /**
+ * list the content of a namespace using AJAX
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Pierre Spring <pierre.spring@caillou.ch>
+ */
+ list: function (event) {
+ var $link, $content, params;
+
+ if (event) {
+ event.preventDefault();
+ }
+
+ jQuery('div.success, div.info, div.error, div.notify').remove();
+
+ $link = jQuery(this);
+
+ //popup
+ $content = jQuery('#media__content');
+
+ if ($content.length === 0) {
+ //fullscreen media manager
+ $content = jQuery('div.filelist');
+
+ if ($link.hasClass('idx_dir')) {
+ //changing namespace
+ jQuery('div.file').empty();
+ jQuery('div.namespaces .selected').removeClass('selected');
+ $link.addClass('selected');
+ }
+ }
+
+ params = 'call=medialist&';
+
+ if ($link[0].search) {
+ params += $link[0].search.substr(1);
+ } else if ($link.is('form')) {
+ params += dw_mediamanager.form_params($link);
+ } else if ($link.closest('form').length > 0) {
+ params += dw_mediamanager.form_params($link.closest('form'));
+ }
+
+ // fetch the subtree
+ dw_mediamanager.update_content($content, params);
+ },
+
+ /**
+ * Returns form parameters
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+ form_params: function ($form) {
+ if (!$form.length) return;
+
+ var action = '';
+ var i = $form[0].action.indexOf('?');
+ if (i >= 0) {
+ action = $form[0].action.substr(i + 1);
+ }
+ return action + '&' + $form.serialize();
+ },
+
+ set_fileview_list: function (new_type) {
+ dw_mediamanager.set_fileview_opt(['list', 'listType', function (new_type) {
+ jQuery('div.filelist div.panelContent ul')
+ .toggleClass('rows', new_type === 'rows')
+ .toggleClass('thumbs', new_type === 'thumbs');
+ }], new_type);
+
+ // FIXME: Move to onchange handler (opt[2])?
+ dw_mediamanager.resize();
+ },
+
+ set_fileview_sort: function (new_sort) {
+ dw_mediamanager.set_fileview_opt(['sort', 'sortBy', function (new_sort) {
+ // FIXME
+ }], new_sort);
+ },
+
+ set_fileview_opt: function (opt, new_val) {
+ if (typeof new_val === 'undefined') {
+ new_val = jQuery('form.options li.' + opt[1] + ' input')
+ .filter(':checked').val();
+ // if new_val is still undefined (because form.options is not in active tab), set to most spacious option
+ if (typeof new_val === 'undefined') {
+ new_val = 'thumbs';
+ }
+ }
+
+ if (new_val !== dw_mediamanager.view_opts[opt[0]]) {
+ opt[2](new_val);
+
+ DokuCookie.setValue(opt[0], new_val);
+
+ dw_mediamanager.view_opts[opt[0]] = new_val;
+ }
+ },
+
+ /**
+ * 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('div.file');
+ params = 'call=mediadetails&';
+
+ if ($link[0].search) {
+ params += $link[0].search.substr(1);
+ } else if ($link.is('form')) {
+ params += dw_mediamanager.form_params($link);
+ } else if ($link.closest('form').length > 0) {
+ params += dw_mediamanager.form_params($link.closest('form'));
+ }
+
+ update_list = ($link[0].id == 'mediamanager__btn_delete' ||
+ $link[0].id == 'mediamanager__btn_restore');
+
+ dw_mediamanager.update_content($content, params, update_list);
+ },
+
+ update_content: function ($content, params, update_list) {
+ var $container;
+
+ jQuery.post(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ params,
+ function (data) {
+ dw_mediamanager.$resizables().resizable('destroy');
+
+ if (update_list) {
+ dw_mediamanager.list.call(jQuery('#mediamanager__page').find('form.options button[type="submit"]')[0]);
+ }
+
+ $content.html(data);
+
+ dw_mediamanager.prepare_content($content);
+ dw_mediamanager.updatehide();
+
+ dw_mediamanager.update_resizable();
+ dw_behaviour.revisionBoxHandler();
+
+ // Make sure that the list view style stays the same
+ dw_mediamanager.set_fileview_list(dw_mediamanager.view_opts.list);
+
+ dw_mediamanager.image_diff();
+ dw_mediamanager.init_ajax_uploader();
+ dw_mediamanager.init_options();
+
+ },
+ 'html'
+ );
+ $container = $content.find('div.panelContent');
+ if ($container.length === 0) {
+ $container = $content;
+ }
+ $container.html('<img src="' + DOKU_BASE + 'lib/images/throbber.gif" alt="..." class="load" />');
+ },
+
+ window_resize: function () {
+ dw_mediamanager.resize();
+
+ dw_mediamanager.opacity_slider();
+ dw_mediamanager.portions_slider();
+ },
+
+ $resizables: function () {
+ return jQuery('#mediamanager__page').find('div.namespaces, div.filelist');
+ },
+
+ /**
+ * Updates mediamanager layout
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+ update_resizable: function () {
+ var $resizables = dw_mediamanager.$resizables();
+
+ $resizables.resizable({
+ handles: (jQuery('html[dir=rtl]').length ? 'w' : 'e'),
+ resize: function (event, ui) {
+ var $page = jQuery('#mediamanager__page');
+ var widthFull = $page.width();
+ var widthResizables = 0;
+ $resizables.each(function () {
+ widthResizables += jQuery(this).width();
+ });
+ var $filePanel = $page.find('div.panel.file');
+
+ // set max width of resizable column
+ var widthOtherResizable = widthResizables - jQuery(this).width();
+ var minWidthNonResizable = parseFloat($filePanel.css("min-width"));
+ var maxWidth = widthFull - (widthOtherResizable + minWidthNonResizable) - 1;
+ $resizables.resizable("option", "maxWidth", maxWidth);
+
+ // width of file panel in % = 100% - width of resizables in %
+ // this calculates with 99.9 and not 100 to overcome rounding errors
+ var relWidthNonResizable = 99.9 - (100 * widthResizables / widthFull);
+ // set width of file panel
+ $filePanel.width(relWidthNonResizable + '%');
+
+ dw_mediamanager.resize();
+
+ dw_mediamanager.opacity_slider();
+ dw_mediamanager.portions_slider();
+ }
+ });
+
+ dw_mediamanager.resize();
+ },
+
+ resize: function () {
+ var $contents = jQuery('#mediamanager__page').find('div.panelContent'),
+ height = jQuery(window).height() - jQuery(document.body).height() +
+ Math.max.apply(null, jQuery.map($contents, function (v) {
+ return jQuery(v).height();
+ }));
+
+ // If the screen is too small, don’t try to resize
+ if (height < dw_mediamanager.minHeights[dw_mediamanager.view_opts.list]) {
+ $contents.add(dw_mediamanager.$resizables()).height('auto');
+ } else {
+ $contents.height(height);
+ dw_mediamanager.$resizables().each(function () {
+ var $this = jQuery(this);
+ $this.height(height + $this.find('div.panelContent').offset().top - $this.offset().top);
+ });
+ }
+ },
+
+ /**
+ * Prints 'select' for image difference representation type
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+ image_diff: function () {
+ if (jQuery('#mediamanager__difftype').length) return;
+
+ var $form = jQuery('#mediamanager__form_diffview');
+ if (!$form.length) return;
+
+ var $label = jQuery(document.createElement('label'));
+ $label.append('<span>' + LANG.media_diff + '</span> ');
+ var $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 () {
+ var $select = jQuery('#mediamanager__difftype');
+ var $content = jQuery('#mediamanager__diff');
+
+ var params = dw_mediamanager.form_params($select.closest('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 $diff = jQuery("#mediamanager__diff");
+ var $slider = $diff.find("div.slider");
+ if (!$slider.length) return;
+
+ var $image = $diff.find('div.imageDiff.opacity div.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.on("slide", function (event, ui) {
+ jQuery('#mediamanager__diff').find('div.imageDiff.opacity div.image2 img').css({opacity: $slider.slider("option", "value")});
+ });
+ },
+
+ /**
+ * Sets options for red line diff slider
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+ portions_slider: function () {
+ var $diff = jQuery("#mediamanager__diff");
+ if (!$diff.length) return;
+
+ var $image1 = $diff.find('div.imageDiff.portions div.image1 img');
+ var $image2 = $diff.find('div.imageDiff.portions div.image2 img');
+ if (!$image1.length || !$image2.length) return;
+
+ $diff.width('100%');
+ $image2.parent().width('97%');
+ $image1.width('100%');
+ $image2.width('100%');
+
+ if ($image1.width() < $diff.width()) {
+ $diff.width($image1.width());
+ }
+
+ $image2.parent().width('50%');
+ $image2.width($image1.width());
+ $image1.width($image1.width());
+
+ var $slider = $diff.find("div.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.on("slide", function (event, ui) {
+ jQuery('#mediamanager__diff').find('div.imageDiff.portions div.image2').css({width: $slider.slider("option", "value") + '%'});
+ });
+ },
+
+ /**
+ * Parse a URI query string to an associative array
+ *
+ * @author Kate Arzamastseva <pshns@ukr.net>
+ */
+ params_toarray: function (str) {
+ var vars = [], hash;
+ var hashes = str.split('&');
+ for (var i = 0; i < hashes.length; i++) {
+ hash = hashes[i].split('=');
+ vars[decodeURIComponent(hash[0])] = decodeURIComponent(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();
+ // toggle list of allowed mime types
+ $content.find('a.allowedmime').on('click', function (event) {
+ event.preventDefault();
+ $toggle = jQuery(this);
+ $list = $toggle.next('span');
+ $list.toggle();
+ }).next('span').hide();
+
+ },
+
+ /**
+ * shows the popup for a image link
+ */
+ select: function (event) {
+ var $link, id, dot, ext;
+
+ event.preventDefault();
+
+ $link = jQuery(this);
+ id = $link.attr('id').substr(2);
+
+ if (!opener) {
+ // if we don't run in popup display example
+ // the id's are a bit wierd and jQuery('#ex_wiki_dokuwiki-128.png')
+ // will not be found by Sizzle (the CSS Selector Engine
+ // used by jQuery), hence the document.getElementById() call
+ jQuery(document.getElementById('ex_' + id.replace(/:/g, '_').replace(/^_/, ''))).dw_toggle();
+ return;
+ }
+
+ dw_mediamanager.ext = false;
+ dot = id.lastIndexOf(".");
+
+ if (-1 === dot) {
+ dw_mediamanager.insert(id);
+ return;
+ }
+
+ ext = id.substr(dot);
+
+ if ({'.jpg': 1, '.jpeg': 1, '.png': 1, '.gif': 1, '.swf': 1}[ext] !== 1) {
+ dw_mediamanager.insert(id);
+ return;
+ }
+
+ // remove old callback from the insert button and set the new one.
+ var $sendbtn = jQuery('#media__sendbtn');
+ $sendbtn.off().on('click', bind(dw_mediamanager.insert, id));
+
+ dw_mediamanager.unforbid('ext');
+ if (ext === '.swf') {
+ dw_mediamanager.ext = 'swf';
+ dw_mediamanager.forbid('ext', {
+ link: ['1', '2'],
+ size: ['4']
+ });
+ } else {
+ dw_mediamanager.ext = 'img';
+ }
+
+ // Set to defaults
+ dw_mediamanager.setOpt('link');
+ dw_mediamanager.setOpt('align');
+ dw_mediamanager.setOpt('size');
+
+ // toggle buttons for detail and linked image, original size
+ jQuery('#media__linkbtn1, #media__linkbtn2, #media__sizebtn4')
+ .toggle(dw_mediamanager.ext === 'img');
+
+ dw_mediamanager.$popup.dialog('open');
+
+ $sendbtn.focus();
+ },
+
+ /**
+ * Deletion confirmation dialog to the delete buttons.
+ *
+ * @author Michael Klier <chi@chimeric.de>
+ * @author Pierre Spring <pierre.spring@caillou.ch>
+ */
+ confirmattach: function (e) {
+ if (!confirm(LANG.del_confirm + "\n" + jQuery(this).attr('title'))) {
+ e.preventDefault();
+ }
+ },
+
+ /**
+ * Creates checkboxes for additional options
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Pierre Spring <pierre.spring@caillou.ch>
+ */
+ attachoptions: function () {
+ var $obj, opts;
+
+ $obj = jQuery('#media__opts');
+ if ($obj.length === 0) {
+ return;
+ }
+
+ opts = [];
+ // keep open
+ if (opener) {
+ opts.push(['keepopen', 'keepopen']);
+ }
+ opts.push(['hide', 'hidedetails']);
+
+ jQuery.each(opts,
+ function (_, opt) {
+ var $box, $lbl;
+ $box = jQuery(document.createElement('input'))
+ .attr('type', 'checkbox')
+ .attr('id', 'media__' + opt[0])
+ .on('click', bind(dw_mediamanager.toggleOption, opt[0]));
+
+ if (DokuCookie.getValue(opt[0])) {
+ $box.prop('checked', true);
+ dw_mediamanager[opt[0]] = true;
+ }
+
+ $lbl = jQuery(document.createElement('label'))
+ .attr('for', 'media__' + opt[0])
+ .text(LANG[opt[1]]);
+
+ $obj.append($box, $lbl, document.createElement('br'));
+ });
+
+ dw_mediamanager.updatehide();
+ },
+
+ /**
+ * Generalized toggler
+ *
+ * @author Pierre Spring <pierre.spring@caillou.ch>
+ */
+ toggleOption: function (variable) {
+ if (jQuery(this).prop('checked')) {
+ DokuCookie.setValue(variable, 1);
+ dw_mediamanager[variable] = true;
+ } else {
+ DokuCookie.setValue(variable, '');
+ dw_mediamanager[variable] = false;
+ }
+ if (variable === 'hide') {
+ dw_mediamanager.updatehide();
+ }
+ },
+
+ /**
+ * Sets the visibility of the image details accordingly to the
+ * chosen hide state
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ updatehide: function () {
+ jQuery('#media__content').find('div.detail').dw_toggle(!dw_mediamanager.hide);
+ },
+
+ /**
+ * set media insertion option
+ *
+ * @author Dominik Eckelmann <eckelmann@cosmocode.de>
+ */
+ setOpt: function (opt, e) {
+ var val, i;
+ if (typeof e !== 'undefined') {
+ val = this.id.substring(this.id.length - 1);
+ } else {
+ val = dw_mediamanager.getOpt(opt);
+ }
+
+ if (val === false) {
+ DokuCookie.setValue(opt, '');
+ dw_mediamanager[opt] = false;
+ return;
+ }
+
+ if (opt === 'link') {
+ if (val !== '4' && dw_mediamanager.link === '4') {
+ dw_mediamanager.unforbid('linkonly');
+ dw_mediamanager.setOpt('align');
+ dw_mediamanager.setOpt('size');
+ } else if (val === '4') {
+ dw_mediamanager.forbid('linkonly', {align: false, size: false});
+ }
+
+ jQuery("#media__size, #media__align").dw_toggle(val !== '4');
+ }
+
+ DokuCookie.setValue(opt, val);
+ dw_mediamanager[opt] = val;
+
+ for (i = 1; i <= 4; i++) {
+ jQuery("#media__" + opt + "btn" + i).removeClass('selected');
+ }
+ jQuery('#media__' + opt + 'btn' + val).addClass('selected');
+ },
+
+ unforbid: function (group) {
+ delete dw_mediamanager.forbidden_opts[group];
+ },
+
+ forbid: function (group, forbids) {
+ dw_mediamanager.forbidden_opts[group] = forbids;
+ },
+
+ allowedOpt: function (opt, val) {
+ var ret = true;
+ jQuery.each(dw_mediamanager.forbidden_opts,
+ function (_, forbids) {
+ ret = forbids[opt] !== false &&
+ jQuery.inArray(val, forbids[opt]) === -1;
+ return ret;
+ });
+ return ret;
+ },
+
+ getOpt: function (opt) {
+ var allowed = bind(dw_mediamanager.allowedOpt, opt);
+
+ // Current value
+ if (dw_mediamanager[opt] !== false && allowed(dw_mediamanager[opt])) {
+ return dw_mediamanager[opt];
+ }
+
+ // From cookie
+ if (DokuCookie.getValue(opt) && allowed(DokuCookie.getValue(opt))) {
+ return DokuCookie.getValue(opt);
+ }
+
+ // size default
+ if (opt === 'size' && allowed('2')) {
+ return '2';
+ }
+
+ // Whatever is allowed, and be it false
+ return jQuery.grep(['1', '2', '3', '4'], allowed)[0] || false;
+ }
+};
+
+/**
+ * Default implementation for the media manager's select action
+ *
+ * Can be overriden with the onselect URL parameter. Is called on the opener context
+ *
+ * @param {string} edid
+ * @param {string} mediaid
+ * @param {string} opts
+ * @param {string} align [none, left, center, right]
+ */
+function dw_mediamanager_item_select(edid, mediaid, opts, align, keepopen) {
+ var alignleft = '';
+ var alignright = '';
+
+ // Get the 2 characters after the cursor to check if we're currently inside an image tag
+ var cursorInImageTag = false;
+ var textArea = jQuery('#' + edid)[0];
+ var selection = DWgetSelection(textArea);
+ selection.end = selection.end + 2;
+ var charsAfterCursor = selection.getText();
+ if (charsAfterCursor === '}}') {
+ cursorInImageTag = true;
+ }
+
+ if (align !== '1') {
+ alignleft = align === '2' ? '' : ' ';
+ alignright = align === '4' ? '' : ' ';
+ }
+ if (keepopen && cursorInImageTag) {
+ selection.start = selection.start + 2;
+ DWsetSelection(selection);
+ }
+ insertTags(edid, '{{' + alignleft + mediaid + opts + alignright + '|', '}}', '');
+}
+
+jQuery(dw_mediamanager.init);
diff --git a/platform/www/lib/scripts/page.js b/platform/www/lib/scripts/page.js
new file mode 100644
index 0000000..77d644a
--- /dev/null
+++ b/platform/www/lib/scripts/page.js
@@ -0,0 +1,201 @@
+/**
+ * Page behaviours
+ *
+ * This class adds various behaviours to the rendered page
+ */
+dw_page = {
+ /**
+ * initialize page behaviours
+ */
+ init: function(){
+ dw_page.sectionHighlight();
+ dw_page.currentIDHighlight();
+ jQuery('a.fn_top').on('mouseover', dw_page.footnoteDisplay);
+ dw_page.makeToggle('#dw__toc h3','#dw__toc > div');
+ },
+
+ /**
+ * Highlight the section when hovering over the appropriate section edit button
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ sectionHighlight: function() {
+ jQuery('form.btn_secedit')
+ .on('mouseover', function(){
+ var $tgt = jQuery(this).parent(),
+ nr = $tgt.attr('class').match(/(\s+|^)editbutton_(\d+)(\s+|$)/)[2],
+ $highlight = jQuery(), // holder for elements in the section to be highlighted
+ $highlightWrap = jQuery('<div class="section_highlight"></div>'); // section highlight wrapper
+
+ // Walk the dom tree in reverse to find the sibling which is or contains the section edit marker
+ while($tgt.length > 0 && !($tgt.hasClass('sectionedit' + nr) || $tgt.find('.sectionedit' + nr).length)) {
+ $tgt = $tgt.prev();
+ $highlight = $highlight.add($tgt);
+ }
+ // insert the section highlight wrapper before the last element added to $highlight
+ $highlight.filter(':last').before($highlightWrap);
+ // and move the elements to be highlighted inside the section highlight wrapper
+ $highlight.detach().appendTo($highlightWrap);
+ })
+ .on('mouseout', function(){
+ // find the section highlight wrapper...
+ var $highlightWrap = jQuery('.section_highlight');
+ // ...move its children in front of it (as siblings)...
+ $highlightWrap.before($highlightWrap.children().detach());
+ // ...and remove the section highlight wrapper
+ $highlightWrap.detach();
+ });
+ },
+
+
+ /**
+ * Highlight internal link pointing to current page
+ *
+ * @author Henry Pan <dokuwiki@phy25.com>
+ */
+ currentIDHighlight: function(){
+ jQuery('a.wikilink1, a.wikilink2').filter('[data-wiki-id="'+JSINFO.id+'"]').wrap('<span class="curid"></div>');
+ },
+
+ /**
+ * Create/get a insitu popup used by the footnotes
+ *
+ * @param target - the DOM element at which the popup should be aligned at
+ * @param popup_id - the ID of the (new) DOM popup
+ * @return the Popup jQuery object
+ */
+ insituPopup: function(target, popup_id) {
+ // get or create the popup div
+ var $fndiv = jQuery('#' + popup_id);
+
+ // popup doesn't exist, yet -> create it
+ if($fndiv.length === 0){
+ $fndiv = jQuery(document.createElement('div'))
+ .attr('id', popup_id)
+ .addClass('insitu-footnote JSpopup')
+ .attr('aria-hidden', 'true')
+ .on('mouseleave', function () {jQuery(this).hide().attr('aria-hidden', 'true');})
+ .attr('role', 'tooltip');
+ jQuery('.dokuwiki:first').append($fndiv);
+ }
+
+ // position() does not support hidden elements
+ $fndiv.show().position({
+ my: 'left top',
+ at: 'left center',
+ of: target
+ }).hide();
+
+ return $fndiv;
+ },
+
+ /**
+ * Display an insitu footnote popup
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Anika Henke <anika@selfthinker.org>
+ */
+ footnoteDisplay: function () {
+ var $content = jQuery(jQuery(this).attr('href')) // Footnote text anchor
+ .parent().siblings('.content').clone();
+
+ if (!$content.length) {
+ return;
+ }
+
+ // prefix ids on any elements with "insitu__" to ensure they remain unique
+ jQuery('[id]', $content).each(function(){
+ var id = jQuery(this).attr('id');
+ jQuery(this).attr('id', 'insitu__' + id);
+ });
+
+ var content = $content.html().trim();
+ // now put the content into the wrapper
+ dw_page.insituPopup(this, 'insitu__fn').html(content)
+ .show().attr('aria-hidden', 'false');
+ },
+
+ /**
+ * Makes an element foldable by clicking its handle
+ *
+ * This is used for the TOC toggling, but can be used for other elements
+ * as well. A state indicator is inserted into the handle and can be styled
+ * by CSS.
+ *
+ * To properly reserve space for the expanded element, the sliding animation is
+ * done on the children of the content. To make that look good and to make sure aria
+ * attributes are assigned correctly, it's recommended to make sure that the content
+ * element contains a single child element only.
+ *
+ * @param {selector} handle What should be clicked to toggle
+ * @param {selector} content This element will be toggled
+ * @param {int} state initial state (-1 = open, 1 = closed)
+ */
+ makeToggle: function(handle, content, state){
+ var $handle, $content, $clicky, $child, setClicky;
+ $handle = jQuery(handle);
+ if(!$handle.length) return;
+ $content = jQuery(content);
+ if(!$content.length) return;
+
+ // we animate the children
+ $child = $content.children();
+
+ // class/display toggling
+ setClicky = function(hiding){
+ if(hiding){
+ $clicky.html('<span>+</span>');
+ $handle.addClass('closed');
+ $handle.removeClass('open');
+ }else{
+ $clicky.html('<span>−</span>');
+ $handle.addClass('open');
+ $handle.removeClass('closed');
+ }
+ };
+
+ $handle[0].setState = function(state){
+ var hidden;
+ if(!state) state = 1;
+
+ // Assert that content instantly takes the whole space
+ $content.css('min-height', $content.height()).show();
+
+ // stop any running animation
+ $child.stop(true, true);
+
+ // was a state given or do we toggle?
+ if(state === -1) {
+ hidden = false;
+ } else if(state === 1) {
+ hidden = true;
+ } else {
+ hidden = $child.is(':hidden');
+ }
+
+ // update the state
+ setClicky(!hidden);
+
+ // Start animation and assure that $toc is hidden/visible
+ $child.dw_toggle(hidden, function () {
+ $content.toggle(hidden);
+ $content.attr('aria-expanded', hidden);
+ $content.css('min-height',''); // remove min-height again
+ }, true);
+ };
+
+ // the state indicator
+ $clicky = jQuery(document.createElement('strong'));
+
+ // click function
+ $handle.css('cursor','pointer')
+ .on('click', $handle[0].setState)
+ .prepend($clicky);
+
+ // initial state
+ $handle[0].setState(state);
+ }
+};
+
+jQuery(dw_page.init);
diff --git a/platform/www/lib/scripts/qsearch.js b/platform/www/lib/scripts/qsearch.js
new file mode 100644
index 0000000..f95515b
--- /dev/null
+++ b/platform/www/lib/scripts/qsearch.js
@@ -0,0 +1,191 @@
+/**
+ * AJAX functions for the pagename quicksearch
+ *
+ * @license GPL2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Adrian Lang <lang@cosmocode.de>
+ * @author Michal Rezler <m.rezler@centrum.cz>
+ */
+jQuery.fn.dw_qsearch = function (overrides) {
+
+ var dw_qsearch = {
+
+ output: '#qsearch__out',
+
+ $inObj: this,
+ $outObj: null,
+ timer: null,
+ curRequest: null,
+
+ /**
+ * initialize the quick search
+ *
+ * Attaches the event handlers
+ *
+ */
+ init: function () {
+ var do_qsearch;
+
+ dw_qsearch.$outObj = jQuery(dw_qsearch.output);
+
+ // objects found?
+ if (dw_qsearch.$inObj.length === 0 ||
+ dw_qsearch.$outObj.length === 0) {
+ return;
+ }
+
+ // attach eventhandler to search field
+ do_qsearch = function () {
+ // abort any previous request
+ if (dw_qsearch.curRequest != null) {
+ dw_qsearch.curRequest.abort();
+ }
+ var value = dw_qsearch.getSearchterm();
+ if (value === '') {
+ dw_qsearch.clear_results();
+ return;
+ }
+ dw_qsearch.$inObj.parents('form').addClass('searching');
+ dw_qsearch.curRequest = jQuery.post(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ {
+ call: 'qsearch',
+ q: encodeURI(value)
+ },
+ dw_qsearch.onCompletion,
+ 'html'
+ );
+ };
+
+ dw_qsearch.$inObj.on('keyup',
+ function () {
+ if (dw_qsearch.timer) {
+ window.clearTimeout(dw_qsearch.timer);
+ dw_qsearch.timer = null;
+ }
+ dw_qsearch.timer = window.setTimeout(do_qsearch, 500);
+ }
+ );
+
+ // attach eventhandler to output field
+ dw_qsearch.$outObj.on('click', dw_qsearch.clear_results);
+ },
+
+ /**
+ * Read search term from input
+ */
+ getSearchterm: function() {
+ return dw_qsearch.$inObj.val();
+ },
+
+ /**
+ * Empty and hide the output div
+ */
+ clear_results: function () {
+ dw_qsearch.$inObj.parents('form').removeClass('searching');
+ dw_qsearch.$outObj.hide();
+ dw_qsearch.$outObj.text('');
+ },
+
+ /**
+ * Callback. Reformat and display the results.
+ *
+ * Namespaces are shortened here to keep the results from overflowing
+ * or wrapping
+ *
+ * @param data The result HTML
+ */
+ onCompletion: function (data) {
+ var max, $links, too_big;
+ dw_qsearch.$inObj.parents('form').removeClass('searching');
+
+ dw_qsearch.curRequest = null;
+
+ if (data === '') {
+ dw_qsearch.clear_results();
+ return;
+ }
+
+ dw_qsearch.$outObj
+ .html(data)
+ .show()
+ .css('white-space', 'nowrap');
+
+ // disable overflow during shortening
+ dw_qsearch.$outObj.find('li').css('overflow', 'visible');
+
+ $links = dw_qsearch.$outObj.find('a');
+ max = dw_qsearch.$outObj[0].clientWidth; // maximum width allowed (but take away paddings below)
+ if (document.documentElement.dir === 'rtl') {
+ max -= parseInt(dw_qsearch.$outObj.css('padding-left'));
+ too_big = function (l) {
+ return l.offsetLeft < 0;
+ };
+ } else {
+ max -= parseInt(dw_qsearch.$outObj.css('padding-right'));
+ too_big = function (l) {
+ return l.offsetWidth + l.offsetLeft > max;
+ };
+ }
+
+ $links.each(function () {
+ var start, length, replace, nsL, nsR, eli, runaway;
+
+ if (!too_big(this)) {
+ return;
+ }
+
+ nsL = this.textContent.indexOf('(');
+ nsR = this.textContent.indexOf(')');
+ eli = 0;
+ runaway = 0;
+
+ while ((nsR - nsL > 3) && too_big(this) && runaway++ < 500) {
+ if (eli !== 0) {
+ // elipsis already inserted
+ if ((eli - nsL) > (nsR - eli)) {
+ // cut left
+ start = eli - 2;
+ length = 2;
+ } else {
+ // cut right
+ start = eli + 1;
+ length = 1;
+ }
+ replace = '';
+ } else {
+ // replace middle with ellipsis
+ start = Math.floor(nsL + ((nsR - nsL) / 2));
+ length = 1;
+ replace = '…';
+ }
+ this.textContent = substr_replace(this.textContent,
+ replace, start, length);
+
+ eli = this.textContent.indexOf('…');
+ nsL = this.textContent.indexOf('(');
+ nsR = this.textContent.indexOf(')');
+ }
+ });
+
+ // reenable overflow
+ dw_qsearch.$outObj.find('li').css('overflow', 'hidden').css('text-overflow', 'ellipsis');
+ }
+
+
+ };
+
+ jQuery.extend(dw_qsearch, overrides);
+
+ if (!overrides.deferInit) {
+ dw_qsearch.init();
+ }
+
+ return dw_qsearch;
+};
+
+jQuery(function () {
+ jQuery('#qsearch__in').dw_qsearch({
+ output: '#qsearch__out'
+ });
+});
diff --git a/platform/www/lib/scripts/script.js b/platform/www/lib/scripts/script.js
new file mode 100644
index 0000000..0e03dcf
--- /dev/null
+++ b/platform/www/lib/scripts/script.js
@@ -0,0 +1,30 @@
+// if jQuery was loaded, let's make it noConflict here.
+if ('function' === typeof jQuery && 'function' === typeof jQuery.noConflict) {
+ jQuery.noConflict();
+}
+
+/**
+ * Some browser detection
+ */
+var clientPC = navigator.userAgent.toLowerCase(); // Get client info
+var is_macos = navigator.appVersion.indexOf('Mac') != -1;
+var is_gecko = ((clientPC.indexOf('gecko')!=-1) && (clientPC.indexOf('spoofer')==-1) &&
+ (clientPC.indexOf('khtml') == -1) && (clientPC.indexOf('netscape/7.0')==-1));
+var is_safari = ((clientPC.indexOf('applewebkit')!=-1) && (clientPC.indexOf('spoofer')==-1));
+var is_khtml = (navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled ));
+if (clientPC.indexOf('opera')!=-1) {
+ var is_opera = true;
+ var is_opera_preseven = (window.opera && !document.childNodes);
+ var is_opera_seven = (window.opera && document.childNodes);
+}
+
+/**
+ * Handler to close all open Popups
+ */
+function closePopups(){
+ jQuery('div.JSpopup').hide();
+}
+
+jQuery(function () {
+ jQuery(document).on('click', closePopups);
+});
diff --git a/platform/www/lib/scripts/search.js b/platform/www/lib/scripts/search.js
new file mode 100644
index 0000000..111dca9
--- /dev/null
+++ b/platform/www/lib/scripts/search.js
@@ -0,0 +1,48 @@
+jQuery(function () {
+ 'use strict';
+
+ var $searchForm = jQuery('.search-results-form');
+ if (!$searchForm.length) {
+ return;
+ }
+
+ var $toggleAssistanceButton = jQuery('<button>')
+ .addClass('toggleAssistant')
+ .attr('type', 'button')
+ .attr('aria-expanded', 'false')
+ .text(LANG.search_toggle_tools)
+ .prependTo($searchForm.find('fieldset'))
+ ;
+
+ $toggleAssistanceButton.on('click', function () {
+ jQuery('.advancedOptions').toggle(0, function () {
+ var $me = jQuery(this);
+ if ($me.attr('aria-hidden')) {
+ $me.removeAttr('aria-hidden');
+ $toggleAssistanceButton.attr('aria-expanded', 'true');
+ DokuCookie.setValue('sa', 'on');
+ } else {
+ $me.attr('aria-hidden', 'true');
+ $toggleAssistanceButton.attr('aria-expanded', 'false');
+ DokuCookie.setValue('sa', 'off');
+ }
+ });
+ });
+
+ if (DokuCookie.getValue('sa') === 'on') {
+ $toggleAssistanceButton.trigger('click');
+ }
+
+ $searchForm.find('.advancedOptions .toggle div.current').on('click', function () {
+ var $me = jQuery(this);
+ $me.parent().siblings().removeClass('open');
+ $me.parent().siblings().find('ul:first').attr('aria-expanded', 'false');
+ $me.parent().toggleClass('open');
+ if ($me.parent().hasClass('open')) {
+ $me.parent().find('ul:first').attr('aria-expanded', 'true');
+ } else {
+ $me.parent().find('ul:first').attr('aria-expanded', 'false');
+ }
+ });
+
+});
diff --git a/platform/www/lib/scripts/textselection.js b/platform/www/lib/scripts/textselection.js
new file mode 100644
index 0000000..16874ba
--- /dev/null
+++ b/platform/www/lib/scripts/textselection.js
@@ -0,0 +1,152 @@
+/**
+ * Text selection related functions.
+ */
+
+/**
+ * selection prototype
+ *
+ * Object that capsulates the selection in a textarea. Returned by DWgetSelection.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function selection_class(){
+ this.start = 0;
+ this.end = 0;
+ this.obj = null;
+ this.scroll = 0;
+ this.fix = 0;
+
+ this.getLength = function(){
+ return this.end - this.start;
+ };
+
+ this.getText = function(){
+ return (!this.obj) ? '' : this.obj.value.substring(this.start,this.end);
+ };
+}
+
+/**
+ * Get current selection/cursor position in a given textArea
+ *
+ * @link http://groups.drupal.org/node/1210
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @link http://linebyline.blogspot.com/2006/11/textarea-cursor-position-in-internet.html
+ * @returns object - a selection object
+ */
+function DWgetSelection(textArea) {
+ var sel = new selection_class();
+
+ textArea.focus();
+ sel.obj = textArea;
+ sel.start = textArea.selectionStart;
+ sel.end = textArea.selectionEnd;
+ sel.scroll = textArea.scrollTop;
+ return sel;
+}
+
+/**
+ * Set the selection
+ *
+ * You need to get a selection object via DWgetSelection() first, then modify the
+ * start and end properties and pass it back to this function.
+ *
+ * @link http://groups.drupal.org/node/1210
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param {selection_class} selection a selection object as returned by DWgetSelection()
+ */
+function DWsetSelection(selection){
+ selection.obj.setSelectionRange(selection.start, selection.end);
+ if(selection.scroll) selection.obj.scrollTop = selection.scroll;
+}
+
+/**
+ * Inserts the given text at the current cursor position or replaces the current
+ * selection
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param {string} text the new text to be pasted
+ * @param {selection_class} selection selection object returned by DWgetSelection
+ * @param {int} opts.startofs number of charcters at the start to skip from new selection
+ * @param {int} opts.endofs number of characters at the end to skip from new selection
+ * @param {boolean} opts.nosel set true if new text should not be selected
+ */
+function pasteText(selection,text,opts){
+ if(!opts) opts = {};
+ // replace the content
+
+ selection.obj.value =
+ selection.obj.value.substring(0, selection.start) + text +
+ selection.obj.value.substring(selection.end, selection.obj.value.length);
+
+ // set new selection
+ if (is_opera) {
+ // Opera replaces \n by \r\n when inserting text.
+ selection.end = selection.start + text.replace(/\r?\n/g, '\r\n').length;
+ } else {
+ selection.end = selection.start + text.length;
+ }
+
+
+ // modify the new selection if wanted
+ if(opts.startofs) selection.start += opts.startofs;
+ if(opts.endofs) selection.end -= opts.endofs;
+
+ // no selection wanted? set cursor to end position
+ if(opts.nosel) selection.start = selection.end;
+
+ DWsetSelection(selection);
+}
+
+
+/**
+ * Format selection
+ *
+ * Apply tagOpen/tagClose to selection in textarea, use sampleText instead
+ * of selection if there is none.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function insertTags(textAreaID, tagOpen, tagClose, sampleText){
+ var txtarea = jQuery('#' + textAreaID)[0];
+
+ var selection = DWgetSelection(txtarea);
+ var text = selection.getText();
+ var opts;
+
+ // don't include trailing space in selection
+ if(text.charAt(text.length - 1) == ' '){
+ selection.end--;
+ text = selection.getText();
+ }
+
+ if(!text){
+ // nothing selected, use the sample text and select it
+ text = sampleText;
+ opts = {
+ startofs: tagOpen.length,
+ endofs: tagClose.length
+ };
+ }else{
+ // place cursor at the end
+ opts = {
+ nosel: true
+ };
+ }
+
+ // surround with tags
+ text = tagOpen + text + tagClose;
+
+ // do it
+ pasteText(selection,text,opts);
+}
+
+/**
+ * Wraps around pasteText() for backward compatibility
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function insertAtCarret(textAreaID, text){
+ var txtarea = jQuery('#' + textAreaID)[0];
+ var selection = DWgetSelection(txtarea);
+ pasteText(selection,text,{nosel: true});
+}
diff --git a/platform/www/lib/scripts/toolbar.js b/platform/www/lib/scripts/toolbar.js
new file mode 100644
index 0000000..b766f8b
--- /dev/null
+++ b/platform/www/lib/scripts/toolbar.js
@@ -0,0 +1,282 @@
+// used to identify pickers
+var pickercounter=0;
+
+/**
+ * Create a toolbar
+ *
+ * @param string tbid ID of the element where to insert the toolbar
+ * @param string edid ID of the editor textarea
+ * @param array tb Associative array defining the buttons
+ * @param bool allowblock Allow buttons creating multiline content
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function initToolbar(tbid,edid,tb, allowblock){
+ var $toolbar, $edit;
+ if (typeof tbid == 'string') {
+ $toolbar = jQuery('#' + tbid);
+ } else {
+ $toolbar = jQuery(tbid);
+ }
+
+ $edit = jQuery('#' + edid);
+
+ if ($toolbar.length == 0 || $edit.length == 0 || $edit.attr('readOnly')) {
+ return;
+ }
+
+ if (typeof allowblock === 'undefined') {
+ allowblock = true;
+ }
+
+ //empty the toolbar area:
+ $toolbar.html('');
+
+ jQuery.each(tb, function (k, val) {
+ if (!tb.hasOwnProperty(k) || (!allowblock && val.block === true)) {
+ return;
+ }
+ var actionFunc, $btn;
+
+ // create new button (jQuery object)
+ $btn = jQuery(createToolButton(val.icon, val.title, val.key, val.id,
+ val['class']));
+
+ // type is a tb function -> assign it as onclick
+ actionFunc = 'tb_'+val.type;
+ if( jQuery.isFunction(window[actionFunc]) ){
+ $btn.on('click', bind(window[actionFunc],$btn,val,edid) );
+ $toolbar.append($btn);
+ return;
+ }
+
+ // type is a init function -> execute it
+ actionFunc = 'addBtnAction'+val.type.charAt(0).toUpperCase()+val.type.substring(1);
+ if( jQuery.isFunction(window[actionFunc]) ){
+ var pickerid = window[actionFunc]($btn, val, edid);
+ if(pickerid !== ''){
+ $toolbar.append($btn);
+ $btn.attr('aria-controls', pickerid);
+ if (actionFunc === 'addBtnActionPicker') {
+ $btn.attr('aria-haspopup', 'true');
+ }
+ }
+ return;
+ }
+
+ alert('unknown toolbar type: '+val.type+' '+actionFunc);
+ });
+}
+
+/**
+ * Button action for format buttons
+ *
+ * @param DOMElement btn Button element to add the action to
+ * @param array props Associative array of button properties
+ * @param string edid ID of the editor textarea
+ * @author Gabriel Birke <birke@d-scribe.de>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function tb_format(btn, props, edid) {
+ var sample = props.sample || props.title;
+ insertTags(edid,
+ fixtxt(props.open),
+ fixtxt(props.close),
+ fixtxt(sample));
+ pickerClose();
+ return false;
+}
+
+/**
+ * Button action for format buttons
+ *
+ * This works exactly as tb_format() except that, if multiple lines
+ * are selected, each line will be formatted seperately
+ *
+ * @param DOMElement btn Button element to add the action to
+ * @param array props Associative array of button properties
+ * @param string edid ID of the editor textarea
+ * @author Gabriel Birke <birke@d-scribe.de>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function tb_formatln(btn, props, edid) {
+ var sample = props.sample || props.title,
+ opts,
+ selection = DWgetSelection(jQuery('#'+edid)[0]);
+
+ sample = fixtxt(sample);
+ props.open = fixtxt(props.open);
+ props.close = fixtxt(props.close);
+
+ // is something selected?
+ if(selection.getLength()){
+ sample = selection.getText();
+ opts = {nosel: true};
+ }else{
+ opts = {
+ startofs: props.open.length,
+ endofs: props.close.length
+ };
+ }
+
+ sample = sample.split("\n").join(props.close+"\n"+props.open);
+ sample = props.open+sample+props.close;
+
+ pasteText(selection,sample,opts);
+
+ pickerClose();
+ return false;
+}
+
+/**
+ * Button action for insert buttons
+ *
+ * @param DOMElement btn Button element to add the action to
+ * @param array props Associative array of button properties
+ * @param string edid ID of the editor textarea
+ * @author Gabriel Birke <birke@d-scribe.de>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function tb_insert(btn, props, edid) {
+ insertAtCarret(edid,fixtxt(props.insert));
+ pickerClose();
+ return false;
+}
+
+/**
+ * Button action for the media popup
+ *
+ * @param DOMElement btn Button element to add the action to
+ * @param array props Associative array of button properties
+ * @param string edid ID of the editor textarea
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function tb_mediapopup(btn, props, edid) {
+ window.open(
+ DOKU_BASE+props.url+encodeURIComponent(NS)+'&edid='+encodeURIComponent(edid),
+ props.name,
+ props.options);
+ return false;
+}
+
+/**
+ * Button action for automatic headlines
+ *
+ * Insert a new headline based on the current section level
+ *
+ * @param DOMElement btn Button element to add the action to
+ * @param array props Associative array of button properties
+ * @param string edid ID of the editor textarea
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function tb_autohead(btn, props, edid){
+ var lvl = currentHeadlineLevel(edid),
+ tags;
+
+ // determine new level
+ lvl += props.mod;
+ if(lvl < 1) lvl = 1;
+ if(lvl > 5) lvl = 5;
+
+ tags = (new Array(8 - lvl)).join('=');
+ insertTags(edid, tags+' ', ' '+tags+"\n", props.text);
+ pickerClose();
+ return false;
+}
+
+
+/**
+ * Add button action for picker buttons and create picker element
+ *
+ * @param jQuery btn Button element to add the action to
+ * @param array props Associative array of button properties
+ * @param string edid ID of the editor textarea
+ * @return boolean If button should be appended
+ * @author Gabriel Birke <birke@d-scribe.de>
+ */
+function addBtnActionPicker($btn, props, edid) {
+ var pickerid = 'picker'+(pickercounter++);
+ var picker = createPicker(pickerid, props, edid);
+ jQuery(picker).attr('aria-hidden', 'true');
+
+ $btn.click(
+ function(e) {
+ pickerToggle(pickerid,$btn);
+ e.preventDefault();
+ return '';
+ }
+ );
+
+ return pickerid;
+}
+
+/**
+ * Add button action for the link wizard button
+ *
+ * @param DOMElement btn Button element to add the action to
+ * @param array props Associative array of button properties
+ * @param string edid ID of the editor textarea
+ * @return boolean If button should be appended
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+function addBtnActionLinkwiz($btn, props, edid) {
+ dw_linkwiz.init(jQuery('#'+edid));
+ jQuery($btn).click(function(e){
+ dw_linkwiz.val = props;
+ dw_linkwiz.toggle();
+ e.preventDefault();
+ return '';
+ });
+ return 'link__wiz';
+}
+
+
+/**
+ * Show/Hide a previously created picker window
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function pickerToggle(pickerid,$btn){
+ var $picker = jQuery('#' + pickerid),
+ pos = $btn.offset();
+ if ($picker.hasClass('a11y')) {
+ $picker.removeClass('a11y').attr('aria-hidden', 'false');
+ } else {
+ $picker.addClass('a11y').attr('aria-hidden', 'true');
+ }
+ var picker_left = pos.left + 3,
+ picker_width = $picker.width(),
+ window_width = jQuery(window).width();
+ if (picker_width > 300) {
+ $picker.css("max-width", "300");
+ picker_width = 300;
+ }
+ if ((picker_left + picker_width + 40) > window_width) {
+ picker_left = window_width - picker_width - 40;
+ }
+ if (picker_left < 0) {
+ picker_left = 0;
+ }
+ $picker.offset({left: picker_left, top: pos.top+$btn[0].offsetHeight+3});
+}
+
+/**
+ * Close all open pickers
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function pickerClose(){
+ jQuery('.picker').addClass('a11y');
+}
+
+
+/**
+ * Replaces \n with linebreaks
+ */
+function fixtxt(str){
+ return str.replace(/\\n/g,"\n");
+}
+
+jQuery(function () {
+ initToolbar('tool__bar','wiki__text',toolbar);
+ jQuery('#tool__bar').attr('role', 'toolbar');
+});
diff --git a/platform/www/lib/scripts/tree.js b/platform/www/lib/scripts/tree.js
new file mode 100644
index 0000000..9b2d245
--- /dev/null
+++ b/platform/www/lib/scripts/tree.js
@@ -0,0 +1,107 @@
+jQuery.fn.dw_tree = function(overrides) {
+ var dw_tree = {
+
+ /**
+ * Delay in ms before showing the throbber.
+ * Used to skip the throbber for fast AJAX calls.
+ */
+ throbber_delay: 500,
+
+ $obj: this,
+
+ toggle_selector: 'a.idx_dir',
+
+ init: function () {
+ this.$obj.on('click', this.toggle_selector, this,
+ this.toggle);
+ jQuery('ul:first', this.$obj).attr('role', 'tree');
+ jQuery('ul', this.$obj).not(':first').attr('role', 'group');
+ jQuery('li', this.$obj).attr('role', 'treeitem');
+ jQuery('li.open > ul', this.$obj).attr('aria-expanded', 'true');
+ jQuery('li.closed > ul', this.$obj).attr('aria-expanded', 'false');
+ jQuery('li.closed', this.$obj).attr('aria-live', 'assertive');
+ },
+
+ /**
+ * Open or close a subtree using AJAX
+ * The contents of subtrees are "cached" until the page is reloaded.
+ * A "loading" indicator is shown only when the AJAX call is slow.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ * @author Pierre Spring <pierre.spring@caillou.ch>
+ */
+ toggle: function (e) {
+ var $listitem, $sublist, timeout, $clicky, show_sublist, dw_tree, opening;
+
+ e.preventDefault();
+
+ dw_tree = e.data;
+ $clicky = jQuery(this);
+ $listitem = $clicky.closest('li');
+ $sublist = $listitem.find('ul').first();
+ opening = $listitem.hasClass('closed');
+ dw_tree.toggle_display($clicky, opening);
+ if ($sublist.is(':visible')) {
+ $listitem.removeClass('open').addClass('closed');
+ $sublist.attr('aria-expanded', 'false');
+ } else {
+ $listitem.removeClass('closed').addClass('open');
+ $sublist.attr('aria-expanded', 'true');
+ }
+
+ // if already open, close by hiding the sublist
+ if (!opening) {
+ $sublist.dw_hide();
+ return;
+ }
+
+ show_sublist = function (data) {
+ $sublist.hide();
+ if (typeof data !== 'undefined') {
+ $sublist.html(data);
+ $sublist.parent().attr('aria-busy', 'false').removeAttr('aria-live');
+ jQuery('li.closed', $sublist).attr('aria-live', 'assertive');
+ }
+ if ($listitem.hasClass('open')) {
+ // Only show if user didn’t close the list since starting
+ // to load the content
+ $sublist.dw_show();
+ }
+ };
+
+ // just show if already loaded
+ if ($sublist.length > 0) {
+ show_sublist();
+ return;
+ }
+
+ //prepare the new ul
+ $sublist = jQuery('<ul class="idx" role="group"/>');
+ $listitem.append($sublist);
+
+ timeout = window.setTimeout(
+ bind(show_sublist, '<li aria-busy="true"><img src="' + DOKU_BASE + 'lib/images/throbber.gif" alt="loading..." title="loading..." /></li>'), dw_tree.throbber_delay);
+
+ dw_tree.load_data(function (data) {
+ window.clearTimeout(timeout);
+ show_sublist(data);
+ }, $clicky);
+ },
+
+ toggle_display: function ($clicky, opening) {
+ },
+
+ load_data: function (show_data, $clicky) {
+ show_data();
+ }
+ };
+
+ jQuery.extend(dw_tree, overrides);
+
+ if (!overrides.deferInit) {
+ dw_tree.init();
+ }
+
+ return dw_tree;
+};
diff --git a/platform/www/lib/styles/all.css b/platform/www/lib/styles/all.css
new file mode 100644
index 0000000..1ae9a02
--- /dev/null
+++ b/platform/www/lib/styles/all.css
@@ -0,0 +1,68 @@
+/**
+ * Basic screen and print styles. These styles are needed for basic DokuWiki functions
+ * regardless of the used template. Templates can override them of course
+ */
+
+div.clearer {
+ clear: both;
+ font-size: 0;
+ line-height: 0;
+ height: 0;
+ overflow: hidden;
+}
+
+/* one of the many clearfix versions */
+.group {
+ display: inline-block;
+}
+.group {
+ display: block;
+}
+.group:before,
+.group:after {
+ content: "";
+ display: table;
+}
+.group:after {
+ clear: both;
+}
+
+div.no {
+ display: inline;
+ margin: 0;
+ padding: 0;
+}
+
+.hidden {
+ display: none;
+}
+
+/* image alignment */
+.medialeft {
+ float: left;
+}
+.mediaright {
+ float: right;
+}
+.mediacenter {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+/* table cell alignment */
+.leftalign { text-align: left; }
+.centeralign { text-align: center; }
+.rightalign { text-align: right; }
+[dir=rtl] .leftalign { text-align: left; }
+[dir=rtl] .centeralign { text-align: center; }
+[dir=rtl] .rightalign { text-align: right; }
+
+/* underline */
+em.u {
+ font-style: normal;
+ text-decoration: underline;
+}
+em em.u {
+ font-style: italic;
+}
diff --git a/platform/www/lib/styles/feed.css b/platform/www/lib/styles/feed.css
new file mode 100644
index 0000000..44b72d7
--- /dev/null
+++ b/platform/www/lib/styles/feed.css
@@ -0,0 +1,63 @@
+rss channel, feed, RDF {
+ font: 80% "Lucida Grande", Verdana, Lucida, Helvetica, Arial, sans-serif;
+ background-color: __background__;
+ color: __text__;
+ margin: 0;
+ padding: 0;
+}
+
+
+link, description, language, managingEditor, copyright, lastBuildDate, date,
+pubDate, generator, webMaster, ttl, docs, tagline, author, copyright,
+generator, content, created, issued, modified, subject, id, format, creator,
+category, image {
+ display: none;
+}
+
+item link, entry id {
+ display: block;
+ color: __extern__;
+ text-decoration: underline;
+}
+
+channel title, feed title {
+ display: block;
+ font-size: 200%;
+ font-weight: bolder;
+ color: __extern__;
+ text-decoration: none;
+ border-bottom: 20px solid __background_alt__;
+}
+
+:root:before {
+ content: "This data file is meant to be read in a XML feed reader. See document source."
+}
+
+item, entry {
+ display: block;
+ margin: 1em 180px 1em 1em;
+ border-bottom: 1px solid __border__;
+ padding-bottom: 1em;
+}
+
+item title, entry title {
+ display: block;
+ background: transparent none;
+ border: 0px solid __background_other__;
+ padding: 0;
+ color: __text_alt__;
+ font-size: 1.4em;
+ font-weight: bold;
+}
+
+item pubDate, entry modified, item date {
+ display: inline;
+ color: __text_neu__;
+ font: 1em trebuchet ms, arial, helvetica, sans-serif;
+}
+
+item description, entry summary {
+ display: block;
+ clear: both;
+ padding-top: 0.5em;
+}
diff --git a/platform/www/lib/styles/geshi.less b/platform/www/lib/styles/geshi.less
new file mode 100644
index 0000000..69749e8
--- /dev/null
+++ b/platform/www/lib/styles/geshi.less
@@ -0,0 +1,144 @@
+/**
+ * GeSHi syntax highlighting styles
+ *
+ * Generated with https://www.dokuwiki.org/tips:geshi_style_builder
+ * Cleaned up with http://cleancss.com/
+ * Manulally LESSified
+ */
+.code {
+ .co0 {
+ color: #666666;
+ font-style: italic;
+ }
+
+ .co4 {
+ color: #cc0000;
+ font-style: italic;
+ }
+
+ .es5 {
+ color: #006699;
+ font-weight: bold;
+ }
+
+ .es6 {
+ color: #009933;
+ font-weight: bold;
+ }
+
+ .kw2 {
+ color: #000000;
+ font-weight: bold;
+ }
+
+ .kw5 {
+ color: #008000;
+ }
+
+ .kw6 {
+ color: #f08;
+ font-weight: bold;
+ }
+
+ .me0 {
+ color: #004000;
+ }
+
+ .nu0 {
+ color: #cc66cc;
+ }
+
+ .re0 {
+ color: #0000ff;
+ }
+
+ .re3 {
+ color: #ff3333;
+ font-weight: bold;
+ }
+
+ .re4 {
+ color: #009999;
+ }
+
+ .re5 {
+ color: #660033;
+ }
+
+ .re7 {
+ color: #991111;
+ }
+
+ .re8 {
+ color: #00b000;
+ }
+
+ .sc-2 {
+ color: #404040;
+ }
+
+ .sy3 {
+ color: #000040;
+ }
+
+ .br0, .sy0 {
+ color: #66cc66;
+ }
+
+ .co1, .coMULTI, .sc-1 {
+ color: #808080;
+ font-style: italic;
+ }
+
+ .co2, .sy1 {
+ color: #339933;
+ }
+
+ .co3, .sy4 {
+ color: #008080;
+ }
+
+ .es0, .es1, .esHARD {
+ color: #000099;
+ font-weight: bold;
+ }
+
+ .es2, .es3, .es4 {
+ color: #660099;
+ font-weight: bold;
+ }
+
+ .kw1, .kw8 {
+ color: #b1b100;
+ }
+
+ .kw10, .kw11, .kw12, .kw9 {
+ color: #003399;
+ font-weight: bold;
+ }
+
+ .kw13, .kw14, .kw15, .kw16, .me1, .me2 {
+ color: #006600;
+ }
+
+ .kw3, .kw7, .sy2 {
+ color: #000066;
+ }
+
+ .kw4, .re2 {
+ color: #993333;
+ }
+
+ .re1, .st0, .st_h {
+ color: #ff0000;
+ }
+
+ li, .li1 {
+ font-weight: normal;
+ vertical-align:top;
+ }
+
+ .ln-xtra {
+ background-color: #ffc;
+ }
+}
diff --git a/platform/www/lib/styles/index.html b/platform/www/lib/styles/index.html
new file mode 100644
index 0000000..977f90e
--- /dev/null
+++ b/platform/www/lib/styles/index.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="refresh" content="0; URL=../../" />
+<meta name="robots" content="noindex" />
+<title>nothing here...</title>
+</head>
+<body>
+<!-- this is just here to prevent directory browsing -->
+</body>
+</html>
diff --git a/platform/www/lib/styles/print.css b/platform/www/lib/styles/print.css
new file mode 100644
index 0000000..a5c39e8
--- /dev/null
+++ b/platform/www/lib/styles/print.css
@@ -0,0 +1,15 @@
+/**
+ * Basic print styles. These styles are needed for basic DokuWiki functions
+ * regardless of the used template. Templates can override them of course
+ */
+
+div.error, /* messages with msg() */
+div.info,
+div.success,
+div.notify,
+.secedit, /* section edit button */
+.a11y, /* accessibly hidden text */
+.JSpopup, /* modal windows */
+#link__wiz {
+ display: none;
+}
diff --git a/platform/www/lib/styles/screen.css b/platform/www/lib/styles/screen.css
new file mode 100644
index 0000000..bbc1e86
--- /dev/null
+++ b/platform/www/lib/styles/screen.css
@@ -0,0 +1,96 @@
+/**
+ * Basic screen styles. These styles are needed for basic DokuWiki functions
+ * regardless of the used template. Templates can override them of course
+ */
+
+/* messages with msg() */
+div.error,
+div.info,
+div.success,
+div.notify {
+ color: #000;
+ background-repeat: no-repeat;
+ background-position: 8px 50%;
+ border: 1px solid;
+ font-size: 90%;
+ margin: 0 0 0.5em;
+ padding: 0.4em;
+ padding-left: 32px;
+ overflow: hidden;
+ border-radius: 5px;
+}
+
+[dir=rtl] div.error,
+[dir=rtl] div.info,
+[dir=rtl] div.success,
+[dir=rtl] div.notify {
+ background-position: 99% 50%;
+ padding-left: .4em;
+ padding-right: 32px;
+}
+
+div.error {
+ background-color: #fcc;
+ background-image: url(../images/error.png);
+ border-color: #ebb;
+}
+
+div.info {
+ background-color: #ccf;
+ background-image: url(../images/info.png);
+ border-color: #bbe;
+}
+
+div.success {
+ background-color: #cfc;
+ background-image: url(../images/success.png);
+ border-color: #beb;
+}
+
+div.notify {
+ background-color: #ffc;
+ background-image: url(../images/notify.png);
+ border-color: #eeb;
+}
+
+/* modal windows */
+.JSpopup,
+#link__wiz {
+ position: absolute;
+ background-color: #fff;
+ color: #000;
+ z-index: 20;
+ overflow: hidden;
+}
+
+#link__wiz .ui-dialog-content {
+ padding-left: 0;
+ padding-right: 0;
+}
+
+/* media manager popup toggle buttons */
+
+#media__popup_content button.button {
+ border: 1px outset;
+}
+
+#media__popup_content button.selected {
+ border-style: inset;
+}
+
+/* hide something accessibly
+ (e.g. for screen readers or to keep access keys working) */
+.a11y {
+ position: absolute !important;
+ left: -99999em !important;
+ top: auto !important;
+ width: 1px !important;
+ height: 1px !important;
+ overflow: hidden !important;
+}
+[dir=rtl] .a11y {
+ left: auto !important;
+ right: -99999em !important;
+}
+
+@import "geshi.less";
diff --git a/platform/www/lib/tpl/acervus/COPYING b/platform/www/lib/tpl/acervus/COPYING
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/platform/www/lib/tpl/acervus/README.md b/platform/www/lib/tpl/acervus/README.md
new file mode 100644
index 0000000..63ba921
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/README.md
@@ -0,0 +1,3 @@
+# gta-dokuwiki-theme
+
+GTA's dokuwiki theme code, based in White Theme.
diff --git a/platform/www/lib/tpl/acervus/conf/default.php b/platform/www/lib/tpl/acervus/conf/default.php
new file mode 100644
index 0000000..089c8d1
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/conf/default.php
@@ -0,0 +1,11 @@
+<?php
+/*
+ * default configuration settings
+ *
+ * @author Lee, Kwangyoung <ipari@leaflette.com>
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ */
+
+$conf['numberedHeading'] = 0;
+$conf['tocPosition'] = 'wikipedia';
+$conf['footer'] = '';
diff --git a/platform/www/lib/tpl/acervus/conf/metadata.php b/platform/www/lib/tpl/acervus/conf/metadata.php
new file mode 100644
index 0000000..e85feee
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/conf/metadata.php
@@ -0,0 +1,11 @@
+<?php
+/*
+ * default configuration settings
+ *
+ * @author Lee, Kwangyoung <ipari@leaflette.com>
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ */
+
+$meta['numberedHeading'] = array('onoff');
+$meta['tocPosition'] = array('multichoice', '_choices' => array('dokuwiki', 'wikipedia'));
+$meta['footer'] = array('string');
diff --git a/platform/www/lib/tpl/acervus/css/basic.less b/platform/www/lib/tpl/acervus/css/basic.less
new file mode 100644
index 0000000..9bce960
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/css/basic.less
@@ -0,0 +1,506 @@
+
+// @font-face {
+// font-family: 'Norwester';
+// src: url('css/fonts/Norwester-Regular.eot');
+// src: url('css/fonts/Norwester-Regular.eot?#iefix') format('embedded-opentype'),
+// url('css/fonts/Norwester-Regular.woff') format('woff'),
+// url('css/fonts/Norwester-Regular.ttf') format('truetype');
+// font-weight: normal;
+// font-style: normal;
+// }
+
+
+/**
+ * This file provides the most basic styles.
+ */
+
+html,
+body {
+ margin: 0;
+ padding: 0;
+ background-color: @ini_background;
+ color: @ini_text;
+}
+
+body {
+ font: normal @ini_font_size/@ini_line_height 'EB Garamond','Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ /* default font size: 100% => 16px; 93.75% => 15px; 87.5% => 14px; 81.25% => 13px; 75% => 12px */
+ -webkit-text-size-adjust: 100%;
+ -moz-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+ text-size-adjust: 100%;
+}
+
+/* headers */
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font: normal @ini_font_size/@ini_line_height 'EB Garamond','Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ padding: 0;
+ clear: left;
+ font-weight: 600;
+}
+
+
+[dir=rtl] h1,
+[dir=rtl] h2,
+[dir=rtl] h3,
+[dir=rtl] h4,
+[dir=rtl] h5,
+[dir=rtl] h6 {
+ clear: right;
+}
+
+h1 {
+ font-size: 2em;
+ margin: 0 0 0.444em;
+}
+h2 {
+ font-size: 1.666em;
+ margin: 0 0 0.5em;
+ border-bottom: 1px solid @ini_border;
+}
+h3 {
+ font-size: 1.4em;
+ margin: 0 0 0.5em;
+ border-bottom: 1px solid @ini_border;
+}
+h4 {
+ font-size: 1.2em;
+ margin: 0 0 0.4em;
+}
+h5 {
+ font-size: 1em;
+ margin: 0 0 0.4em;
+}
+h6 {
+ font-size: 0.8em;
+ margin: 0 0 0.4em;
+}
+
+/* basic bargins and paddings */
+p,
+ul,
+ol,
+dl,
+pre,
+table,
+hr,
+blockquote,
+figure,
+details,
+fieldset,
+address {
+ margin: 0 0 1.4em 0; /* bottom margin = line-height */
+ padding: 0;
+}
+
+blockquote blockquote {
+ margin: 0;
+ padding: 0 0.5em;
+}
+
+div,
+video,
+audio {
+ margin: 0;
+ padding: 0;
+}
+
+/* lists */
+li,
+dd {
+ padding: 0;
+ margin: 0 0 0 1.5em;
+}
+[dir=rtl] li,
+[dir=rtl] dd {
+ margin: 0 1.5em 0 0;
+}
+dt {
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+
+li ul,
+li ol,
+li dl,
+dl ul,
+dl ol,
+dl dl {
+ margin-bottom: 0;
+ padding: 0;
+}
+li li {
+ font-size: 100%;
+}
+
+ul { list-style: square outside; }
+ol { list-style: decimal outside; }
+ol ol { list-style-type: lower-alpha; }
+ol ol ol { list-style-type: upper-roman; }
+ol ol ol ol { list-style-type: upper-alpha; }
+ol ol ol ol ol { list-style-type: lower-roman; }
+
+/*____________ tables ____________*/
+
+table {
+ border-collapse: collapse;
+ empty-cells: show;
+ border-spacing: 0;
+ border: 1px solid @ini_border;
+}
+
+caption {
+ caption-side: top;
+ text-align: left;
+}
+[dir=rtl] caption {
+ text-align: right;
+}
+
+th,
+td {
+ padding: .3em .5em;
+ margin: 0;
+ vertical-align: top;
+ border: 1px solid @ini_border;
+}
+th {
+ font-weight: bold;
+ background-color: @ini_background_alt;
+ text-align: left;
+}
+[dir=rtl] th {
+ text-align: right;
+}
+
+
+/*____________ links ____________*/
+
+a {
+ outline: none;
+ word-wrap: break-word;
+}
+a:link,
+a:visited {
+ text-decoration: none;
+ color: @ini_link;
+}
+a:link:hover,
+a:visited:hover,
+a:link:focus,
+a:visited:focus,
+a:link:active,
+a:visited:active {
+ text-decoration: underline;
+}
+
+
+/* misc */
+img {
+ border-width: 0;
+ vertical-align: bottom;
+ height: auto;
+}
+
+img,
+object,
+iframe,
+video,
+audio,
+select {
+ max-width: 100%;
+}
+
+hr {
+ border-top: solid @ini_border;
+ border-bottom: solid @ini_background;
+ border-width: 1px 0;
+ height: 0;
+ text-align: center;
+ clear: both;
+}
+
+del {
+ color: @ini_text_alt;
+}
+
+pre {
+ overflow: auto;
+ word-wrap: normal;
+ direction: ltr;
+ unicode-bidi: bidi-override;
+ text-align: left;
+}
+
+
+acronym,
+abbr {
+ cursor: help;
+ border-bottom: 1px dotted;
+ font-style: normal;
+ text-decoration: none;
+}
+em acronym,
+em abbr {
+ font-style: italic;
+}
+
+mark {
+ background-color: @ini_highlight;
+ color: inherit;
+}
+
+pre,
+code,
+samp,
+kbd {
+ font-family: Consolas, "Andale Mono WT", "Andale Mono", "Bitstream Vera Sans Mono", "Nimbus Mono L", Monaco, "Courier New", monospace;
+ /* same font stack should be used for ".dokuwiki table.diff td" in _diff.css */
+ font-size: 1em;
+ padding: 0 4px;
+ border: 1px solid @ini_border;
+ direction: ltr;
+ text-align: left;
+ background-color: @ini_background_alt;
+ color: @ini_text
+}
+pre {
+ overflow: auto;
+ word-wrap: normal;
+ border: 1px solid @ini_border;
+ padding: .7em 1em;
+}
+
+blockquote {
+ padding: 0.5em;
+ border: solid @ini_border;
+ background-color: @ini_background_alt;
+ border-width: 0 0 0 .25em;
+}
+[dir=rtl] blockquote {
+ border-width: 0 .25em 0 0;
+}
+q:before,
+q:after {
+ content: '';
+}
+
+sub,
+sup {
+ font-size: .8em;
+ line-height: 1;
+}
+sub {
+ vertical-align: sub;
+}
+sup {
+ vertical-align: super;
+}
+
+small {
+ font-size: .8em;
+}
+
+/*____________ forms ____________*/
+
+/* for all of the form styles, style.ini colours are not used on purpose (except for fieldset border) */
+
+form {
+ display: inline;
+ margin: 0;
+ padding: 0;
+}
+fieldset {
+ padding: .7em 1em 0;
+ padding: .7rem 1rem; /* for those browsers understanding :last-child */
+ border: 1px solid @ini_text_alt;
+}
+fieldset > :last-child {
+ margin-bottom: 0;
+}
+legend {
+ margin: 0;
+ padding: 0 .1em;
+}
+label {
+ vertical-align: middle;
+ cursor: pointer;
+}
+
+input,
+textarea,
+button,
+select,
+optgroup,
+option,
+keygen,
+output,
+meter,
+progress {
+ font: inherit;
+ font-weight: normal;
+ color: #333;
+ background-color: #fff;
+ line-height: normal;
+ margin: 0;
+ vertical-align: middle;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+select {
+ max-width: 100%;
+}
+optgroup {
+ font-style: italic;
+ font-weight: bold;
+}
+option {
+ font-style: normal;
+ font-weight: normal;
+}
+
+input,
+textarea,
+select,
+keygen {
+ border: 1px solid #ccc;
+ box-shadow: inset 0 0 1px #eee;
+ border-radius: 2px;
+}
+input:active,
+input:focus,
+textarea:active,
+textarea:focus,
+select:active,
+select:focus,
+keygen:active,
+keygen:focus {
+ border-color: #999;
+}
+input[type=radio],
+input[type=checkbox],
+input[type=image] {
+ padding: 0;
+ border-style: none;
+ box-shadow: none;
+}
+
+/* all types of buttons */
+input[type=submit],
+input[type=button],
+input[type=reset],
+input.button,
+a.button,
+button,
+.qq-upload-button {
+ color: #333;
+ background-color: #eee;
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPgo8bGluZWFyR3JhZGllbnQgaWQ9Imc4MjQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRkZGRkYiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNGNEY0RjQiIG9mZnNldD0iMC4zIi8+PHN0b3Agc3RvcC1jb2xvcj0iI0VFRUVFRSIgb2Zmc2V0PSIwLjk5Ii8+PHN0b3Agc3RvcC1jb2xvcj0iI0NDQ0NDQyIgb2Zmc2V0PSIuOTkiLz4KPC9saW5lYXJHcmFkaWVudD4KPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNnODI0KSIgLz4KPC9zdmc+);
+ background-image: linear-gradient(to bottom, #ffffff 0%, #f4f4f4 30%, #eeeeee 99%, #cccccc 99%);
+ border: 1px solid #ccc;
+ border-radius: 2px;
+ padding: .1em .5em;
+ cursor: pointer;
+}
+
+input[type=submit]:hover,
+input[type=submit]:active,
+input[type=submit]:focus,
+input[type=button]:hover,
+input[type=button]:active,
+input[type=button]:hover,
+input[type=reset]:hover,
+input[type=reset]:active,
+input[type=reset]:hover,
+input.button:hover,
+input.button:active,
+input.button:focus,
+a.button:hover,
+a.button:active,
+a.button:focus,
+button:hover,
+button:active,
+button:focus,
+.qq-upload-button:hover {
+ border-color: #999;
+ background-color: #ddd;
+ background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPgo8bGluZWFyR3JhZGllbnQgaWQ9Imc2NzAiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRkZGRkYiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNGNEY0RjQiIG9mZnNldD0iMC4zIi8+PHN0b3Agc3RvcC1jb2xvcj0iI0RERERERCIgb2Zmc2V0PSIwLjk5Ii8+PHN0b3Agc3RvcC1jb2xvcj0iI0JCQkJCQiIgb2Zmc2V0PSIuOTkiLz4KPC9saW5lYXJHcmFkaWVudD4KPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNnNjcwKSIgLz4KPC9zdmc+);
+ background-image: linear-gradient(to bottom, #ffffff 0%, #f4f4f4 30%, #dddddd 99%, #bbbbbb 99%);
+}
+
+input::-moz-focus-inner,
+button::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+input[disabled],
+button[disabled],
+select[disabled],
+textarea[disabled],
+option[disabled],
+input[readonly],
+button[readonly],
+select[readonly],
+textarea[readonly] {
+ cursor: auto;
+ opacity: .5;
+ background-color: #eee;
+}
+
+
+.img-reporte {
+ width: 30%;
+ float: right;
+ margin: 0 0 10px 10px;
+ padding: 5px;
+ border: #ccc 1px solid;
+ /*background: #E2E1E0;*/
+}
+
+@media (max-width: 600px) {
+.img-reporte {
+ width: 100%;
+ float: right;
+ margin: 0 0 10px 10px;
+ padding: 5px;
+ border: #ccc 1px solid;
+ /*background: #E2E1E0;*/
+}
+
+
+}
+
+
+/* CAMBIOS VARIOS */
+
+.dokuwiki textarea.edit, textarea.widearea-fullscreen {
+ font-family: monospace !important;
+ font-size: 1em !important;
+}
+
+#dokuwiki__content {
+ text-align: justify;
+}
+
+.footnotes {
+ font-size: 80%;
+}
+
+.fn {
+ border-bottom: 1px #e9e9e9 solid;
+ padding: 5px;
+ opacity: 0.8;
+}
+
+li.level1, li.level2, li.level3, li.level4, li.level5 {
+ text-align: left;
+}
diff --git a/platform/www/lib/tpl/acervus/css/content.less b/platform/www/lib/tpl/acervus/css/content.less
new file mode 100644
index 0000000..4d72c5d
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/css/content.less
@@ -0,0 +1,361 @@
+/* content */
+#dokuwiki__content {
+ /* existing wikipage */
+ a.wikilink1 {
+ color: @ini_existing;
+ }
+ /* not existing wikipage */
+ a.wikilink2 {
+ border-bottom: 1px dashed @ini_missing;
+ color: @ini_missing !important;
+ }
+ /*a.wikilink2:link,
+ a.wikilink2:visited {
+ border-bottom: none;
+ }*/
+ a.wikilink2:hover,
+ a.wikilink2:active,
+ a.wikilink2:focus {
+ /* border-bottom-width: 0; */
+ text-decoration: none;
+ color: #31363B !important;
+ border-bottom: 1px dashed #31363B;
+
+ }
+
+ /*____________ Numbered Headings ____________*/
+
+ .page.numbered_heading {
+ /* TOC */
+
+ // counter-reset: toc-level0;
+ // #dw__toc ul {
+ //
+ // }
+ //
+ //
+ // #dw__toc li.level1 > div > a:before {
+ // color: @ini_numbered_heading;
+ // content: counter(toc-level0) ". ";
+ // counter-increment: toc-level0;
+ // }
+ //
+ // #dw__toc li.level1 {
+ // counter-reset: toc-level1;
+ // }
+ //
+ // #dw__toc li.level2 > div > a:before {
+ // color: @ini_numbered_heading;
+ // content: counter(toc-level0) "." counter(toc-level1) ". ";
+ // counter-increment: toc-level1;
+ // }
+ // #dw__toc li.level2 {
+ // counter-reset: toc-level2;
+ // }
+ // #dw__toc li.level3 > div > a:before {
+ // color: @ini_numbered_heading;
+ // content: counter(toc-level0) "." counter(toc-level1) "." counter(toc-level2) ". ";
+ // counter-increment: toc-level2;
+ // }
+ // #dw__toc li.level3 {
+ // counter-reset: toc-level3;
+ // }
+ // #dw__toc li.level4 > div > a:before {
+ // color: @ini_numbered_heading;
+ // content: counter(toc-level0) "." counter(toc-level1) "." counter(toc-level2) "." counter(toc-level3) ". ";
+ // counter-increment: toc-level3;
+ // }
+
+ /* Contents */
+ counter-reset: level1;
+ h2:before {
+ color: @ini_numbered_heading;
+ content: counter(level1) ". ";
+ counter-increment: level1;
+ }
+ h2 {
+ counter-reset: level2;
+ }
+ h3:before {
+ color: @ini_numbered_heading;
+ content: counter(level1) "." counter(level2) ". ";
+ counter-increment: level2;
+ }
+ h3 {
+ counter-reset: level3;
+ }
+ h4:before {
+ color: @ini_numbered_heading;
+ content: counter(level1) "." counter(level2) "." counter(level3) ". ";
+ counter-increment: level3;
+ }
+ h2:before,
+ h3:before,
+ h4:before {
+ font-size: 90%;
+ }
+
+ #dw__toc h3:before {
+ content: '';
+ }
+ }
+
+ div.license {
+ margin: 0 -1em;
+ padding: .4em 1em;
+ background-color: @ini_background_alt;
+ }
+ .medialeft {
+ margin-right: .6em;
+ }
+ .mediaright {
+ margin-left: .6em;
+ }
+}
+
+/* table of contents */
+#dw__toc {
+ float: inherit;
+ display: inline-block;
+ width: auto;
+ margin: 0 0 1.4em 0;
+ border: 1px solid @ini_border;
+ background-color: @ini_background;
+
+ span {
+ float: right;
+ }
+ h3,
+ div {
+ padding: .4em .8em;
+ }
+ h3.toggle {
+ margin: 0;
+ font-size: 1em;
+ }
+ h3.closed {
+ border: none;
+ }
+ a {
+ color: @ini_text;
+ }
+}
+
+/* any link to current page */
+.dokuwiki span.curid a {
+ font-weight: bold;
+}
+
+/* undo "clever" styling from fileuploader.js */
+.qq-upload-button {
+ cursor: inherit !important;
+ direction: inherit !important;
+}
+.qq-upload-button input {
+ font-family: inherit !important;
+ font-size: 1em !important;
+ margin: inherit !important;
+ opacity: 1 !important;
+ padding: inherit !important;
+ position: static !important;
+}
+
+.JSpopup {
+ padding: 0.2em 0.4em;
+ border: 1px solid @ini_border;
+}
+
+/* editor */
+.dokuwiki .editBar {
+ .editButtons,
+ .summary {
+ display: block !important;
+ margin-bottom: .6em;
+ }
+}
+
+
+/* fields */
+.dokuwiki fieldset {
+ width: auto;
+ text-align:left;
+ margin: 0 0 1em 0;
+ padding: 1em;
+ border: none;
+ background: @ini_background_alt;
+
+ legend {
+ display: block;
+ padding: 0;
+ font-weight: bold;
+ font-size: 1.4em;
+ }
+
+ span {
+ display: inline-block;
+ margin: 0 0 .2em 0;
+ vertical-align: top;
+ }
+
+ label.block {
+ text-align: left;
+
+ span {
+ display: block;
+ }
+
+ input.edit {
+ width: 100%;
+ max-width: 400px;
+ }
+ }
+}
+
+/* login */
+#dw__login,
+#dw__register {
+ label.simple {
+ margin-left: 0;
+ }
+}
+
+/* admin */
+/* _admin.less in default template */
+
+/* main task grouped in two columns */
+.dokuwiki div.ui-admin {
+ ul.admin_tasks,
+ ul.admin_plugins {
+ float: left;
+ width: 40%;
+ list-style-type: none;
+
+ /* general menu item styling */
+ li {
+ margin: 0 0 0.5em 0;
+ white-space: nowrap;
+
+ a span {
+ display: inline-block;
+
+ &.icon {
+ vertical-align: top;
+ margin-right: 0.5em;
+
+ svg {
+ width: 1.5em;
+ height: 1.5em;
+ }
+ }
+
+ &.prompt {
+ white-space: normal;
+ }
+ }
+ }
+ }
+ [dir=rtl] & ul.admin_tasks,
+ [dir=rtl] & ul.admin_plugins {
+ float: right;
+ }
+}
+
+
+/* DokuWiki version */
+#admin__version {
+ clear: left;
+ margin-bottom: 2em;
+ color: @ini_text_alt;
+}
+[dir=rtl] & #admin__version {
+ clear: right;
+ float: left;
+}
+
+
+/* config */
+#config__manager {
+ fieldset {
+ margin: 0 0 2em 0;
+ padding: 0;
+ border: none;
+ background: none;
+
+ legend {
+ text-align: left;
+ font-size: 1.666em; // same as h3
+ }
+ div.table {
+ width: 100%;
+ padding: 0;
+ background: @ini_background_alt;
+ }
+ table {
+ width: 100%;
+ }
+ td.label {
+ padding: .4em .4em 1em;
+ span.outkey {
+ margin-top: -1.2em;
+ }
+ }
+ td {
+ div.input {
+ width: auto;
+ padding: 2px 4px;
+ }
+ input.edit {
+ padding: 4px 6px;
+ }
+ input.edit,
+ select.edit,
+ textarea.edit {
+ width: 100%;
+ border: none;
+ }
+ }
+ div.selection {
+ width: 40%;
+ padding: 2px 4px;
+
+ label {
+ width: 90%;
+ }
+ }
+ div.other {
+ background: none;
+ }
+ }
+}
+
+/* media manager */
+#mediamanager__page {
+ min-width: 0 !important;
+
+ .namespaces {
+ min-width: 0;
+ }
+ .filelist {
+ min-width: 0;
+ .rows li {
+ dt a {
+ vertical-align: top;
+ }
+ dt a img {
+ max-width: 100%;
+ }
+ .name,
+ .size,
+ .filesize,
+ .date {
+ white-space: normal;
+ word-wrap: break-word;
+ }
+ }
+ }
+ .file {
+ min-width: 0;
+ }
+}
+
+pre {white-space: pre-wrap;}
diff --git a/platform/www/lib/tpl/acervus/css/design.less b/platform/www/lib/tpl/acervus/css/design.less
new file mode 100644
index 0000000..182721a
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/css/design.less
@@ -0,0 +1,301 @@
+/* general */
+.btn_icon {
+ text-indent: -9999px;
+}
+
+.sidebar,
+.breadcrumbs {
+ a,
+ a.wikilink1 {
+ color: @ini_text;
+ }
+ a:hover,
+ a.wikilink1:hover {
+ color: @ini_existing;
+ text-decoration: none;
+ }
+ a.wikilink2,
+ a.wikilink2:link {
+ color: @ini_missing;
+ border: none;
+ }
+}
+
+/* header */
+#dokuwiki__header {
+ background: @ini_background;
+ border-bottom: 1px solid @ini_border;
+
+ h1 {
+ margin: 2px 0 0 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font: 1.6em 'Source Sans Pro', 'Segoe UI', 'Times new roman', Times, serif;
+ white-space: nowrap;
+ font-weight: 600;
+ }
+ a,
+ a:link,
+ a:hover,
+ a:active {
+ color: @ini_text;
+ text-decoration: none;
+ }
+ button {
+ width: 30px;
+ height: 30px;
+ margin: 5px 0;
+ border: 0 none;
+ outline: none;
+ background: transparent url('images/ipari-simpleline.png') no-repeat;
+ background-size: 30px;
+ text-indent: -9999px;
+ }
+ div.left {
+ button:first-child {
+ margin-left: 16px;
+ }
+ button {
+ margin-left: 8px;
+ }
+ }
+ div.right {
+ button:last-child {
+ margin-right: 16px;
+ }
+ button {
+ margin-right: 4px;
+ }
+ }
+ div.search {
+ display: none;
+ padding: 6px 10px;
+
+ button {
+ display: none;
+ }
+ input[type=text] {
+ padding: 4px 6px;
+ }
+ }
+ .btn_left {
+ background-position: 0 0;
+ }
+ .btn_right {
+ background-position: 0 -90px;
+ }
+ .btn_search {
+ background-position: 0 -45px;
+ }
+}
+
+/* nav */
+#dokuwiki__aside,
+#dokuwiki__tools {
+ display: none;
+ background-color: @ini_background;
+}
+#sidebar_bg {
+ display: none;
+ background-color: rgba(0, 0, 0, .75);
+}
+
+#dokuwiki__aside {
+ > h1,
+ > h2,
+ > div.level1,
+ > div.level2 {
+ padding-left: 8px;
+ padding-right: 8px;
+ margin: 0 0 8px;
+ }
+ p,
+ pre,
+ table,
+ hr,
+ blockquote,
+ figure,
+ details,
+ fieldset,
+ address {
+ margin: 0 0 .4em 0;
+ }
+ p + p {
+ margin-top: 1em;
+ }
+}
+
+.sidebar {
+ overflow-x: hidden;
+ overflow-y: auto;
+ h3 {
+ font-size: 16px;
+ margin-bottom: 0;
+ padding: 9px 12px;
+ border-bottom: 1px solid @ini_border;
+ }
+ ul {
+ list-style-type: none;
+ margin: 0 0 .4em 0;
+ }
+ li {
+ margin: 0;
+ }
+ li li {
+ margin-left: 1em;
+ }
+ li a {
+ display: block;
+ padding: 8px 10px;
+ }
+ li a.urlextern {
+ padding: 10px 12px 10px 30px;
+ background-position: 10px 10px;
+ }
+ li a:hover {
+ background-color: @ini_background_alt !important;
+ }
+
+ div.user {
+ padding: 6px 12px;
+ }
+}
+
+#dokuwiki__tools {
+ li a {
+ height: 30px;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+ li a:before {
+ content: url('images/ipari-simpleline.png');
+ display: inline-block;
+ font-size: 0;
+ line-height: 0;
+ }
+ li a span {
+ display: inline-block;
+ vertical-align: top;
+ padding: .2em .6em;
+ }
+ li a,
+ li a:hover,
+ li a:active,
+ li a:visited {
+ background-image: none !important;
+ }
+ a.action.admin:before {
+ margin-top: -720px;
+ }
+ a.action.profile:before {
+ margin-top: -540px;
+ }
+ a.action.login:before {
+ margin-top: -630px;
+ }
+ a.action.logout:before {
+ margin-top: -675px;
+ }
+ a.action.register:before {
+ margin-top: -585px;
+ }
+ a.action.create:before {
+ margin-top: -135px;
+ }
+ a.action.show:before {
+ margin-top: -225px;
+ }
+ a.action.source:before {
+ margin-top: -180px;
+ }
+ a.action.edit:before {
+ margin-top: -90px;
+ }
+ a.action.revs:before {
+ margin-top: -495px;
+ }
+ a.action.backlink:before {
+ margin-top: -450px;
+ }
+ a.action.top:before {
+ margin-top: -405px;
+ }
+ a.action.recent:before {
+ margin-top: -270px;
+ }
+ a.action.media:before {
+ margin-top: -315px;
+ }
+ a.action.index:before {
+ margin-top: -360px;
+ }
+ // icons for plugins
+ a.action.plugin_export_pdf:before {
+ margin-top: -765px;
+ }
+}
+
+
+#to_top {
+ input.button {
+ width: 48px;
+ height: 48px;
+ border: 0;
+ background: transparent url('images/top.png') no-repeat;
+ opacity: 0.2;
+ text-indent: -9999px;
+ }
+}
+
+/* content */
+#dokuwiki__content {
+ margin-top: 40px;
+
+ div.breadcrumbs {
+ margin-bottom: 1em;
+ padding: 0.4em 1em;
+ opacity: 0.4;
+ }
+ div.breadcrumbs:hover {
+ opacity: 1.0;
+ }
+ div.page {
+ padding: 1em;
+ }
+}
+
+/* footer */
+#dokuwiki__footer {
+ border-top: 1px solid @ini_border;
+ padding: 0.5em 1em;
+
+ div.doc {
+ margin-bottom: 1em;
+ }
+ div.license {
+ margin-bottom: 1em;
+ font-size: 85%;
+
+ > a {
+ float: left;
+ margin-right: .5em;
+ padding-top: .3em;
+ }
+ a.urlextern {
+ float: left;
+ display: block;
+ background-position: 0 0.2em;
+ }
+ :after {
+ content: '';
+ display: block;
+ clear: both;
+ }
+ }
+ div.footer {
+ margin: 0 -1em;
+ padding: 0.5em 1em;
+ }
+}
+
+/* icons */
diff --git a/platform/www/lib/tpl/acervus/css/fonts/Norwester-Regular.eot b/platform/www/lib/tpl/acervus/css/fonts/Norwester-Regular.eot
new file mode 100644
index 0000000..95bb006
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/css/fonts/Norwester-Regular.eot
Binary files differ
diff --git a/platform/www/lib/tpl/acervus/css/fonts/Norwester-Regular.ttf b/platform/www/lib/tpl/acervus/css/fonts/Norwester-Regular.ttf
new file mode 100644
index 0000000..091f756
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/css/fonts/Norwester-Regular.ttf
Binary files differ
diff --git a/platform/www/lib/tpl/acervus/css/fonts/Norwester-Regular.woff b/platform/www/lib/tpl/acervus/css/fonts/Norwester-Regular.woff
new file mode 100644
index 0000000..4a52852
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/css/fonts/Norwester-Regular.woff
Binary files differ
diff --git a/platform/www/lib/tpl/acervus/css/hacks.css b/platform/www/lib/tpl/acervus/css/hacks.css
new file mode 100644
index 0000000..3349974
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/css/hacks.css
@@ -0,0 +1,187 @@
+#dokuwiki__site {
+ background: #f8f8f8cc;
+}
+
+#dokuwiki__header {
+ background: #fff;
+ border-bottom: 1px solid #ddd;
+ box-shadow: 1px 1px 24px #dadada94;
+}
+
+#dokuwiki__header strong {
+ color: darkred;
+}
+
+#dokuwiki__header em {
+ font-weight: 400;
+}
+
+#dokuwiki__content div.breadcrumbs {
+ margin-bottom: 1em;
+ padding: 2em 1em 0em;
+ opacity: 0.4;
+}
+
+#dokuwiki__content div.download {
+ /* margin-bottom: 1em; */
+ padding: 0.6em 1em 0em;
+ opacity: 0.6;
+}
+
+
+#dokuwiki__content div.page a:link, a:visited {
+ color: #A00;
+}
+
+h1#sitetitle:hover {
+ text-shadow: 0px 0px 5px #ccc;
+}
+
+#dokuwiki__footer div.doc {
+ margin-bottom: 1em;
+ text-align: center;
+ font-size: 80%;
+ color: #5a5a5a;
+}
+
+.dokuwiki form.bureaucracy__plugin fieldset {
+ width: 100%;
+}
+
+.dokuwiki form.bureaucracy__plugin label input[type=checkbox] {
+ width: 5%;
+ margin-right: 45%;
+ vertical-align: -webkit-baseline-middle;
+}
+
+.dokuwiki form.bureaucracy__plugin label>span {
+ vertical-align: middle;
+ padding-right: 5px;
+}
+
+.dokuwiki form.bureaucracy__plugin {
+ margin: 0;
+}
+
+.dokuwiki div.bureaucracy__plugin {
+ width: 100%;
+ font-size: 100%;
+ padding: 0em;
+ border-radius: 3px;
+ animation: highlight 10000ms ease-in;
+}
+
+.dokuwiki div.bureaucracy__plugin:target {
+
+}
+@keyframes highlight {
+ 0% {
+ background-color: white;
+ }
+ 50% {
+ background-color: yellow;
+ }
+ 100% {
+ background-color: white;
+ }
+}
+
+
+input[type=text], textarea {
+ -webkit-transition: all 0.30s ease-in-out;
+ -moz-transition: all 0.30s ease-in-out;
+ -ms-transition: all 0.30s ease-in-out;
+ -o-transition: all 0.30s ease-in-out;
+ outline: none;
+ padding: 3px 0px 3px 3px;
+ margin: 5px 1px 3px 0px;
+ border: 1px solid #DDDDDD;
+}
+
+input[type=text]:focus, textarea:focus {
+ box-shadow: 0 0 5px rgba(81, 203, 238, 1);
+ padding: 3px 0px 3px 3px;
+ margin: 5px 1px 3px 0px;
+ border: 1px solid rgba(81, 203, 238, 1);
+}
+
+.anchorjs-link{
+color: #c5c5c5 !important;
+ transition: all .25s linear;
+}
+p a.anchorjs-link:hover {
+ margin-left: -1.125em !important;
+color: red !important;
+text-decoration:none !important;
+}
+
+h2 a.anchorjs-link, h3 a.anchorjs-link, h4 a.anchorjs-link, h5 a.anchorjs-link, li a.anchorjs-link {
+ margin-left: -1.8em !important;
+}
+
+h2 a.anchorjs-link:hover, h3 a.anchorjs-link:hover, h4 a.anchorjs-link:hover, h5 a.anchorjs-link:hover {
+ margin-left: -1.9em !important;
+color: red !important;
+text-decoration:none !important;
+}
+
+.dw__toc .anchorjs-link {
+ display: none;
+}
+
+#dokuwiki__content {
+ counter-reset: paragraph;
+}
+
+.page p a.anchorjs-link:before {
+ position: absolute;
+ right: 22px;
+ color: #c5c5c5;
+ content: counter(paragraph);
+ counter-increment: paragraph;
+ text-align: right;
+ font-size: 12pt;
+ top: 1px;
+}
+
+
+
+.numbered_heading .page {counter-reset: level1;}
+.numbered_heading #dw__toc:before,
+.numbered_heading #dw__toc:after {counter-reset: level1; content: "";}
+.numbered_heading #dw__toc h3:before{content: ""}
+
+.numbered_heading ul.toc li.level1 {counter-reset: level2;}
+.numbered_heading ul.toc li.level2 {counter-reset: level3;}
+.numbered_heading ul.toc li.level3 {counter-reset: level4;}
+.numbered_heading ul.toc li.level4 {counter-reset: level5;}
+.numbered_heading ul.toc li.level5 {}
+
+.numbered_heading ul.toc li.level1 a:before {
+ content: counter(level1) ". ";
+ counter-increment: level1;
+}
+
+.numbered_heading ul.toc li.level1:first-child a:before {
+ /* content: none; */
+}
+
+.numbered_heading ul.toc li.level2 a:before {
+ content: counter(level1) "." counter(level2) ". ";
+ counter-increment: level2;
+}
+
+.numbered_heading ul.toc li.level3 a:before {
+ content: counter(level1) "." counter(level2) "." counter(level3) ". ";
+ counter-increment: level3;
+}
+
+.numbered_heading ul.toc li.level4 a:before {
+ content: counter(level1) "." counter(level2) "." counter(level3) "." counter(level4) ". ";
+ counter-increment: level4;
+}
+
+.numbered_heading ul.toc li.level5 a:before {
+ content: counter(level1) "." counter(level2) "." counter(level3) "." counter(level4) "." counter(level5) ". ";
+ counter-increment: level5;
+}
diff --git a/platform/www/lib/tpl/acervus/css/mobile.less b/platform/www/lib/tpl/acervus/css/mobile.less
new file mode 100644
index 0000000..79f5888
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/css/mobile.less
@@ -0,0 +1,136 @@
+@media only screen and (max-width: 760px) {
+
+/* admin */
+.dokuwiki div.ui-admin {
+ ul.admin_tasks,
+ ul.admin_plugins {
+ float: none;
+ width: 90%;
+ margin: 0;
+ }
+}
+// before "Frusterick Manners"
+.dokuwiki ul.admin_tasks {
+ float: none;
+ width: auto;
+ margin: 0;
+}
+
+/* config */
+#config__manager {
+ fieldset {
+ legend {
+ padding: 0;
+ }
+ td.label,
+ td.value {
+ display: block;
+ width: auto !important;
+ }
+ td.label {
+ padding: .2em .4em;
+ border: none;
+ }
+ td.value {
+ padding: .4em .4em 1.2em;
+ border: none;
+ background: @ini_background;
+ }
+ div.selection {
+ float: none;
+ width: auto;
+ margin-right: 0;
+ }
+ }
+}
+
+/* media manager */
+#mediamanager__page {
+ .panel {
+ float: none;
+ }
+ .namespaces {
+ width: auto;
+ }
+ .filelist {
+ width: auto;
+ min-width: 0;
+ ul {
+ margin: 0;
+ }
+ ul.rows li {
+ display: inline-block;
+ float: left;
+ width: 50%;
+ max-height: none !important;
+
+ dt,
+ dd {
+ float: none !important;
+ width: auto !important;
+ }
+ dt {
+ height: 60px;
+ }
+ dt a {
+ text-align: left;
+ vertical-align: top;
+ width: auto;
+ height: 60px;
+ padding: 0;
+ }
+ dt a img {
+ max-width: none;
+ max-height: 60px;
+ }
+ }
+ ul.rows li:nth-child(2n+1) {
+ background: none;
+ }
+ ul.rows li:hover {
+ background: @ini_background_alt;
+ }
+ }
+ .panelHeader {
+ margin-right: 0;
+ h3 {
+ float: none;
+ }
+ form.options {
+ float: none;
+ }
+ ul li.listType {
+ margin-left: -2px;
+ }
+ }
+ .panelContent {
+ margin: 0 0 20px 0;
+ }
+ .file {
+ width: auto;
+
+ }
+ .ui-resizable-e {
+ display: none !important;
+ }
+}
+
+} /* @media for 760px */
+
+
+@media only screen and (max-width: 480px) {
+ /* TOC */
+ #dw__toc {
+ display: block;
+ }
+
+ /* WRAP plugin */
+ .dokuwiki .wrap_left,
+ .dokuwiki .wrap_right,
+ .dokuwiki .wrap_center,
+ .dokuwiki .wrap_column {
+ float: inherit;
+ width: 100% !important;
+ margin: 0;
+ }
+} /* @media for 480px */
diff --git a/platform/www/lib/tpl/acervus/css/print.less b/platform/www/lib/tpl/acervus/css/print.less
new file mode 100644
index 0000000..6fe15d4
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/css/print.less
@@ -0,0 +1,94 @@
+/**
+ * This file provides the styles for printing.
+ */
+
+/* hide certain sections */
+audio,
+video,
+.sidebar,
+.breadcrumbs,
+#dokuwiki__header .left,
+#dokuwiki__header .right,
+#dokuwiki__header .search,
+#dw__toc span,
+#dokuwiki__footer .doc,
+#to_top {
+ display: none;
+}
+
+a:link,
+a:visited {
+ text-decoration: none;
+ border-bottom: 1pt dotted;
+ color: #333;
+ background-color: inherit;
+}
+
+#dokuwiki__header,
+#dw__toc {
+ a:link,
+ a:visited {
+ border: none;
+ }
+}
+
+/* code blocks */
+pre {
+ font-family: monospace;
+}
+dl.code dt,
+dl.file dt {
+ font-weight: bold;
+}
+
+/* images */
+img {
+ border-width: 0;
+ vertical-align: middle;
+}
+.medialeft {
+ margin-right: .6em;
+}
+.mediaright {
+ margin-left: .6em;
+}
+
+/* tables */
+table {
+ border-collapse: collapse;
+}
+th,
+td {
+ text-align: left;
+ border-bottom: 1px solid #999;
+}
+
+
+/*____________ a bit of layout ____________*/
+
+#dokuwiki__header {
+ border-bottom: 1pt solid #999;
+
+ h1 {
+ font-size: 1.5em;
+ }
+}
+
+#dokuwiki__footer {
+ border-top: 1pt solid #999;
+ div.license {
+ padding-top: .5em;
+ > a {
+ display: inline-block;
+ float: left;
+ margin: .2em .5em 0 0;
+ }
+ }
+}
+
+.dokuwiki div.footnotes {
+ clear: both;
+ border-top: 1pt solid #000;
+ margin-top: 10pt;
+ padding: 1em 0;
+}
diff --git a/platform/www/lib/tpl/acervus/css/responsive.css b/platform/www/lib/tpl/acervus/css/responsive.css
new file mode 100644
index 0000000..ecb8db4
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/css/responsive.css
@@ -0,0 +1,13 @@
+h1#sitetitle-short p, h1#sitetitle p {margin: 0;}
+
+@media (min-width: 768px) {
+ h1#sitetitle-short {display:none;}
+ h1#sitetitle {display:inline-block;}
+
+}
+
+@media (max-width: 768px) {
+ h1#sitetitle-short {display:inline-block;}
+ h1#sitetitle {display:none;}
+
+}
diff --git a/platform/www/lib/tpl/acervus/css/structure.less b/platform/www/lib/tpl/acervus/css/structure.less
new file mode 100644
index 0000000..9b8dab4
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/css/structure.less
@@ -0,0 +1,64 @@
+/* header */
+#dokuwiki__header {
+ position: fixed;
+ z-index: 102;
+ top: 0;
+ width: 100%;
+ max-height: 40px;
+
+ h1 {
+ display: inline-block;
+ width: 50%;
+ text-align: center;
+ }
+ div.left {
+ width: 25%;
+ float: left;
+ /* trick for keep width when aside is deactivated */
+ min-height: 1px;
+ }
+ div.right {
+ width: 25%;
+ float: right;
+ text-align: right;
+ }
+ div.search {
+ float: right;
+ }
+}
+
+/* nav */
+.sidebar {
+ position: fixed;
+ z-index: 104;
+ width: @ini_sidebar_width;
+ max-width: 66%;
+ height: 100%;
+}
+
+#dokuwiki__aside {
+ left: 0;
+}
+#dokuwiki__tools {
+ right: 0;
+}
+#sidebar_bg {
+ position: fixed;
+ z-index: 103;
+ width: 100%;
+ height: 100%;
+}
+#to_top {
+ position: fixed;
+ z-index: 101;
+ right: 10px;
+ bottom: 10px;
+}
+
+/* content */
+.wrapper {
+ margin: 0 auto;
+ max-width: @ini_body_width;
+}
+
+/* footer */
diff --git a/platform/www/lib/tpl/acervus/detail.php b/platform/www/lib/tpl/acervus/detail.php
new file mode 100644
index 0000000..e1b4900
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/detail.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * DokuWiki Image Detail Page
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Anika Henke <anika@selfthinker.org>
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ */
+
+// must be run from within DokuWiki
+if (!defined('DOKU_INC')) die();
+@require_once(dirname(__FILE__).'/tpl_functions.php');
+header('X-UA-Compatible: IE=edge,chrome=1');
+
+?><!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $conf['lang']?>"
+ lang="<?php echo $conf['lang']?>" dir="<?php echo $lang['direction'] ?>" class="no-js">
+<head>
+ <meta charset="UTF-8" />
+ <title>
+ <?php echo hsc(tpl_img_getTag('IPTC.Headline',$IMG))?>
+ [<?php echo strip_tags($conf['title'])?>]
+ </title>
+ <script>(function(H){H.className=H.className.replace(/\bno-js\b/,'js')})(document.documentElement)</script>
+ <?php tpl_metaheaders()?>
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <?php echo tpl_favicon(array('favicon', 'mobile')) ?>
+ <?php tpl_includeFile('meta.html') ?>
+</head>
+
+<body>
+ <div id="dokuwiki__detail" class="<?php echo tpl_classes(); ?>">
+ <?php html_msgarea() ?>
+
+ <?php if($ERROR): print $ERROR; ?>
+ <?php else: ?>
+
+ <?php if($REV) echo p_locale_xhtml('showrev');?>
+ <h1><?php echo hsc(tpl_img_getTag('IPTC.Headline', $IMG))?></h1>
+
+ <div class="content group">
+ <?php tpl_img(900, 700); ?>
+
+ <div class="img_detail">
+ <h2><?php print nl2br(hsc(tpl_img_getTag('simple.title'))); ?></h2>
+
+ <?php if(function_exists('tpl_img_meta')): ?>
+ <?php tpl_img_meta(); ?>
+ <?php else: /* deprecated since Release 2014-05-05 */ ?>
+ <dl>
+ <?php
+ $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 endif; ?>
+ <?php //Comment in for Debug// dbg(tpl_img_getTag('Simple.Raw')); ?>
+ </div>
+ </div><!-- /.content -->
+
+ <p class="back">
+ <?php tpl_action('mediaManager', 1) ?><br />
+ &larr; <?php tpl_action('img_backto', 1) ?>
+ </p>
+
+ <?php endif; ?>
+ </div>
+</body>
+</html>
diff --git a/platform/www/lib/tpl/acervus/images/apple-touch-icon.png b/platform/www/lib/tpl/acervus/images/apple-touch-icon.png
new file mode 100644
index 0000000..73d2601
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/images/apple-touch-icon.png
Binary files differ
diff --git a/platform/www/lib/tpl/acervus/images/bg.png b/platform/www/lib/tpl/acervus/images/bg.png
new file mode 100644
index 0000000..007b812
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/images/bg.png
Binary files differ
diff --git a/platform/www/lib/tpl/acervus/images/bg2.png b/platform/www/lib/tpl/acervus/images/bg2.png
new file mode 100644
index 0000000..4b1f61e
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/images/bg2.png
Binary files differ
diff --git a/platform/www/lib/tpl/acervus/images/bg3.png b/platform/www/lib/tpl/acervus/images/bg3.png
new file mode 100644
index 0000000..1ea6e1e
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/images/bg3.png
Binary files differ
diff --git a/platform/www/lib/tpl/acervus/images/favicon.ico b/platform/www/lib/tpl/acervus/images/favicon.ico
new file mode 100644
index 0000000..ecbf22f
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/images/favicon.ico
Binary files differ
diff --git a/platform/www/lib/tpl/acervus/images/ipari-simpleline.png b/platform/www/lib/tpl/acervus/images/ipari-simpleline.png
new file mode 100644
index 0000000..e48025e
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/images/ipari-simpleline.png
Binary files differ
diff --git a/platform/www/lib/tpl/acervus/images/top.png b/platform/www/lib/tpl/acervus/images/top.png
new file mode 100644
index 0000000..1d43e0c
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/images/top.png
Binary files differ
diff --git a/platform/www/lib/tpl/acervus/lang/de/lang.php b/platform/www/lib/tpl/acervus/lang/de/lang.php
new file mode 100644
index 0000000..7a8b91b
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/de/lang.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author LarsDW223
+ */
+$lang['__link__'] = 'Allgemeine Linkfarbe';
+$lang['__existing__'] = 'Farbe für Links zu existierenden Seiten';
+$lang['__missing__'] = 'Farbe für Links zu nicht-existierenden Seiten';
+$lang['__numbered_heading__'] = 'Farbe für nummerierte Überschriften';
+$lang['__font_size__'] = 'Globale Schriftgröße (%, px, pt)';
+$lang['__line_height__'] = 'Globale Zeilenhöhe (x.x, %)';
+$lang['__body_width__'] = 'Breite der Seite (%, px, em, ...)';
+$lang['__sidebar_width__'] = 'Breite der Sidebar, falls vorhanden (%, px, em, ...)';
diff --git a/platform/www/lib/tpl/acervus/lang/de/settings.php b/platform/www/lib/tpl/acervus/lang/de/settings.php
new file mode 100644
index 0000000..671f49e
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/de/settings.php
@@ -0,0 +1,6 @@
+<?php
+
+$lang['numberedHeading'] = 'Überschriften nummerieren';
+$lang['tocPosition'] = 'Position des Inhaltsverzeichnisses';
+$lang['tocPosition_o_dokuwiki'] = 'DokuWiki';
+$lang['tocPosition_o_wikipedia'] = 'Wikipedia';
diff --git a/platform/www/lib/tpl/acervus/lang/de/style.txt b/platform/www/lib/tpl/acervus/lang/de/style.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/de/style.txt
@@ -0,0 +1 @@
+
diff --git a/platform/www/lib/tpl/acervus/lang/en/lang.php b/platform/www/lib/tpl/acervus/lang/en/lang.php
new file mode 100644
index 0000000..1583452
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/en/lang.php
@@ -0,0 +1,13 @@
+<?php
+
+// style.ini values
+
+$lang['download'] = 'Download';
+$lang['__link__'] = 'The general link color';
+$lang['__existing__'] = 'The color for links to existing pages';
+$lang['__missing__'] = 'The color for links to non-existing pages';
+$lang['__numbered_heading__'] = 'The color for numbered heading';
+$lang['__font_size__'] = 'The global font size (%, px, pt)';
+$lang['__line_height__'] = 'The global line height (x.x, %)';
+$lang['__body_width__'] = 'The width of the body (%, px, em, ...)';
+$lang['__sidebar_width__'] = 'The width of the sidebar, if any (%, px, em, ...)';
diff --git a/platform/www/lib/tpl/acervus/lang/en/settings.php b/platform/www/lib/tpl/acervus/lang/en/settings.php
new file mode 100644
index 0000000..18fa82c
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/en/settings.php
@@ -0,0 +1,7 @@
+<?php
+
+$lang['numberedHeading'] = 'Numbering headings';
+$lang['tocPosition'] = 'Position of TOC';
+$lang['tocPosition_o_dokuwiki'] = 'DokuWiki';
+$lang['tocPosition_o_wikipedia'] = 'Wikipedia';
+$lang['footer'] = 'Footer page name, empty field disables the footer';
diff --git a/platform/www/lib/tpl/acervus/lang/en/style.txt b/platform/www/lib/tpl/acervus/lang/en/style.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/en/style.txt
@@ -0,0 +1 @@
+
diff --git a/platform/www/lib/tpl/acervus/lang/es/lang.php b/platform/www/lib/tpl/acervus/lang/es/lang.php
new file mode 100644
index 0000000..c0e1f6f
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/es/lang.php
@@ -0,0 +1,5 @@
+<?php
+
+// style.ini values
+
+$lang['download'] = 'Descargar';
diff --git a/platform/www/lib/tpl/acervus/lang/ko/lang.php b/platform/www/lib/tpl/acervus/lang/ko/lang.php
new file mode 100644
index 0000000..09e149a
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/ko/lang.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ * @author Myeongjin <aranet100@gmail.com>
+ */
+$lang['__link__'] = '일반 링크 색';
+$lang['__existing__'] = '문서가 존재하는 링크의 색';
+$lang['__missing__'] = '문서가 존재하지 않는 링크의 색';
+$lang['__numbered_heading__'] = '제목 번호 색';
+$lang['__font_size__'] = '전체 사이트 글자 크기 (%, px, pt)';
+$lang['__line_height__'] = '전체 사이트 줄 간격 (x.x, %)';
+$lang['__body_width__'] = '본문 너비 (%, px, em, ...)';
+$lang['__sidebar_width__'] = '사이드바가 있다면, 그것의 너비 (%, px, em, ...)';
diff --git a/platform/www/lib/tpl/acervus/lang/ko/settings.php b/platform/www/lib/tpl/acervus/lang/ko/settings.php
new file mode 100644
index 0000000..089d158
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/ko/settings.php
@@ -0,0 +1,7 @@
+<?php
+
+$lang['numberedHeading'] = '제목 수준에 번호를 붙힙니다.';
+$lang['tocPosition'] = '목차의 위치';
+$lang['tocPosition_o_dokuwiki'] = '도쿠위키 기본';
+$lang['tocPosition_o_wikipedia'] = '위키백과 스타일';
+$lang['footer'] = '바닥글 문서 이름, 필드를 비우면 바닥글 비활성화';
diff --git a/platform/www/lib/tpl/acervus/lang/ko/style.txt b/platform/www/lib/tpl/acervus/lang/ko/style.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/ko/style.txt
@@ -0,0 +1 @@
+
diff --git a/platform/www/lib/tpl/acervus/lang/ru/lang.php b/platform/www/lib/tpl/acervus/lang/ru/lang.php
new file mode 100644
index 0000000..2cec63d
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/ru/lang.php
@@ -0,0 +1,12 @@
+<?php
+
+// style.ini values
+
+$lang['__link__'] = 'Основной цвет ссылок';
+$lang['__existing__'] = 'Цвет ссылок на существующие страницы';
+$lang['__missing__'] = 'Цвет ссылок на не существующие страницы';
+$lang['__numbered_heading__'] = 'Цвет для нумерованного заголовка';
+$lang['__font_size__'] = 'Основной размер шрифта (%, px, pt)';
+$lang['__line_height__'] = 'Основная высота строки (x.x, %)';
+$lang['__body_width__'] = 'Ширина страницы (%, px, em, ...)';
+$lang['__sidebar_width__'] = 'Ширина боковой панели, если она есть (%, px, em, ...)';
diff --git a/platform/www/lib/tpl/acervus/lang/ru/settings.php b/platform/www/lib/tpl/acervus/lang/ru/settings.php
new file mode 100644
index 0000000..0be18f8
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/ru/settings.php
@@ -0,0 +1,3 @@
+<?php
+
+$lang['numberedHeading'] = 'Нумерация заголовков';
diff --git a/platform/www/lib/tpl/acervus/lang/ru/style.txt b/platform/www/lib/tpl/acervus/lang/ru/style.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/lang/ru/style.txt
@@ -0,0 +1 @@
+
diff --git a/platform/www/lib/tpl/acervus/main.php b/platform/www/lib/tpl/acervus/main.php
new file mode 100644
index 0000000..c4dfd1c
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/main.php
@@ -0,0 +1,265 @@
+<?php
+/**
+ * DokuWiki Starter Template
+ *
+ * @link http://dokuwiki.org/template:ipari
+ * @author Kwangyoung Lee <ipari@leaflette.com>
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ */
+
+if (!defined('DOKU_INC')) die();
+@require_once(dirname(__FILE__).'/tpl_functions.php');
+header('X-UA-Compatible: IE=edge,chrome=1');
+$showSidebar = page_findnearest($conf['sidebar']);
+?>
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $conf['lang'] ?>"
+ lang="<?php echo $conf['lang'] ?>" dir="<?php echo $lang['direction'] ?>" class="no-js">
+<head>
+ <meta charset="UTF-8" />
+ <title><?php tpl_pagetitle() ?> [<?php echo strip_tags($conf['title']) ?>]</title>
+ <script>(function(H){H.className=H.className.replace(/\bno-js\b/,'js')})(document.documentElement)</script>
+ <?php tpl_metaheaders() ?>
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <?php echo tpl_favicon(array('favicon', 'mobile')) ?>
+ <?php tpl_includeFile('meta.html') ?>
+
+<script src="https://cdn.jsdelivr.net/npm/anchor-js/anchor.min.js"></script>
+<!-- <link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700;1,900&display=swap" rel="stylesheet"> -->
+
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;0,700;0,800;1,400;1,500;1,600;1,700;1,800&display=swap" rel="stylesheet">
+
+<?php
+$translation = plugin_load('helper','translation');
+global $ID;
+$startpage = $conf['start'];
+$lang2 = $translation->getLangPart($ID);
+if ($lang2 != '') {
+ $startpage = $lang2 .':'.$startpage;
+}
+?>
+
+</head>
+
+<body id="dokuwiki__top">
+ <div id="dokuwiki__site" class="<?php echo tpl_classes(); ?> <?php echo ($showSidebar) ? 'hasSidebar' : ''; ?>">
+ <?php html_msgarea() ?>
+ <?php tpl_includeFile('header.html') ?>
+
+ <!-- ********** HEADER ********** -->
+ <div id="dokuwiki__header">
+ <div class="group">
+ <!-- <h1 id="sitetitle"><?php tpl_link('/'.$startpage,$title_text,'accesskey="h" title="[H]"') ?></h1> -->
+ <h1><?php tpl_link('/'.$startpage,$conf['title'],'accesskey="h" title="[H]"') ?></h1>
+ <div class="left">
+ <?php if ($showSidebar): ?>
+ <button class="btn_left" accesskey="s", title="[S]">Nav</button>
+ <?php endif; ?>
+ </div>
+ <div class="right">
+ <button class="btn_search">Search</button>
+ <button class="btn_right" accesskey="m", title="[M]">Edit</button>
+ </div>
+ </div>
+ <div class="search">
+ <?php tpl_searchform(); ?>
+ </div>
+
+
+ </div><!-- /header -->
+
+ <!-- ********** sidebar ********** -->
+ <div id="sidebar_wrapper">
+ <!-- ********** ASIDE ********** -->
+ <?php if ($showSidebar): ?>
+ <div id="dokuwiki__aside" class="sidebar">
+ <?php tpl_includeFile('sidebarheader.html') ?>
+ <?php tpl_include_page($conf['sidebar'], 1, 1) ?>
+ <?php tpl_includeFile('sidebarfooter.html') ?>
+ </div><!-- /dokuwiki__aside -->
+ <?php endif; ?>
+
+ <div id="dokuwiki__tools" class="sidebar left">
+ <!-- PAGE TOOLS -->
+ <div id="dokuwiki__pagetools">
+ <h3><?php echo $lang['page_tools'] ?></h3>
+ <ul>
+ <?php white_toolsevent('pagetools', array(
+ 'edit' => tpl_action('edit', 1, 'li', 1, '<span>', '</span>'),
+ 'revisions' => tpl_action('revisions', 1, 'li', 1, '<span>', '</span>'),
+ 'backlink' => tpl_action('backlink', 1, 'li', 1, '<span>', '</span>'),
+ 'subscribe' => tpl_action('subscribe', 1, 'li', 1, '<span>', '</span>'),
+ 'revert' => tpl_action('revert', 1, 'li', 1, '<span>', '</span>'),
+ )); ?>
+ </ul>
+ </div><!-- /dokuwiki__pagetools -->
+
+ <!-- SITE TOOLS -->
+ <div id="dokuwiki__sitetools">
+ <h3><?php echo $lang['site_tools'] ?></h3>
+ <ul>
+ <?php white_toolsevent('sitetools', array(
+ 'recent' => tpl_action('recent', 1, 'li', 1, '<span>', '</span>'),
+ 'media' => tpl_action('media', 1, 'li', 1, '<span>', '</span>'),
+ 'index' => tpl_action('index', 1, 'li', 1, '<span>', '</span>'),
+ )); ?>
+ </ul>
+ </div><!-- /dokuwiki__sitetools -->
+
+ <!-- USER TOOLS -->
+ <?php if ($conf['useacl']): ?>
+ <div id="dokuwiki__usertools">
+ <h3><?php echo $lang['user_tools'] ?></h3>
+ <ul>
+ <?php white_toolsevent('usertools', array(
+ 'admin' => tpl_action('admin', 1, 'li', 1, '<span>', '</span>'),
+ 'profile' => tpl_action('profile', 1, 'li', 1, '<span>', '</span>'),
+ 'register' => tpl_action('register', 1, 'li', 1, '<span>', '</span>'),
+ 'login' => tpl_action('login', 1, 'li', 1, '<span>', '</span>'),
+ )); ?>
+ </ul>
+ <?php
+ if (!empty($_SERVER['REMOTE_USER'])) {
+ echo '<div class="user">';
+ tpl_userinfo();
+ echo '</div>';
+ }
+ ?>
+ </div><!-- /dokuwiki__usertools -->
+ <?php endif ?>
+ </div><!-- /dokuwiki__tools -->
+
+ <div id="sidebar_bg">
+ </div>
+
+ <div id="to_top">
+ <?php tpl_action('top') ?>
+ </div>
+ </div><!-- /sidebar_wrapper -->
+
+ <div class="wrapper group">
+ <!-- ********** CONTENT ********** -->
+ <div id="dokuwiki__content"><div class="group">
+ <?php tpl_flush() ?>
+ <?php tpl_includeFile('pageheader.html') ?>
+
+ <!-- BREADCRUMBS -->
+ <?php if($conf['breadcrumbs']){ ?>
+ <div class="breadcrumbs"><?php tpl_breadcrumbs($ret='›') ?></div>
+ <?php } ?>
+ <?php if($conf['youarehere']){ ?>
+ <div class="breadcrumbs"><?php tpl_youarehere() ?></div>
+ <?php } ?>
+
+ <?php
+ $filepath = $INFO['filepath'];
+ $file_markdown = $filepath;
+ $file_pdf = str_replace('.txt','.pdf',$filepath);
+ $file_epub = str_replace('.txt','.epub',$filepath);
+
+ if (file_exists($file_pdf)) {
+ $file_pdf_url = str_replace('/srv/acerv.us/platform/www','',$file_pdf);
+ $download_link .= '<a href="'. $file_pdf_url .'">pdf</a> | ';
+ }
+
+ if (file_exists($file_epub)) {
+ $file_epub_url = str_replace('/srv/acerv.us/platform/www','',$file_epub);
+ $download_link .= '<a href="'. $file_epub_url .'">epub</a> | ';
+ }
+
+ if ($download_link) {
+ $file_markdown_url = str_replace('/srv/acerv.us/platform','',$file_markdown);
+ echo '<div class="download">' . tpl_getLang('download') . ': ' . $download_link . '</div>';
+ $download_link .= '<a href="'. $file_markdown_url .'">markdown</a>';
+ }
+
+
+ ?>
+
+
+ <div class="page group
+ <?php if(tpl_getConf('numberedHeading')): ?> numbered_heading<?php endif ?>
+ <?php if(tpl_getConf('tocPosition')): ?> toc_<?php echo tpl_getConf('tocPosition') ?><?php endif ?>
+ ">
+ <!-- wikipage start -->
+ <?php tpl_content() ?>
+ <!-- wikipage stop -->
+ </div>
+
+ <?php tpl_flush() ?>
+ <?php tpl_includeFile('pagefooter.html') ?>
+ </div></div><!-- /content -->
+
+ <!-- ********** FOOTER ********** -->
+ <div id="dokuwiki__footer">
+ <?php if($INFO['exists']): ?>
+ <div class="doc"><?php white_pageinfo() ?></div>
+ <?php endif ?>
+ <?php tpl_includeFile('sidebarfooter.html') ?>
+ <?php tpl_license('badge', false, false) ?>
+ <div class="footer">
+ <?php tpl_include_page(tpl_getConf('footer'), 1, 1) ?>
+ </div>
+
+ <center><?php
+ if ($translation) echo $translation->showTranslations();
+ ?></center>
+ </div><!-- /footer -->
+
+ <?php tpl_includeFile('footer.html') ?>
+ </div><!-- /wrapper -->
+
+ </div><!-- /site -->
+
+ <div class="no"><?php tpl_indexerWebBug() /* provide DokuWiki housekeeping, required in all templates */ ?></div>
+
+<script>
+
+function getAnchor() {
+ // alert(decodeURI(document.URL));
+ return (decodeURI(document.URL).split('#').length > 1) ? decodeURI(document.URL).split('#')[1] : null;
+}
+
+if (document.URL.toLowerCase().indexOf("index") === -1) { // not show on index
+ if (document.URL.includes(':en') ||
+ document.URL.includes(':es') ||
+ document.URL.includes(':fr') ||
+ document.URL.includes(':script') ||
+ document.URL.includes(':article')
+ ) { // only shows number of paragraph in books, articles and scripts pages
+ anchors.options = {
+ placement: 'left',
+ visible: 'always',
+ icon: '¶'
+ };
+ //anchors.add('');
+ anchors.add('#dokuwiki__content p');
+ anchors.add('#dokuwiki__content li');
+ anchors.add('#dokuwiki__content tr');
+
+ // highlight referenced paragraph
+ anchor = getAnchor();
+ if (anchor !== null) {
+ var anchor_element = document.getElementById(anchor);
+ anchor_element.style.backgroundColor = "#ffff0033";
+ (function($) {
+ $([document.documentElement, document.body]).animate({
+ scrollTop: $("#" + anchor).offset().top-50
+ }, 2000);
+ })(jQuery);
+
+
+ }
+
+ }
+
+}
+
+
+</script>
+
+
+</body>
+</html>
diff --git a/platform/www/lib/tpl/acervus/manager.dat b/platform/www/lib/tpl/acervus/manager.dat
new file mode 100644
index 0000000..7a94e4b
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/manager.dat
@@ -0,0 +1,2 @@
+downloadurl=https://github.com/ipari/dokuwiki-template-white/zipball/master
+installed=Thu, 07 Jun 2018 20:52:40 -0400
diff --git a/platform/www/lib/tpl/acervus/mediamanager.php b/platform/www/lib/tpl/acervus/mediamanager.php
new file mode 100644
index 0000000..d6faf42
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/mediamanager.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * DokuWiki Media Manager Popup
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ */
+// must be run from within DokuWiki
+if (!defined('DOKU_INC')) die();
+@require_once(dirname(__FILE__).'/tpl_functions.php');
+header('X-UA-Compatible: IE=edge,chrome=1');
+
+?><!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $conf['lang']?>"
+ lang="<?php echo $conf['lang']?>" dir="<?php echo $lang['direction'] ?>" class="popup no-js">
+<head>
+ <meta charset="UTF-8" />
+ <title>
+ <?php echo hsc($lang['mediaselect'])?>
+ [<?php echo strip_tags($conf['title'])?>]
+ </title>
+ <script>(function(H){H.className=H.className.replace(/\bno-js\b/,'js')})(document.documentElement)</script>
+ <?php tpl_metaheaders()?>
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <?php echo tpl_favicon(array('favicon', 'mobile')) ?>
+ <?php tpl_includeFile('meta.html') ?>
+</head>
+
+<body>
+ <div id="media__manager" class="<?php echo tpl_classes(); ?>">
+ <?php html_msgarea() ?>
+ <div id="mediamgr__aside"><div class="group">
+ <h1><?php echo hsc($lang['mediaselect'])?></h1>
+
+ <?php /* keep the id! additional elements are inserted via JS here */?>
+ <div id="media__opts"></div>
+
+ <?php tpl_mediaTree() ?>
+ </div></div>
+
+ <div id="mediamgr__content"><div class="group">
+ <?php tpl_mediaContent() ?>
+ </div></div>
+ </div>
+</body>
+</html>
diff --git a/platform/www/lib/tpl/acervus/script.js b/platform/www/lib/tpl/acervus/script.js
new file mode 100644
index 0000000..503b135
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/script.js
@@ -0,0 +1,81 @@
+(function($) {
+ var fadeOption = {duration: 150};
+
+ function toggleLeft() {
+ $('#sidebar_bg').show('fade', fadeOption);
+ $('#dokuwiki__aside').show();
+ }
+
+ function toggleRight() {
+ $('#sidebar_bg').show('fade', fadeOption);
+ $('#dokuwiki__tools').show();
+ }
+
+ function preventParentWheel(e) {
+ var curScrollPos = $(this).scrollTop();
+ var scrollableDist = $(this).prop('scrollHeight') - $(this).outerHeight();
+ var wheelEvent = e.originalEvent;
+ var dY = wheelEvent.deltaY;
+
+ if (dY < 0 && curScrollPos <= 0) {
+ return false;
+ }
+ if (dY > 0 && curScrollPos >= scrollableDist) {
+ return false;
+ }
+ }
+
+ function showSearch() {
+ $('div.search').toggle();
+ $('div.search').find('input.edit').select();
+ }
+
+ function bindEvents() {
+ $('.sidebar').on('wheel scroll', preventParentWheel);
+ $('.btn_left').click(function() {
+ toggleLeft();
+ });
+ $('.btn_right').click(function() {
+ toggleRight();
+ });
+ $('#sidebar_bg').click(function() {
+ $(this).hide('fade', fadeOption);
+ $('#dokuwiki__aside').hide();
+ $('#dokuwiki__tools').hide();
+ });
+ $('.btn_search').click(function() {
+ showSearch();
+ });
+ $(document).keydown(function(e) {
+ if (e.which == 70 && e.altKey) {
+ showSearch();
+ e.preventDefault();
+ }
+ });
+ }
+
+ function initUI() {
+ // Move TOC
+ if ($('.page h2').length > 0) {
+ $('.toc_wikipedia').find('#dw__toc').insertBefore($('.page h2:first'));
+ } else {
+ $('.toc_wikipedia').find('#dw__toc').insertAfter($('.page h1:first').next('.level1'));
+ }
+ if ($('.page > .level1 > blockquote').length > 0) {
+ $('.toc_dokuwiki').find('#dw__toc').insertAfter($('.page > .level1 > blockquote'));
+ } else {
+ $('.toc_dokuwiki').find('#dw__toc').insertAfter($('.page h1:first'));
+ }
+ // $('.toc_dokuwiki').find('blockquote:first').insertAfter($('.page h1:first'));
+
+ // Anchor link should be shifted by header pixel
+ $(window).on("hashchange", function () {
+ window.scrollTo(window.scrollX, window.scrollY - 48);
+ });
+ }
+
+ $(function() {
+ initUI();
+ bindEvents();
+ });
+})(jQuery);
diff --git a/platform/www/lib/tpl/acervus/style.ini b/platform/www/lib/tpl/acervus/style.ini
new file mode 100644
index 0000000..48e316d
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/style.ini
@@ -0,0 +1,86 @@
+; Please see http://www.php.net/manual/en/function.parse-ini-file.php
+; for limitations of the ini format used here
+
+; To extend this file or make changes to it, it is recommended to create
+; a local conf/tpl/<template-folder-name>/style.ini file to prevent losing
+; any changes after an upgrade.
+; Please don't forget to copy the section your changes should be under
+; (i.e. [stylesheets] or [replacements]) into that file as well.
+
+; Define the stylesheets your template uses here. The second value
+; defines for which output media the style should be loaded. Currently
+; print, screen and all are supported.
+; You can reference CSS and LESS files here. Files referenced here will
+; be checked for updates when considering a cache rebuild while files
+; included through LESS' @import statements are not
+
+[stylesheets]
+
+../dokuwiki/css/_imgdetail.css = screen
+../dokuwiki/css/_media_popup.css = screen
+../dokuwiki/css/_media_fullscreen.css = screen
+../dokuwiki/css/_fileuploader.css = screen
+../dokuwiki/css/_tabs.css = screen
+../dokuwiki/css/_links.css = screen
+../dokuwiki/css/_toc.css = screen
+../dokuwiki/css/_footnotes.css = screen
+../dokuwiki/css/_search.less = screen
+../dokuwiki/css/_recent.css = screen
+../dokuwiki/css/_diff.css = screen
+../dokuwiki/css/_edit.css = screen
+../dokuwiki/css/_modal.css = screen
+../dokuwiki/css/_forms.css = screen
+../dokuwiki/css/_admin.less = screen
+
+css/basic.less = screen
+css/structure.less = screen
+css/design.less = screen
+css/content.less = screen
+css/responsive.css = screen
+css/hacks.css = screen
+
+css/mobile.less = all
+css/print.less = print
+
+
+; This section is used to configure some placeholder values used in
+; the stylesheets. Changing this file is the simplest method to
+; give your wiki a new look.
+; Placeholders defined here will also be made available as LESS variables
+; (with surrounding underscores removed, and the prefix @ini_ added)
+
+[replacements]
+
+;--------------------------------------------------------------------------
+;------ guaranteed dokuwiki color placeholders that every plugin can use
+
+; main text and background colors
+__text__ = "#333" ; @ini_text
+__background__ = "#fff" ; @ini_background
+; alternative text and background colors
+__text_alt__ = "#999" ; @ini_text_alt
+__background_alt__ = "#f6f6f6" ; @ini_background_alt
+; neutral text and background colors
+__text_neu__ = "#666" ; @ini_text_neu
+__background_neu__ = "#ddd" ; @ini_background_neu
+; border color
+__border__ = "#ddd" ; @ini_border
+
+; highlighted text (e.g. search snippets)
+__highlight__ = "#ff9" ; @ini_highlight
+
+; these are used for links
+__link__ = "#AA0000" ; @ini_link
+__existing__ = "#800000" ; @ini_existing
+__missing__ = "#858585" ; @ini_missing
+
+__numbered_heading__ = "#ff00cc" ; @ini_numbered_heading
+
+;--------------------------------------------------------------------------
+; fonts
+__font_size__ = "13pt" ; @ini_font_size
+__line_height__ = "1.6" ; @ini_line_height
+
+; widths
+__body_width__ = "56em" ; @ini_body_width
+__sidebar_width__ = "16em" ; @ini_sidebar_width
diff --git a/platform/www/lib/tpl/acervus/template.info.txt b/platform/www/lib/tpl/acervus/template.info.txt
new file mode 100644
index 0000000..7c68a3e
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/template.info.txt
@@ -0,0 +1,6 @@
+base acervus
+author Franco Augusto
+email franco@reevo.org
+date 2022
+name Illich theme
+desc Theme for Acervus platform. Simple, minimal and responsive template, based in 'White Template'
diff --git a/platform/www/lib/tpl/acervus/tpl_functions.php b/platform/www/lib/tpl/acervus/tpl_functions.php
new file mode 100644
index 0000000..ae82ea5
--- /dev/null
+++ b/platform/www/lib/tpl/acervus/tpl_functions.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Template Functions
+ *
+ * This file provides template specific custom functions that are
+ * not provided by the DokuWiki core.
+ * It is common practice to start each function with an underscore
+ * to make sure it won't interfere with future core functions.
+ */
+
+// must be run from within DokuWiki
+if (!defined('DOKU_INC')) die();
+
+/**
+ * copied to core (available since Detritus)
+ */
+function white_toolsevent($toolsname, $items, $view='main') {
+ $data = array(
+ 'view' => $view,
+ 'items' => $items
+ );
+
+ $hook = 'TEMPLATE_'.strtoupper($toolsname).'_DISPLAY';
+ $evt = new Doku_Event($hook, $data);
+ if($evt->advise_before()){
+ $actions = array('export_pdf');
+ foreach($evt->data['items'] as $k => $html) {
+ if (in_array($k, $actions)) {
+ $html = str_replace(' '.$k, ' plugin_'.$k, $html);
+ }
+ echo $html;
+ }
+ }
+ $evt->advise_after();
+}
+
+function white_breadcrumbs() {
+ global $lang;
+ global $conf;
+
+ //check if enabled
+ if(!$conf['breadcrumbs']) return false;
+
+ $crumbs = breadcrumbs(); //setup crumb trace
+
+ $crumbs_sep = ' <span class="bcsep">'.$sep.'</span> ';
+
+ //render crumbs, highlight the last one
+ print '<h3>'.$lang['breadcrumb'].'</h3>';
+ $last = count($crumbs);
+ $i = 0;
+ print '<ul>';
+ foreach($crumbs as $id => $name) {
+ $i++;
+ print '<li>';
+ if($i == $last) print '<span class="curid">';
+ tpl_link(wl($id), hsc($name), 'class="breadcrumbs" title="'.$id.'"');
+ if($i == $last) print '</span>';
+ print '</li>';
+ }
+ print '</ul>';
+ return true;
+}
+
+function white_pageinfo($ret = false) {
+ global $conf;
+ global $lang;
+ global $INFO;
+ global $ID;
+
+ // return if we are not allowed to view the page
+ if(!auth_quickaclcheck($ID)) {
+ return false;
+ }
+ $date = dformat($INFO['lastmod']);
+
+ // print it
+ if($INFO['exists']) {
+ $out = '';
+ $out .= $lang['lastmod'];
+ $out .= ' ';
+ $out .= $date;
+ if($ret) {
+ return $out;
+ } else {
+ echo $out;
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_admin.less b/platform/www/lib/tpl/dokuwiki/css/_admin.less
new file mode 100644
index 0000000..38ca4bc
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_admin.less
@@ -0,0 +1,64 @@
+.dokuwiki div.ui-admin {
+
+ /* main task grouped in two columns */
+ ul.admin_tasks {
+ float: left;
+ width: 40%;
+ list-style-type: none;
+ font-size: 1.125em;
+ }
+ [dir=rtl] & ul.admin_tasks {
+ float: right;
+ }
+
+ /* general menu item styling */
+ ul {
+ padding: 0;
+ li {
+ margin: 0 0 1em 0;
+ font-weight: bold;
+ list-style-type: none;
+ white-space: nowrap;
+
+ a {
+ display: flex;
+ span {
+ display: inline-block;
+
+ &.icon {
+ width: 1.5em;
+ min-height: 1.5em;
+ margin: 0 0.5em;
+ vertical-align: top;
+
+ svg {
+ width: 1.5em;
+ height: 1.5em;
+ fill: @ini_link;
+ display: inline-block;
+ path {
+ fill: @ini_link;
+ }
+ }
+ }
+
+ &.prompt {
+ white-space: normal;
+ }
+ }
+ }
+ }
+ }
+
+ /* DokuWiki version */
+ #admin__version {
+ clear: left;
+ float: right;
+ color: @ini_text_neu;
+ background-color: inherit;
+ }
+ [dir=rtl] & #admin__version {
+ clear: right;
+ float: left;
+ }
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_diff.css b/platform/www/lib/tpl/dokuwiki/css/_diff.css
new file mode 100644
index 0000000..bc56a37
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_diff.css
@@ -0,0 +1,137 @@
+/**
+ * This file provides styles for the diff view, which shows you
+ * differences between two versions of a page (?do=diff).
+ */
+
+.dokuwiki table.diff {
+ width: 100%;
+ border-width: 0;
+}
+.dokuwiki table.diff th,
+.dokuwiki table.diff td {
+ vertical-align: top;
+ padding: 0;
+ border-width: 0;
+ /* no style.ini colours because deleted and added lines have a fixed background colour */
+ background-color: #fff;
+ color: #333;
+}
+
+/* table header */
+.dokuwiki table.diff th {
+ border-bottom: 1px solid @ini_border;
+ font-size: 110%;
+ font-weight: normal;
+}
+.dokuwiki table.diff th a {
+ font-weight: bold;
+}
+.dokuwiki table.diff th span.user {
+ font-size: .9em;
+}
+.dokuwiki table.diff th span.sum {
+ font-size: .9em;
+ font-weight: bold;
+}
+.dokuwiki table.diff th.minor {
+ color: #999;
+}
+.dokuwiki table.diff_sidebyside th {
+ width: 50%;
+}
+
+/* table body */
+.dokuwiki table.diff .diff-lineheader {
+ width: .7em;
+ text-align: right;
+}
+[dir=rtl] .dokuwiki table.diff .diff-lineheader {
+ text-align: left;
+}
+.dokuwiki table.diff .diff-lineheader,
+.dokuwiki table.diff td {
+ font-family: Consolas, "Andale Mono WT", "Andale Mono", "Bitstream Vera Sans Mono", "Nimbus Mono L", Monaco, "Courier New", monospace;
+}
+.dokuwiki table.diff td.diff-blockheader {
+ font-weight: bold;
+}
+.dokuwiki table.diff .diff-addedline {
+ background-color: #cfc;
+ color: inherit;
+}
+.dokuwiki table.diff .diff-deletedline {
+ background-color: #fdd;
+ color: inherit;
+}
+.dokuwiki table.diff td.diff-context {
+ background-color: #eee;
+ color: inherit;
+}
+.dokuwiki table.diff td.diff-addedline strong,
+.dokuwiki table.diff td.diff-deletedline strong {
+ color: #f00;
+ background-color: inherit;
+ font-weight: bold;
+}
+
+/* diff options */
+
+.dokuwiki .diffoptions form {
+ float: left;
+}
+.dokuwiki .diffoptions p {
+ float: right;
+}
+
+/* diff nav */
+
+.dokuwiki table.diff_sidebyside td.diffnav {
+ padding-bottom: .7em;
+}
+.dokuwiki .diffnav a {
+ display: inline-block;
+ vertical-align: middle;
+}
+.dokuwiki .diffnav a span {
+ display: none;
+}
+
+.dokuwiki .diffnav a:hover,
+.dokuwiki .diffnav a:active,
+.dokuwiki .diffnav a:focus {
+ background-color: @ini_background_alt;
+ text-decoration: none;
+}
+
+.dokuwiki .diffnav a:before {
+ display: inline-block;
+ line-height: 1;
+ padding: .2em .4em;
+ border: 1px solid @ini_border;
+ border-radius: 2px;
+ color: @ini_text;
+}
+
+.dokuwiki .diffnav a.diffprevrev:before {
+ content: '\25C0'; /* left triangle */
+}
+.dokuwiki .diffnav a.diffnextrev:before,
+.dokuwiki .diffnav a.difflastrev:before {
+ content: '\25B6'; /* right triangle */
+}
+.dokuwiki .diffnav a.diffbothprevrev:before {
+ content: '\25C0\25C0';
+}
+.dokuwiki .diffnav a.diffbothnextrev:before {
+ content: '\25B6\25B6';
+}
+
+.dokuwiki .diffnav select {
+ width: 60%;
+ min-width: 9em;
+ height: 1.5em; /* height is necessary for longer options in Webkit */
+}
+
+.dokuwiki .diffnav select option[selected] {
+ font-weight: bold;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_edit.css b/platform/www/lib/tpl/dokuwiki/css/_edit.css
new file mode 100644
index 0000000..30926be
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_edit.css
@@ -0,0 +1,141 @@
+/**
+ * This file provides styles for the edit view (?do=edit), preview
+ * and section edit buttons.
+ */
+
+/* edit view
+********************************************************************/
+
+.dokuwiki div.editBox {
+}
+
+/*____________ toolbar ____________*/
+
+.dokuwiki div.toolbar {
+ display: inline-block;
+ margin-bottom: .5em;
+}
+#draft__status {
+ float: right;
+ color: @ini_text_alt;
+ background-color: inherit;
+}
+[dir=rtl] #draft__status {
+ float: left;
+}
+#tool__bar {
+ float: left;
+}
+[dir=rtl] #tool__bar {
+ float: right;
+}
+
+/* buttons inside of toolbar */
+.dokuwiki div.toolbar button.toolbutton {
+}
+/* picker popups (outside of .dokuwiki) */
+div.picker {
+ width: 300px;
+ border: 1px solid @ini_border;
+ background-color: @ini_background_alt;
+ color: inherit;
+}
+/* picker for headlines */
+div.picker.pk_hl {
+ width: auto;
+}
+
+/* buttons inside of picker */
+div.picker button.pickerbutton,
+div.picker button.toolbutton {
+ padding: .1em .35em;
+ border-width: 0;
+}
+
+/*____________ edit textarea ____________*/
+
+.dokuwiki textarea.edit {
+ width: 100%;
+ margin-bottom: .5em;
+}
+
+/*____________ below the textarea ____________*/
+
+.dokuwiki div.editBar {
+ overflow: hidden;
+ margin-bottom: .5em;
+}
+
+/* size and wrap controls */
+#size__ctl {
+ float: right;
+}
+[dir=rtl] #size__ctl {
+ float: left;
+}
+#size__ctl img {
+ cursor: pointer;
+}
+
+/* edit buttons */
+.dokuwiki .editBar .editButtons {
+ display: inline;
+ margin-right: 1em;
+}
+[dir=rtl] .dokuwiki .editBar .editButtons {
+ margin-right: 0;
+ margin-left: 1em;
+}
+.dokuwiki .editBar .editButtons button {
+}
+
+/* summary input and minor changes checkbox */
+.dokuwiki .editBar .summary {
+ display: inline;
+}
+.dokuwiki .editBar .summary label {
+ vertical-align: middle;
+ white-space: nowrap;
+}
+.dokuwiki .editBar .summary label span {
+ vertical-align: middle;
+}
+.dokuwiki .editBar .summary input {
+}
+/* change background colour if summary is missing */
+.dokuwiki .editBar .summary input.missing {
+ color: @ini_text;
+ background-color: #ffcccc;
+}
+
+/* preview
+********************************************************************/
+
+.dokuwiki div.preview {
+ border: dotted @ini_border;
+ border-width: .2em 0;
+ padding: 1.4em 0;
+ margin-bottom: 1.4em;
+}
+
+/* section edit buttons
+********************************************************************/
+
+.dokuwiki .secedit {
+ float: right;
+ margin-top: -1.4em;
+}
+[dir=rtl] .dokuwiki .secedit {
+ float: left;
+}
+.dokuwiki .secedit button {
+ font-size: 75%;
+}
+
+/* style for section highlighting */
+.dokuwiki div.section_highlight {
+ margin: 0 -1em; /* negative side margin = side padding + side border */
+ padding: 0 .5em;
+ border: solid @ini_background_alt;
+ border-width: 0 .5em;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_fileuploader.css b/platform/www/lib/tpl/dokuwiki/css/_fileuploader.css
new file mode 100644
index 0000000..f300396
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_fileuploader.css
@@ -0,0 +1,107 @@
+/**
+ * This file provides the styles for the file uploader
+ * used in the media manager (both fullscreen and popup).
+ */
+
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+
+.qq-uploader .error {
+ color: #f00;
+ background-color: #fff;
+}
+
+/* select file button */
+
+.qq-upload-button {
+ display: inline-block;
+ text-decoration: none;
+ font-size: 100%;
+ cursor: pointer;
+ margin: 1px 1px 5px;
+}
+
+.qq-upload-button-focus {
+ outline: 1px dotted;
+}
+
+/* drop area */
+
+.qq-upload-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 70px;
+ z-index: 2;
+ background: @ini_background_neu;
+ color: @ini_text;
+ 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: @ini_background_alt;
+}
+
+/* list of files to upload */
+
+div.qq-uploader ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.qq-uploader li {
+ margin: 0 0 5px;
+ color: @ini_text;
+}
+
+.qq-uploader li span,
+.qq-uploader li input,
+.qq-uploader li a {
+ margin-right: 5px;
+}
+
+.qq-upload-file {
+ display: block;
+ font-weight: bold;
+}
+
+.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-action-container * {
+ vertical-align: middle;
+}
+.qq-overwrite-check input {
+ margin-left: 10px;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_footnotes.css b/platform/www/lib/tpl/dokuwiki/css/_footnotes.css
new file mode 100644
index 0000000..a57dfb9
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_footnotes.css
@@ -0,0 +1,31 @@
+/**
+ * This file provides styles for footnotes.
+ */
+
+/*____________ footnotes inside the text ____________*/
+
+/* link to footnote inside the text */
+.dokuwiki sup a.fn_top {
+}
+/* JSpopup */
+div.insitu-footnote {
+ max-width: 40%;
+ min-width: 5em;
+}
+
+/*____________ footnotes at the bottom of the page ____________*/
+
+.dokuwiki div.footnotes {
+ border-top: 1px solid @ini_border;
+ padding: .5em 0 0 0;
+ margin: 1em 0 0 0;
+ clear: both;
+}
+.dokuwiki div.footnotes div.fn {
+}
+.dokuwiki div.footnotes div.fn div.content {
+ display: inline;
+}
+.dokuwiki div.footnotes div.fn sup a.fn_bot {
+ font-weight: bold;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_forms.css b/platform/www/lib/tpl/dokuwiki/css/_forms.css
new file mode 100644
index 0000000..bf70fa2
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_forms.css
@@ -0,0 +1,106 @@
+/* TODO: this file is not up to the best standards and will be fixed after an overhaul of the form code */
+
+/**
+ * This file provides styles for forms in general and specifically
+ * for ?do=
+ * - login
+ * - resendpwd
+ * - register
+ * - profile
+ * - subscribe
+ */
+
+/* ---------------- forms ------------------------ */
+
+.dokuwiki form {
+ border: none;
+ display: inline;
+}
+
+.dokuwiki label.block {
+ display: block;
+ text-align: right;
+ font-weight: bold;
+}
+[dir=rtl] .dokuwiki label.block {
+ text-align: left;
+}
+
+.dokuwiki label.simple {
+ display: block;
+ text-align: left;
+ font-weight: normal;
+}
+[dir=rtl] .dokuwiki label.simple {
+ text-align: right;
+}
+
+.dokuwiki label.block select,
+.dokuwiki label.block input.edit {
+ width: 50%;
+}
+
+.dokuwiki label span {
+ vertical-align: middle;
+}
+
+.dokuwiki fieldset {
+ width: 400px;
+ text-align: center;
+ border: 1px solid @ini_border;
+ padding: 0.5em;
+ margin: auto;
+}
+
+
+.dokuwiki input.edit,
+.dokuwiki select.edit {
+ vertical-align: middle;
+}
+.dokuwiki select.edit {
+ padding: 0.1em 0;
+}
+
+
+.dokuwiki button {
+ vertical-align: middle;
+}
+/**
+ * Styles for auth forms
+ */
+#dw__login label[for="remember__me"] {
+ margin-left: 50%;
+ margin-bottom: 1.4em;
+}
+#dw__login fieldset,
+#dw__resendpwd fieldset,
+#dw__register fieldset {
+ padding-bottom: 0.7em;
+}
+#dw__profiledelete {
+ display: block;
+ margin-top: 2.8em;
+}
+
+/**
+ * Styles for the subscription page
+ */
+
+#subscribe__form {
+ display: block;
+ width: 400px;
+ text-align: center;
+}
+
+#subscribe__form fieldset {
+ text-align: left;
+ margin: 0.5em 0;
+}
+[dir=rtl] #subscribe__form fieldset {
+ text-align: right;
+}
+
+#subscribe__form label {
+ display: block;
+ margin: 0 0.5em 0.5em;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_imgdetail.css b/platform/www/lib/tpl/dokuwiki/css/_imgdetail.css
new file mode 100644
index 0000000..a074000
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_imgdetail.css
@@ -0,0 +1,38 @@
+/**
+ * This file provides styles for the image detail page (detail.php).
+ */
+
+#dokuwiki__detail {
+ padding: 1em;
+}
+#dokuwiki__detail h1 {
+}
+
+#dokuwiki__detail img {
+ float: left;
+ margin: 0 1.5em .5em 0;
+}
+[dir=rtl] #dokuwiki__detail div.content img {
+ float: right;
+ margin-right: 0;
+ margin-left: 1.5em;
+}
+#dokuwiki__detail div.img_detail {
+ float: left;
+}
+[dir=rtl] #dokuwiki__detail div.content div.img_detail {
+ float: right
+}
+
+#dokuwiki__detail div.img_detail h2 {
+}
+#dokuwiki__detail div.img_detail dl {
+}
+#dokuwiki__detail div.img_detail dl dt {
+}
+#dokuwiki__detail div.img_detail dl dd {
+}
+
+#dokuwiki__detail p.back {
+ clear: both;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_links.css b/platform/www/lib/tpl/dokuwiki/css/_links.css
new file mode 100644
index 0000000..695f4b8
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_links.css
@@ -0,0 +1,69 @@
+/**
+ * This file provides styles for all types of links.
+ */
+
+/*____________ links to wiki pages ____________*/
+
+/* existing wikipage */
+.dokuwiki a.wikilink1 {
+}
+/* not existing wikipage */
+.dokuwiki a.wikilink2 {
+ text-decoration: none;
+}
+.dokuwiki a.wikilink2:link,
+.dokuwiki a.wikilink2:visited {
+ border-bottom: 1px dashed;
+}
+.dokuwiki a.wikilink2:hover,
+.dokuwiki a.wikilink2:active,
+.dokuwiki a.wikilink2:focus {
+ border-bottom-width: 0;
+}
+
+/* any link to current page */
+.dokuwiki span.curid a {
+ font-weight: bold;
+}
+
+/*____________ other link types ____________*/
+
+.dokuwiki a.urlextern,
+.dokuwiki a.windows,
+.dokuwiki a.mail,
+.dokuwiki a.mediafile,
+.dokuwiki a.interwiki {
+ background-repeat: no-repeat;
+ background-position: 0 center;
+ padding: 0 0 0 18px;
+}
+/* external link */
+.dokuwiki a.urlextern {
+ background-image: url(../../images/external-link.png);
+}
+/* windows share */
+.dokuwiki a.windows {
+ background-image: url(../../images/unc.png);
+}
+/* email link */
+.dokuwiki a.mail {
+ background-image: url(../../images/email.png);
+}
+
+/* icons of the following are set by dokuwiki in lib/exe/css.php */
+/* link to some embedded media */
+.dokuwiki a.mediafile {
+}
+/* interwiki link */
+.dokuwiki a.interwiki {
+}
+
+/* RTL corrections; if link icons don't work as expected, remove the following lines */
+[dir=rtl] .dokuwiki a.urlextern,
+[dir=rtl] .dokuwiki a.windows,
+[dir=rtl] .dokuwiki a.mail,
+[dir=rtl] .dokuwiki a.interwiki,
+[dir=rtl] .dokuwiki a.mediafile {
+ background-position: right center;
+ padding: 0 18px 0 0;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_media_fullscreen.css b/platform/www/lib/tpl/dokuwiki/css/_media_fullscreen.css
new file mode 100644
index 0000000..9a00d4d
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_media_fullscreen.css
@@ -0,0 +1,541 @@
+/**
+ * This file provides the styles for the fullscreen media manager
+ * (?do=media).
+ *
+ * What most templates would probably need to change (depending on
+ * their site width) are the 4 min-width's (search for @change).
+ */
+
+
+/*____________ structure ____________*/
+
+#mediamanager__page h1 {
+ margin-bottom: .5em;
+}
+
+#mediamanager__page {
+ /* min-width must be summary of all 3 panels' min-widths */
+ min-width: 50em; /* @change */
+ width: 100%;
+ text-align: left;
+}
+[dir=rtl] #mediamanager__page {
+ text-align: right;
+}
+#mediamanager__page .panel {
+ float: left;
+}
+[dir=rtl] #mediamanager__page .panel {
+ float: right;
+}
+
+#mediamanager__page .namespaces {
+ width: 20%;
+ min-width: 10em; /* @change */
+ left:0 !important; /* overrules jQuery UI resizable in rtl */
+}
+#mediamanager__page .filelist {
+ width: 50%;
+ min-width: 25em; /* @change */
+ left:0 !important; /* overrules jQuery UI resizable in rtl */
+}
+#mediamanager__page .file {
+ width: 30%;
+ min-width: 15em; /* @change */
+}
+
+#mediamanager__page .tabs li {
+ white-space: nowrap;
+}
+
+#mediamanager__page .panelHeader {
+ background-color: @ini_background_alt;
+ margin: 0 10px 10px 0;
+ padding: 10px 10px 8px;
+ text-align: left;
+ min-height: 20px;
+ overflow: hidden;
+}
+[dir=rtl] #mediamanager__page .panelHeader {
+ text-align: right;
+ margin: 0 0 10px 10px;
+}
+#mediamanager__page .panelContent {
+ overflow-y: auto;
+ overflow-x: hidden;
+ padding: 0;
+ margin: 0 10px 10px 0;
+ position: relative;
+}
+[dir=rtl] #mediamanager__page .panelContent {
+ text-align: right;
+ margin: 0 0 10px 10px;
+}
+
+#mediamanager__page .file .panelHeader,
+#mediamanager__page .file .panelContent {
+ margin-right: 0;
+}
+[dir=rtl] #mediamanager__page .file .panelHeader,
+[dir=rtl] #mediamanager__page .file .panelContent {
+ margin-left: 0;
+}
+
+#mediamanager__page .ui-resizable-e {
+ width: 6px;
+ right: 2px;
+ background: transparent url(../../images/resizecol.png) center center no-repeat;
+}
+#mediamanager__page .ui-resizable-e:hover {
+ background-color: @ini_background_alt;
+}
+[dir=rtl] #mediamanager__page .ui-resizable-w {
+ width: 6px;
+ left: 2px;
+ background: transparent url(../../images/resizecol.png) center center no-repeat;
+}
+[dir=rtl] #mediamanager__page .ui-resizable-w:hover {
+ background-color: @ini_background_alt;
+}
+
+
+#mediamanager__page dd {
+ margin: 0;
+}
+
+#mediamanager__page .panelHeader h3 {
+ float: left;
+ font-weight: normal;
+ font-size: 1em;
+ padding: 0;
+ margin: 0 0 3px;
+}
+[dir=rtl] #mediamanager__page .panelHeader h3 {
+ float : right
+}
+
+
+/*____________ namespaces panel ____________*/
+
+[dir=rtl] #mediamanager__page .namespaces {
+ text-align: right;
+}
+
+/* make it look like a tab (as in _tabs.css) */
+#mediamanager__page .namespaces h2 {
+ font-size: 1em;
+ display: inline-block;
+ padding: .3em .8em;
+ margin: 0 0 0 .3em;
+ border-radius: .5em .5em 0 0;
+ font-weight: normal;
+ background-color: @ini_background_alt;
+ color: @ini_text;
+ border: 1px solid @ini_border;
+ border-bottom-color: @ini_background_alt;
+ line-height: 1.4em;
+ position: relative;
+ bottom: -1px;
+ z-index: 2;
+}
+[dir=rtl] #mediamanager__page .namespaces h2 {
+ margin: 0 .3em 0 0;
+ position: relative;
+ right: 10px;
+}
+#mediamanager__page .namespaces .panelHeader {
+ border-top: 1px solid @ini_border;
+ z-index: 1;
+}
+
+#mediamanager__page .namespaces ul {
+ margin-left: .2em;
+ margin-bottom: 0;
+ padding: 0;
+ list-style: none;
+}
+[dir=rtl] #mediamanager__page .namespaces ul {
+ margin-left: 0;
+ margin-right: .2em;
+}
+#mediamanager__page .namespaces ul ul {
+ margin-left: 1em;
+}
+[dir=rtl] #mediamanager__page .namespaces ul ul {
+ margin-left: 0;
+ margin-right: 1em;
+}
+#mediamanager__page .namespaces ul ul li {
+ margin: 0;
+}
+
+#mediamanager__page .namespaces ul .selected {
+ background-color: __highlight__;
+ font-weight: bold;
+}
+
+
+/*____________ file list panel ____________*/
+
+/* file list header */
+
+#mediamanager__page .panelHeader form.options {
+ float: right;
+ margin-top: -3px;
+}
+[dir=rtl] #mediamanager__page .panelHeader form.options {
+ float : left
+}
+#mediamanager__page .panelHeader ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+#mediamanager__page .panelHeader ul li {
+ color: @ini_text;
+ float: left;
+ line-height: 1;
+ padding-left: 3px;
+}
+[dir=rtl] #mediamanager__page .panelHeader ul li {
+ padding-right: 3px;
+ padding-left: 0;
+ float: right;
+}
+
+#mediamanager__page .panelHeader ul li.ui-controlgroup-horizontal {
+ padding-left: 30px;
+ margin: 0 0 0 5px;
+}
+#mediamanager__page .panelHeader ul li.listType {
+ background: url('../../images/icon-list.png') 3px 1px no-repeat;
+}
+#mediamanager__page .panelHeader ul li.sortBy {
+ background: url('../../images/icon-sort.png') 3px 1px no-repeat;
+}
+[dir=rtl] #mediamanager__page .panelHeader ul li.ui-controlgroup-horizontal {
+ padding-left: 0;
+ padding-right: 30px;
+ margin: 0 5px 0 0;
+ background-position: right 1px;
+}
+
+#mediamanager__page .panelHeader form.options .ui-controlgroup-horizontal label{
+ font-size: 90%;
+ margin-right: -0.4em;
+ padding: .3em .5em;
+ line-height: 1;
+}
+
+/* file list content */
+
+#mediamanager__page .filelist ul {
+ padding: 0;
+ margin: 0 10px 0 0;
+}
+[dir=rtl] #mediamanager__page .filelist ul {
+ margin: 0 0 0 10px;
+}
+#mediamanager__page .filelist ul.rows {
+ margin: 0;
+}
+#mediamanager__page .filelist .panelContent ul li:hover {
+ background-color: @ini_background_alt;
+}
+
+#mediamanager__page .filelist li dt a {
+ vertical-align: middle;
+ display: table-cell;
+ overflow: hidden;
+}
+
+/* file list as thumbs */
+
+#mediamanager__page .filelist .thumbs li {
+ width: 100px;
+ min-height: 130px;
+ display: inline-block;
+ /* 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: @ini_background_neu;
+ color: @ini_text;
+ padding: 5px;
+ vertical-align: top;
+ text-align: center;
+ position: relative;
+ line-height: 1.2;
+}
+[dir=rtl] #mediamanager__page .filelist .thumbs li {
+ margin-right: 0;
+ margin-left: 6px;
+}
+
+#mediamanager__page .filelist .thumbs li dt a {
+ width: 100px;
+ height: 90px;
+}
+
+#mediamanager__page .filelist .thumbs li dt a img {
+ max-width: 90px;
+ max-height: 90px;
+}
+
+#mediamanager__page .filelist .thumbs li .name,
+#mediamanager__page .filelist .thumbs li .size,
+#mediamanager__page .filelist .thumbs li .filesize,
+#mediamanager__page .filelist .thumbs li .date {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 90px;
+ white-space: nowrap;
+}
+#mediamanager__page .filelist .thumbs li .name {
+ padding: 5px 0;
+ font-weight: bold;
+}
+#mediamanager__page .filelist .thumbs li .date {
+ font-style: italic;
+ white-space: normal;
+}
+
+/* file list as rows */
+
+#mediamanager__page .filelist .rows li {
+ list-style: none;
+ display: block;
+ position: relative;
+ max-height: 50px;
+ margin: 0 0 3px 0;
+ background-color: @ini_background;
+ color: @ini_text;
+ overflow: hidden;
+}
+
+#mediamanager__page .filelist .rows li:nth-child(2n+1) {
+ background-color: @ini_background_neu;
+}
+
+#mediamanager__page .filelist .rows li dt {
+ float: left;
+ width: 10%;
+ height: 40px;
+ text-align: center;
+}
+[dir=rtl] #mediamanager__page .filelist .rows li dt {
+ float: right;
+}
+#mediamanager__page .filelist .rows li dt a {
+ width: 100px;
+ height: 40px;
+}
+
+#mediamanager__page .filelist .rows li dt a img {
+ max-width: 40px;
+ max-height: 40px;
+}
+
+#mediamanager__page .filelist .rows li .name,
+#mediamanager__page .filelist .rows li .size,
+#mediamanager__page .filelist .rows li .filesize,
+#mediamanager__page .filelist .rows li .date {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ float: left;
+ margin-left: 1%;
+ white-space: nowrap;
+}
+[dir=rtl] #mediamanager__page .filelist .rows li .name,
+[dir=rtl] #mediamanager__page .filelist .rows li .size,
+[dir=rtl] #mediamanager__page .filelist .rows li .filesize,
+[dir=rtl] #mediamanager__page .filelist .rows li .date {
+ float: right;
+ margin-left: 0;
+ margin-right: 1%;
+}
+
+#mediamanager__page .filelist .rows li .name {
+ width: 30%;
+ font-weight: bold;
+}
+#mediamanager__page .filelist .rows li .size,
+#mediamanager__page .filelist .rows li .filesize {
+ width: 15%;
+}
+#mediamanager__page .filelist .rows li .date {
+ width: 20%;
+ font-style: italic;
+ white-space: normal;
+}
+
+/* upload form */
+
+#mediamanager__page div.upload {
+ padding-bottom: 0.5em;
+}
+
+/*____________ file panel ____________*/
+
+#mediamanager__page .file ul.actions {
+ text-align: center;
+ margin: 0 0 5px;
+ padding: 0;
+ list-style: none;
+}
+#mediamanager__page .file ul.actions li {
+ display: inline;
+ margin: 0;
+}
+
+#mediamanager__page .file div.image {
+ margin-bottom: 5px;
+ text-align: center;
+}
+
+#mediamanager__page .file div.image img {
+ width: 100%;
+}
+
+#mediamanager__page .file dl {
+ margin-bottom: 0;
+}
+#mediamanager__page .file dl dt {
+ font-weight: bold;
+ display: block;
+ background-color: @ini_background_alt;
+}
+#mediamanager__page .file dl dd {
+ display: block;
+ background-color: @ini_background_neu;
+}
+
+
+/* file meta data edit form */
+
+#mediamanager__page form.meta div.row {
+ margin-bottom: 5px;
+}
+
+#mediamanager__page form.meta label span {
+ display: block;
+}
+
+#mediamanager__page form.meta input {
+ width: 50%;
+}
+
+#mediamanager__page form.meta button {
+ width: auto;
+}
+
+#mediamanager__page form.meta textarea.edit {
+ height: 6em;
+ width: 95%;
+ min-width: 95%;
+ max-width: 95%;
+}
+
+/* file revisions form */
+
+#mediamanager__page form.changes ul {
+ margin-left: 10px;
+ padding: 0;
+ list-style-type: none;
+}
+[dir=rtl] #mediamanager__page form.changes ul {
+ margin-left: 0;
+ margin-right: 10px;
+}
+
+#mediamanager__page form.changes ul li div.li div {
+ font-size: 90%;
+ color: @ini_text_neu;
+ padding-left: 18px;
+}
+[dir=rtl] #mediamanager__page form.changes ul li div.li div {
+ padding-left: 0;
+ padding-right: 18px;
+}
+#mediamanager__page form.changes ul li div.li input {
+ position: relative;
+ top: 1px;
+}
+
+/* file diff view */
+
+#mediamanager__diff table {
+ table-layout: fixed;
+ border-width: 0;
+}
+
+#mediamanager__diff td,
+#mediamanager__diff th {
+ width: 48%;
+ margin: 0 5px 10px 0;
+ padding: 0;
+ vertical-align: top;
+ text-align: left;
+ border-color: @ini_background;
+}
+[dir=rtl] #mediamanager__diff td,
+[dir=rtl] #mediamanager__diff th {
+ margin: 0 0 10px 5px;
+ text-align: right;
+}
+
+#mediamanager__diff th {
+ font-weight: normal;
+ background-color: @ini_background;
+ line-height: 1.2;
+}
+#mediamanager__diff th a {
+ font-weight: bold;
+}
+#mediamanager__diff th span {
+ font-size: 90%;
+}
+
+#mediamanager__diff dl dd strong{
+ background-color: __highlight__;
+ color: @ini_text;
+ font-weight: normal;
+}
+
+/* image diff views */
+
+#mediamanager__page .file form.diffView {
+ margin-bottom: 10px;
+ display: block;
+}
+
+#mediamanager__diff div.slider {
+ margin: 10px;
+ width: 95%;
+}
+
+#mediamanager__diff .imageDiff {
+ position: relative;
+}
+#mediamanager__diff .imageDiff .image2 {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+#mediamanager__diff .imageDiff.opacity .image2 {
+ opacity: 0.5;
+}
+
+#mediamanager__diff .imageDiff.portions .image2 {
+ border-right: 1px solid red;
+ overflow: hidden;
+}
+
+#mediamanager__diff .imageDiff.portions img {
+ float: left;
+}
+
+#mediamanager__diff .imageDiff img {
+ width: 100%;
+ max-width: none;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_media_popup.css b/platform/www/lib/tpl/dokuwiki/css/_media_popup.css
new file mode 100644
index 0000000..0be5c49
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_media_popup.css
@@ -0,0 +1,208 @@
+/**
+ * This file provides styles for the media manager popup
+ * (mediamanager.php).
+ */
+
+/*____________ structure ____________*/
+
+html.popup {
+ overflow: auto;
+}
+
+#media__manager {
+ height: 100%;
+ overflow: hidden;
+}
+
+#mediamgr__aside {
+ width: 30%;
+ height: 100%;
+ overflow: auto;
+ position: absolute;
+ left: 0;
+ border-right: 1px solid @ini_border;
+}
+[dir=rtl] #mediamgr__aside {
+ left: auto;
+ right: 0;
+ border-right-width: 0;
+ border-left: 1px solid @ini_border;
+}
+#mediamgr__aside .pad {
+ padding: .5em;
+}
+
+#mediamgr__content {
+ width: 69.7%;
+ height: 100%;
+ overflow: auto;
+ position: absolute;
+ right: 0;
+}
+[dir=rtl] #mediamgr__content {
+ right: auto;
+ left: 0;
+}
+#mediamgr__content .pad {
+ padding: .5em;
+}
+
+#media__manager h1,
+#media__manager h2 {
+ font-size: 1.5em;
+ margin-bottom: .5em;
+ padding-bottom: .2em;
+ border-bottom: 1px solid @ini_border;
+}
+
+/* left side
+********************************************************************/
+
+/*____________ options ____________*/
+
+#media__opts {
+ margin-bottom: .5em;
+}
+
+#media__opts input {
+ margin-right: .3em;
+}
+[dir=rtl] #media__opts input {
+ margin-right: 0;
+ margin-left: .3em;
+}
+#media__opts label {
+}
+
+/*____________ tree ____________*/
+
+#media__tree ul {
+ padding-left: .2em;
+}
+[dir=rtl] #media__tree ul {
+ padding-left: 0;
+ padding-right: .2em;
+}
+#media__tree ul li {
+ clear: left;
+ list-style-type: none;
+ list-style-image: none;
+ margin-left: 0;
+}
+[dir=rtl] #media__tree ul li {
+ clear: right;
+ margin-right: 0;
+}
+#media__tree ul li img {
+ float: left;
+ padding: .5em .3em 0 0;
+}
+[dir=rtl] #media__tree ul li img {
+ float: right;
+ padding: .5em 0 0 .3em;
+}
+#media__tree ul li div.li {
+ display: inline;
+}
+#media__tree ul li li {
+ margin-left: 1.5em;
+}
+[dir=rtl] #media__tree ul li li {
+ margin-left: 0;
+ margin-right: 1.5em;
+}
+
+/* right side
+********************************************************************/
+
+/*____________ upload form ____________*/
+
+/* upload info */
+#media__content div.upload {
+ font-size: .9em;
+ margin-bottom: .5em;
+}
+
+#mediamanager__uploader {
+ margin-bottom: 1em;
+}
+#mediamanager__uploader p {
+ margin-bottom: .5em;
+}
+
+/*____________ file list ____________*/
+
+#media__content img.load {
+ margin: 1em auto;
+}
+
+#media__content .odd,
+#media__content .even {
+ padding: .5em;
+}
+#media__content .odd {
+ background-color: @ini_background_alt;
+}
+#media__content .even {
+}
+/* highlight newly uploaded or edited file */
+#media__content #scroll__here {
+ border: 1px dashed @ini_border;
+}
+
+/* link which inserts media file */
+#media__content a.mediafile {
+ margin-right: 1.5em;
+ font-weight: bold;
+ cursor: pointer;
+}
+[dir=rtl] #media__content a.mediafile {
+ margin-right: 0;
+ margin-left: 1.5em;
+}
+#media__content span.info {
+}
+#media__content img.btn {
+ vertical-align: text-bottom;
+}
+
+/* info how to insert media, if JS disabled */
+#media__content div.example {
+ color: @ini_text_neu;
+ margin-left: 1em;
+}
+
+#media__content div.detail {
+ padding: .2em 0;
+}
+#media__content div.detail div.thumb {
+ float: left;
+ margin: 0 .5em 0 18px;
+}
+[dir=rtl] #media__content div.detail div.thumb {
+ float: right;
+ margin: 0 18px 0 .5em;
+}
+#media__content div.detail div.thumb a {
+ display: block;
+ cursor: pointer;
+}
+#media__content div.detail p {
+ margin-bottom: 0;
+}
+
+
+/*____________ media search ____________*/
+
+#dw__mediasearch {
+}
+#dw__mediasearch p {
+}
+#dw__mediasearch label {
+}
+#dw__mediasearch label span {
+}
+#dw__mediasearch input.edit {
+}
+#dw__mediasearch button {
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_modal.css b/platform/www/lib/tpl/dokuwiki/css/_modal.css
new file mode 100644
index 0000000..37f6483
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_modal.css
@@ -0,0 +1,94 @@
+/**
+ * This file provides styles for modal dialogues.
+ */
+
+.dokuwiki .ui-widget {
+ font-size: 100%;
+}
+
+
+/* link wizard (opens from the link button in the edit toolbar)
+********************************************************************/
+
+#link__wiz {
+}
+
+[dir=rtl] #link__wiz_close {
+ float: left;
+}
+
+#link__wiz_result {
+ background-color: @ini_background;
+ width: 293px;
+ height: 193px;
+ overflow: auto;
+ border: 1px solid @ini_border;
+ margin: 3px auto;
+ text-align: left;
+ line-height: 1;
+}
+[dir=rtl] #link__wiz_result {
+ text-align: right;
+}
+
+#link__wiz_result div {
+ padding: 3px 3px 3px 0;
+}
+
+#link__wiz_result div a {
+ display: block;
+ padding-left: 22px;
+ min-height: 16px;
+ background: transparent 3px center no-repeat;
+}
+[dir=rtl] #link__wiz_result div a {
+ padding: 3px 22px 3px 3px;
+ background-position: 257px 3px;
+}
+
+#link__wiz_result div.type_u a {
+ background-image: url(../../images/up.png);
+}
+#link__wiz_result div.type_f a {
+ background-image: url(../../images/page.png);
+}
+#link__wiz_result div.type_d a {
+ background-image: url(../../images/ns.png);
+}
+
+#link__wiz_result div.even {
+ background-color: @ini_background_neu;
+}
+
+#link__wiz_result div.selected {
+ background-color: @ini_background_alt;
+}
+
+#link__wiz_result span {
+ display: block;
+ color: @ini_text_neu;
+ margin-left: 22px;
+}
+
+
+/* media option wizard (opens when inserting media in the media popup)
+********************************************************************/
+
+#media__popup {
+ /* for backwards compatibility (not needed since Rincewind) */
+ display: none;
+}
+
+#media__popup_content p {
+ margin: 0 0 .5em;
+}
+
+#media__popup_content label {
+ margin-right: .5em;
+ cursor: default;
+}
+
+#media__popup_content button {
+ margin-right: 1px;
+ cursor: pointer;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_recent.css b/platform/www/lib/tpl/dokuwiki/css/_recent.css
new file mode 100644
index 0000000..f1be15f
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_recent.css
@@ -0,0 +1,75 @@
+/**
+ * This file provides styles for the recent changes (?do=recent) and
+ * old revisions (?do=revisions).
+ */
+
+/*____________ list of revisions / recent changes ____________*/
+
+/* select type of revisions (media/pages) */
+.dokuwiki .changeType {
+ margin-bottom: .5em;
+}
+
+.dokuwiki form.changes ul li {
+ list-style: none;
+ margin-left: 0;
+}
+[dir=rtl] .dokuwiki form.changes ul li {
+ margin-right: 0;
+}
+.dokuwiki form.changes ul li span,
+.dokuwiki form.changes ul li a {
+ vertical-align: middle;
+}
+.dokuwiki form.changes ul li span.user a {
+ vertical-align: bottom;
+}
+.dokuwiki form.changes ul li.minor {
+ opacity: .7;
+}
+
+.dokuwiki form.changes li span.date {
+}
+.dokuwiki form.changes li a.diff_link {
+ vertical-align: baseline;
+}
+.dokuwiki form.changes li a.revisions_link {
+ vertical-align: baseline;
+}
+.dokuwiki form.changes li a.wikilink1,
+.dokuwiki form.changes li a.wikilink2 {
+}
+.dokuwiki form.changes li span.sum {
+ font-weight: bold;
+}
+.dokuwiki form.changes li span.user {
+}
+
+/*____________ size differences ____________*/
+
+.dokuwiki form.changes li .sizechange {
+ font-size: 80%;
+ border-radius: .2em;
+ padding: .1em .2em;
+ /* cannot use non-guaranteed style.ini colour placeholders, dark templates need to overwrite */
+ background-color: #ddd;
+}
+
+.dokuwiki form.changes li .sizechange.positive {
+ background-color: #cfc;;
+}
+.dokuwiki form.changes li .sizechange.negative {
+ background-color: #fdd;
+}
+
+/*____________ page navigator ____________*/
+
+.dokuwiki div.pagenav {
+ text-align: center;
+ margin: 1.4em 0;
+}
+.dokuwiki div.pagenav-prev,
+.dokuwiki div.pagenav-next {
+ display: inline;
+ margin: 0 .5em;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_search.less b/platform/www/lib/tpl/dokuwiki/css/_search.less
new file mode 100644
index 0000000..59400f9
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_search.less
@@ -0,0 +1,204 @@
+/**
+ * This file provides styles for the search results page (?do=search)
+ * and the AJAX search popup.
+ */
+
+/* general
+********************************************************************/
+
+/* search hit in normal text */
+.dokuwiki .search_hit {
+ color: @ini_text;
+ background-color: __highlight__;
+}
+
+/* "nothing found" at search + media */
+.dokuwiki div.nothing {
+ margin-bottom: 1.4em;
+}
+
+/* search results page
+********************************************************************/
+
+/*____________ advanced search form ____________*/
+.dokuwiki .search-results-form fieldset.search-form {
+ width: 100%;
+ margin: 1em 0;
+
+ input[name="q"] {
+ width: 50%;
+ }
+
+ button.toggleAssistant {
+ float: right;
+ }
+
+ .advancedOptions {
+ padding: 1em 0;
+
+ > div {
+ display: inline-block;
+ position: relative;
+ margin: 0 0.5em;
+ }
+
+ div.toggle {
+ // default closed toggle state
+ div.current {
+ cursor: pointer;
+ max-width: 10em;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ &::after {
+ content: '▼';
+ font-size: smaller;
+ color: @ini_text_alt;
+ }
+ }
+ div.changed {
+ font-weight: bold;
+ }
+ ul {
+ display: none;
+ position: absolute;
+ border: 1px solid @ini_border;
+ background-color: @ini_background;
+ padding: 0.25em 0.5em;
+ text-align: left;
+ min-width: 10em;
+ max-width: 15em;
+ max-height: 50vh;
+ overflow: auto;
+ z-index: 100;
+ li {
+ margin: 0.25em 0;
+ list-style: none;
+
+ a {
+ display: block;
+ }
+ }
+ }
+
+ // open toggle state
+ &.open {
+ div.current::after {
+ content: '▲';
+ }
+
+ ul {
+ display: block;
+ }
+ }
+ }
+ }
+}
+
+[dir=rtl] .search-results-form fieldset.search-form .advancedOptions {
+ div.toggle ul {
+ text-align: right;
+ }
+}
+
+
+/*____________ matching pagenames ____________*/
+
+.dokuwiki div.search_quickresult {
+ margin-bottom: 1.4em;
+
+ ul {
+ padding: 0;
+
+ li {
+ float: left;
+ width: 12em;
+ margin: 0 1.5em;
+ }
+ }
+}
+
+[dir=rtl] .dokuwiki div.search_quickresult ul li {
+ float: right;
+}
+
+/*____________ search results ____________*/
+
+.dokuwiki dl.search_results {
+ margin-bottom: 1.2em;
+
+ /* search heading */
+ dt {
+ font-weight: normal;
+ margin-bottom: .2em;
+ }
+
+ /* last modified line */
+ dd.meta {
+ margin: 0 0 .2em 0;
+ }
+
+ /* search snippet */
+ dd.snippet {
+ color: @ini_text_alt;
+ background-color: inherit;
+ margin: 0 0 1.2em 0;
+
+ /* search hit in search results */
+ strong.search_hit {
+ font-weight: normal;
+ /* color is set in general */
+ }
+
+ /* ellipsis separating snippets */
+ .search_sep {
+ color: @ini_text;
+ background-color: inherit;
+ }
+ }
+}
+
+/* AJAX quicksearch popup
+********************************************************************/
+
+.dokuwiki form.search {
+ div.no {
+ position: relative;
+ }
+
+ /* .JSpopup */
+ div.ajax_qsearch {
+ position: absolute;
+ top: 0;
+ left: -13.5em; /* -( width of #qsearch__in + padding of .ajax_qsearch + a bit more ) */
+ width: 12em;
+ padding: 0.5em;
+ font-size: .9em;
+ z-index: 20;
+ text-align: left;
+ display: none;
+
+ strong {
+ display: block;
+ margin-bottom: .3em;
+ }
+
+ ul {
+ margin: 0 !important;
+ padding: 0 !important;
+
+ li {
+ margin: 0;
+ padding: 0;
+ display: block !important;
+ }
+ }
+ }
+}
+
+[dir=rtl] .dokuwiki form.search div.ajax_qsearch {
+ left: auto;
+ right: -13.5em;
+ text-align: right;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_tabs.css b/platform/www/lib/tpl/dokuwiki/css/_tabs.css
new file mode 100644
index 0000000..507f49e
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_tabs.css
@@ -0,0 +1,84 @@
+/**
+ * This file provides the styles for general tabs.
+ */
+
+.dokuwiki .tabs > ul,
+.dokuwiki ul.tabs {
+ padding: 0;
+ margin: 0;
+ overflow: hidden;
+ position: relative;
+}
+/* border underneath */
+.dokuwiki .tabs > ul:after,
+.dokuwiki ul.tabs:after {
+ position: absolute;
+ content: "";
+ width: 100%;
+ bottom: 0;
+ left: 0;
+ border-bottom: 1px solid @ini_border;
+}
+
+.dokuwiki .tabs > ul li,
+.dokuwiki ul.tabs li {
+ float: left;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+}
+[dir=rtl] .dokuwiki .tabs > ul li,
+[dir=rtl] .dokuwiki ul.tabs li {
+ float: right;
+}
+
+.dokuwiki .tabs > ul li a,
+.dokuwiki ul.tabs li strong,
+.dokuwiki ul.tabs li a {
+ display: inline-block;
+ padding: .3em .8em;
+ margin: 0 0 0 .3em;
+ background-color: @ini_background_neu;
+ color: @ini_text;
+ border: 1px solid @ini_border;
+ border-radius: .5em .5em 0 0;
+ position: relative;
+ z-index: 0;
+}
+[dir=rtl] .dokuwiki .tabs > ul li a,
+[dir=rtl] .dokuwiki ul.tabs li strong,
+[dir=rtl] .dokuwiki ul.tabs li a {
+ margin: 0 .3em 0 0;
+}
+
+.dokuwiki ul.tabs li strong {
+ font-weight: normal;
+}
+
+.dokuwiki ul.tabs li a:link,
+.dokuwiki ul.tabs li a:visited {
+}
+.dokuwiki .tabs > ul li a:hover,
+.dokuwiki .tabs > ul li a:active,
+.dokuwiki .tabs > ul li a:focus,
+.dokuwiki .tabs > ul li .curid a,
+.dokuwiki .tabs > ul .active a,
+.dokuwiki ul.tabs li a:hover,
+.dokuwiki ul.tabs li a:active,
+.dokuwiki ul.tabs li a:focus,
+.dokuwiki ul.tabs li.active a,
+.dokuwiki ul.tabs li strong {
+ background-color: @ini_background_alt;
+ color: @ini_text;
+ text-decoration: none;
+ font-weight: normal;
+}
+
+.dokuwiki .tabs > ul li .curid a,
+.dokuwiki .tabs > ul li .active a,
+.dokuwiki .tabs > ul li .active a,
+.dokuwiki ul.tabs li.active a,
+.dokuwiki ul.tabs li strong {
+ z-index: 2;
+ border-bottom-color: @ini_background_alt;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/_toc.css b/platform/www/lib/tpl/dokuwiki/css/_toc.css
new file mode 100644
index 0000000..469e927
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/_toc.css
@@ -0,0 +1,93 @@
+/**
+ * This file provides styles for the TOC (table of contents), the
+ * sitemap (?do=index) and backlinks (?do=backlink).
+ */
+
+/* toc
+********************************************************************/
+
+/* toc container */
+#dw__toc {
+ float: right;
+ margin: 0 0 1.4em 1.4em;
+ width: 12em;
+ background-color: @ini_background_alt;
+ color: inherit;
+}
+[dir=rtl] #dw__toc {
+ float: left;
+ margin: 0 1.4em 1.4em 0;
+}
+
+/*____________ toc header ____________*/
+
+.dokuwiki h3.toggle {
+ padding: .2em .5em;
+ font-weight: bold;
+}
+
+.dokuwiki .toggle strong {
+ float: right;
+ margin: 0 .2em;
+}
+[dir=rtl] .dokuwiki .toggle strong {
+ float: left;
+}
+
+/*____________ toc list ____________*/
+
+#dw__toc > div {
+ padding: .2em .5em;
+}
+#dw__toc ul {
+ padding: 0;
+ margin: 0;
+}
+#dw__toc ul li {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ line-height: 1.1;
+}
+#dw__toc ul li div.li {
+ padding: .15em 0;
+}
+#dw__toc ul ul {
+ padding-left: 1em;
+}
+[dir=rtl] #dw__toc ul ul {
+ padding-left: 0;
+ padding-right: 1em;
+}
+#dw__toc ul ul li {
+}
+#dw__toc ul li a {
+}
+
+/* in case of toc list jumping one level
+ (e.g. if heading level 3 follows directly after heading level 1) */
+#dw__toc ul li.clear {
+}
+
+
+/* sitemap (and backlinks)
+********************************************************************/
+
+.dokuwiki ul.idx {
+ padding-left: 0;
+}
+[dir=rtl] .dokuwiki ul.idx {
+ padding-right: 0;
+}
+.dokuwiki ul.idx li {
+ list-style-image: url(../../images/bullet.png);
+}
+.dokuwiki ul.idx li.open {
+ list-style-image: url(../../images/open.png);
+}
+.dokuwiki ul.idx li.closed {
+ list-style-image: url(../../images/closed.png);
+}
+[dir=rtl] .dokuwiki ul.idx li.closed {
+ list-style-image: url(../../images/closed-rtl.png);
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/basic.less b/platform/www/lib/tpl/dokuwiki/css/basic.less
new file mode 100644
index 0000000..abb330a
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/basic.less
@@ -0,0 +1,464 @@
+/**
+ * This file provides the most basic styles.
+ *
+ * If you integrate DokuWiki into another project, you might either
+ * want to integrate this file into the other project as well, or use
+ * the other project's basic CSS for DokuWiki instead of this one.
+ *
+ * @author Anika Henke <anika@selfthinker.org>
+ */
+
+html {
+ overflow-x: auto;
+ overflow-y: scroll;
+}
+html,
+body {
+ color: @ini_text;
+ background: @ini_background_site url(images/page-gradient.png) top left repeat-x;
+ margin: 0;
+ padding: 0;
+}
+body {
+ font: normal 87.5%/1.4 Arial, sans-serif;
+ /* default font size: 100% => 16px; 93.75% => 15px; 87.5% => 14px; 81.25% => 13px; 75% => 12px */
+ -webkit-text-size-adjust: 100%;
+}
+
+
+/*____________ headers ____________*/
+
+caption,
+figcaption,
+summary,
+legend {
+ padding: 0;
+ margin: 0 0 .35em;
+ line-height: 1.2;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-weight: bold;
+ padding: 0;
+ line-height: 1.2;
+ clear: left; /* ideally 'both', but problems with toc */
+}
+[dir=rtl] h1,
+[dir=rtl] h2,
+[dir=rtl] h3,
+[dir=rtl] h4,
+[dir=rtl] h5,
+[dir=rtl] h6 {
+ clear: right;
+}
+
+h1 {
+ font-size: 2em;
+ margin: 0 0 0.444em;
+}
+h2 {
+ font-size: 1.5em;
+ margin: 0 0 0.666em;
+}
+h3 {
+ font-size: 1.125em;
+ margin: 0 0 0.888em;
+}
+h4 {
+ font-size: 1em;
+ margin: 0 0 1.0em;
+}
+h5 {
+ font-size: .875em;
+ margin: 0 0 1.1428em;
+}
+h6 {
+ font-size: .75em;
+ margin: 0 0 1.333em;
+}
+/* bottom margin = 1 / font-size */
+
+
+/*____________ basic margins and paddings ____________*/
+
+p,
+ul,
+ol,
+dl,
+pre,
+table,
+hr,
+blockquote,
+figure,
+details,
+fieldset,
+address {
+ margin: 0 0 1.4em 0; /* bottom margin = line-height */
+ padding: 0;
+}
+
+div,
+video,
+audio {
+ margin: 0;
+ padding: 0;
+}
+
+
+/*____________ lists ____________*/
+
+ul,
+ol {
+ padding: 0 0 0 1.5em;
+}
+[dir=rtl] ul,
+[dir=rtl] ol {
+ padding: 0 1.5em 0 0;
+}
+
+li,
+dd {
+ padding: 0;
+ margin: 0 0 0 1.5em;
+}
+[dir=rtl] li,
+[dir=rtl] dd {
+ margin: 0 1.5em 0 0;
+}
+dt {
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+
+li ul,
+li ol,
+li dl,
+dl ul,
+dl ol,
+dl dl {
+ margin-bottom: 0;
+ padding: 0;
+}
+li li {
+ font-size: 100%;
+}
+
+ul { list-style: square outside; }
+ol { list-style: decimal outside; }
+ol ol { list-style-type: lower-alpha; }
+ol ol ol { list-style-type: upper-roman; }
+ol ol ol ol { list-style-type: upper-alpha; }
+ol ol ol ol ol { list-style-type: lower-roman; }
+
+
+/*____________ tables ____________*/
+
+table {
+ border-collapse: collapse;
+ empty-cells: show;
+ border-spacing: 0;
+ border: 1px solid @ini_border;
+}
+
+caption {
+ caption-side: top;
+ text-align: left;
+}
+[dir=rtl] caption {
+ text-align: right;
+}
+
+th,
+td {
+ padding: .3em .5em;
+ margin: 0;
+ vertical-align: top;
+ border: 1px solid @ini_border;
+}
+th {
+ font-weight: bold;
+ background-color: @ini_background_alt;
+ text-align: left;
+}
+[dir=rtl] th {
+ text-align: right;
+}
+
+
+/*____________ links ____________*/
+
+a {
+ outline: none;
+}
+a:link,
+a:visited {
+ text-decoration: none;
+ color: @ini_link;
+}
+a:link:hover,
+a:visited:hover,
+a:link:focus,
+a:visited:focus,
+a:link:active,
+a:visited:active {
+ text-decoration: underline;
+}
+
+
+/*____________ misc ____________*/
+
+img {
+ border-width: 0;
+ vertical-align: middle;
+ color: #666;
+ background-color: transparent;
+ font-style: italic;
+ height: auto;
+}
+video {
+ height: auto;
+}
+img,
+object,
+embed,
+iframe,
+video,
+audio {
+ max-width: 100%;
+}
+button img {
+ max-width: none;
+}
+
+hr {
+ border-top: solid @ini_border;
+ border-bottom: solid @ini_background;
+ border-width: 1px 0;
+ height: 0;
+ text-align: center;
+ clear: both;
+}
+
+acronym,
+abbr {
+ cursor: help;
+ border-bottom: 1px dotted;
+ font-style: normal;
+}
+em acronym,
+em abbr {
+ font-style: italic;
+}
+
+mark {
+ background-color: @ini_highlight;
+ color: inherit;
+}
+
+pre,
+code,
+samp,
+kbd {
+ font-family: Consolas, "Andale Mono WT", "Andale Mono", "Bitstream Vera Sans Mono", "Nimbus Mono L", Monaco, "Courier New", monospace;
+ /* same font stack should be used for ".dokuwiki table.diff td" in _diff.css */
+ font-size: 1em;
+ direction: ltr;
+ text-align: left;
+ background-color: @ini_background_site;
+ color: @ini_text;
+ box-shadow: inset 0 0 .3em @ini_border;
+ border-radius: 2px;
+}
+pre {
+ overflow: auto;
+ word-wrap: normal;
+ border: 1px solid @ini_border;
+ border-radius: 2px;
+ box-shadow: inset 0 0 .5em @ini_border;
+ padding: .7em 1em;
+}
+
+blockquote {
+ padding: 0 .5em;
+ border: solid @ini_border;
+ border-width: 0 0 0 .25em;
+}
+[dir=rtl] blockquote {
+ border-width: 0 .25em 0 0;
+}
+q:before,
+q:after {
+ content: '';
+}
+
+sub,
+sup {
+ font-size: .8em;
+ line-height: 1;
+}
+sub {
+ vertical-align: sub;
+}
+sup {
+ vertical-align: super;
+}
+
+small {
+ font-size: .8em;
+}
+
+wbr {
+ display: inline-block; /* for IE 11 */
+}
+
+/*____________ forms ____________*/
+
+/* for all of the form styles, style.ini colours are not used on purpose (except for fieldset border) */
+
+form {
+ display: inline;
+ margin: 0;
+ padding: 0;
+}
+fieldset {
+ padding: .7em 1em 0;
+ padding: .7rem 1rem; /* for those browsers understanding :last-child */
+ border: 1px solid @ini_text_alt;
+}
+fieldset > :last-child {
+ margin-bottom: 0;
+}
+legend {
+ margin: 0;
+ padding: 0 .1em;
+}
+label {
+ vertical-align: middle;
+ cursor: pointer;
+}
+
+input,
+textarea,
+button,
+select,
+optgroup,
+option,
+keygen,
+output,
+meter,
+progress {
+ font: inherit;
+ font-weight: normal;
+ color: #333;
+ background-color: #fff;
+ line-height: normal;
+ margin: 0;
+ vertical-align: middle;
+ box-sizing: border-box;
+}
+
+select {
+ max-width: 100%;
+}
+optgroup {
+ font-style: italic;
+ font-weight: bold;
+}
+option {
+ font-style: normal;
+ font-weight: normal;
+}
+
+input,
+textarea,
+select,
+keygen {
+ border: 1px solid #ccc;
+ box-shadow: inset 0 0 1px #eee;
+ border-radius: 2px;
+}
+input:active,
+input:focus,
+textarea:active,
+textarea:focus,
+select:active,
+select:focus,
+keygen:active,
+keygen:focus {
+ border-color: #999;
+}
+input[type=radio],
+input[type=checkbox],
+input[type=image] {
+ padding: 0;
+ border-style: none;
+ box-shadow: none;
+}
+
+/* all types of buttons */
+input[type=submit],
+input[type=button],
+input[type=reset],
+input.button,
+a.button,
+button,
+.qq-upload-button {
+ color: #333;
+ background-color: #eee;
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPgo8bGluZWFyR3JhZGllbnQgaWQ9Imc4MjQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRkZGRkYiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNGNEY0RjQiIG9mZnNldD0iMC4zIi8+PHN0b3Agc3RvcC1jb2xvcj0iI0VFRUVFRSIgb2Zmc2V0PSIwLjk5Ii8+PHN0b3Agc3RvcC1jb2xvcj0iI0NDQ0NDQyIgb2Zmc2V0PSIuOTkiLz4KPC9saW5lYXJHcmFkaWVudD4KPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNnODI0KSIgLz4KPC9zdmc+);
+ background-image: linear-gradient(to bottom, #ffffff 0%, #f4f4f4 30%, #eeeeee 99%, #cccccc 99%);
+ border: 1px solid #ccc;
+ border-radius: 2px;
+ padding: .1em .5em;
+ cursor: pointer;
+}
+
+input[type=submit]:hover,
+input[type=submit]:active,
+input[type=submit]:focus,
+input[type=button]:hover,
+input[type=button]:active,
+input[type=button]:hover,
+input[type=reset]:hover,
+input[type=reset]:active,
+input[type=reset]:hover,
+input.button:hover,
+input.button:active,
+input.button:focus,
+a.button:hover,
+a.button:active,
+a.button:focus,
+button:hover,
+button:active,
+button:focus,
+.qq-upload-button:hover {
+ border-color: #999;
+ background-color: #ddd;
+ background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPgo8bGluZWFyR3JhZGllbnQgaWQ9Imc2NzAiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRkZGRkYiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNGNEY0RjQiIG9mZnNldD0iMC4zIi8+PHN0b3Agc3RvcC1jb2xvcj0iI0RERERERCIgb2Zmc2V0PSIwLjk5Ii8+PHN0b3Agc3RvcC1jb2xvcj0iI0JCQkJCQiIgb2Zmc2V0PSIuOTkiLz4KPC9saW5lYXJHcmFkaWVudD4KPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNnNjcwKSIgLz4KPC9zdmc+);
+ background-image: linear-gradient(to bottom, #ffffff 0%, #f4f4f4 30%, #dddddd 99%, #bbbbbb 99%);
+}
+
+input::-moz-focus-inner,
+button::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+input[disabled],
+button[disabled],
+select[disabled],
+textarea[disabled],
+option[disabled],
+input[readonly],
+button[readonly],
+select[readonly],
+textarea[readonly] {
+ cursor: auto;
+ opacity: .5;
+ background-color: #eee;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/content.less b/platform/www/lib/tpl/dokuwiki/css/content.less
new file mode 100644
index 0000000..d300393
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/content.less
@@ -0,0 +1,393 @@
+/**
+ * This file provides the main design styles for the page content.
+ *
+ * @author Anika Henke <anika@selfthinker.org>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Clarence Lee <clarencedglee@gmail.com>
+ */
+
+/*____________ section indenting ____________
+
+.dokuwiki .page h1 {margin-left: 0;}
+.dokuwiki .page h2 {margin-left: .666em;}
+.dokuwiki .page h3 {margin-left: 1.776em;}
+.dokuwiki .page h4 {margin-left: 3em;}
+.dokuwiki .page h5 {margin-left: 4.5712em;}
+.dokuwiki .page div.level1 {margin-left: 0;}
+.dokuwiki .page div.level2 {margin-left: 1em;}
+.dokuwiki .page div.level3 {margin-left: 2em;}
+.dokuwiki .page div.level4 {margin-left: 3em;}
+.dokuwiki .page div.level5 {margin-left: 4em;}
+
+[dir=rtl] .dokuwiki .page h1 {margin-left: 0; margin-right: 0;}
+[dir=rtl] .dokuwiki .page h2 {margin-left: 0; margin-right: .666em;}
+[dir=rtl] .dokuwiki .page h3 {margin-left: 0; margin-right: 1.776em;}
+[dir=rtl] .dokuwiki .page h4 {margin-left: 0; margin-right: 3em;}
+[dir=rtl] .dokuwiki .page h5 {margin-left: 0; margin-right: 4.5712em;}
+[dir=rtl] .dokuwiki .page div.level1 {margin-left: 0; margin-right: 0;}
+[dir=rtl] .dokuwiki .page div.level2 {margin-left: 0; margin-right: 1em;}
+[dir=rtl] .dokuwiki .page div.level3 {margin-left: 0; margin-right: 2em;}
+[dir=rtl] .dokuwiki .page div.level4 {margin-left: 0; margin-right: 3em;}
+[dir=rtl] .dokuwiki .page div.level5 {margin-left: 0; margin-right: 4em;}
+*/
+/* hx margin-left = (1 / font-size) * .levelx-margin */
+
+/*____________ links to wiki pages (addition to _links) ____________*/
+
+/* existing wikipage */
+.dokuwiki a.wikilink1 {
+ color: @ini_existing;
+ background-color: inherit;
+}
+
+/* not existing wikipage */
+.dokuwiki a.wikilink2 {
+ color: @ini_missing;
+ background-color: inherit;
+}
+
+/*____________ images ____________*/
+
+/* embedded images (styles are already partly set in lib/styles/all.css) */
+.dokuwiki img.media {
+ margin: .2em 0;
+}
+
+.dokuwiki img.medialeft {
+ margin: .2em 1em .2em 0;
+}
+
+.dokuwiki img.mediaright {
+ margin: .2em 0 .2em 1em;
+}
+
+.dokuwiki img.mediacenter {
+ margin: .2em auto;
+}
+
+/*____________ lists ____________*/
+
+.dokuwiki .page,
+.dokuwiki .aside {
+ ul li {
+ color: @ini_text_alt;
+ }
+
+ ol li {
+ color: @ini_text_neu;
+ }
+
+ li .li {
+ color: @ini_text;
+ }
+}
+
+/*____________ tables ____________*/
+
+/* div around each table */
+.dokuwiki div.table {
+ overflow-x: auto;
+ margin-bottom: 1.4em;
+}
+
+.dokuwiki div.table table {
+ margin-bottom: 0;
+}
+
+.dokuwiki table.inline {
+ min-width: 50%;
+}
+
+.dokuwiki table.inline tr:hover td {
+ background-color: @ini_background_alt;
+}
+
+.dokuwiki table.inline tr:hover th {
+ background-color: @ini_border;
+}
+
+/*____________ code ____________*/
+
+/* fix if background-color hides underlining */
+.dokuwiki em.u code {
+ text-decoration: underline;
+}
+
+/* filenames for downloadable file and code blocks */
+.dokuwiki dl.code,
+.dokuwiki dl.file {
+ dt {
+ background-color: @ini_background_site;
+ background: linear-gradient(to bottom, @ini_background_alt 0%, @ini_background_site 100%);
+ color: inherit;
+ border: 1px solid @ini_border;
+ border-bottom-color: @ini_background_site;
+ border-top-left-radius: .3em;
+ border-top-right-radius: .3em;
+ padding: .3em .6em .1em;
+ margin-bottom: -1px;
+ float: left;
+
+ a {
+ background-color: transparent;
+ font-size: 0.875em;
+ font-weight: normal;
+ display: block;
+ min-height: 16px;
+ }
+ }
+
+ dd {
+ margin: 0;
+ clear: left;
+ }
+
+ pre {
+ box-shadow: inset -4px -4px .5em -.3em @ini_border;
+ }
+}
+
+[dir=rtl] .dokuwiki dl.code,
+[dir=rtl] .dokuwiki dl.file {
+ dt {
+ float: right;
+ }
+
+ dd {
+ clear: right;
+ }
+}
+
+/* for code in <file> */
+.dokuwiki dl.file {
+ pre,
+ dt {
+ border-style: dashed;
+ }
+ dt {
+ border-bottom-style: solid;
+ }
+}
+
+/*____________ JS popup ____________*/
+
+.JSpopup {
+ background-color: @ini_background;
+ color: @ini_text;
+ border: 1px solid @ini_border;
+ box-shadow: .1em .1em .1em @ini_border;
+ border-radius: 2px;
+ padding: .3em .5em;
+ font-size: .9em;
+}
+
+.dokuwiki form.search div.ajax_qsearch {
+ top: -.35em;
+ font-size: 1em;
+ text-overflow: ellipsis;
+}
+
+.JSpopup ul,
+.JSpopup ol {
+ padding-left: 0;
+}
+
+[dir=rtl] .JSpopup ul,
+[dir=rtl] .JSpopup ol {
+ padding-right: 0;
+}
+
+/* changes to underscored CSS files
+********************************************************************/
+
+#acl__tree li {
+ margin: 0;
+}
+
+#dokuwiki__content span.curid a {
+ font-weight: normal;
+}
+
+#dokuwiki__content strong span.curid a {
+ font-weight: bold;
+}
+
+/*____________ changes to _edit ____________*/
+
+.dokuwiki div.toolbar {
+ button.toolbutton {
+ border-radius: 0;
+ border-left-width: 0;
+ padding: .1em .35em;
+ }
+
+ button.toolbutton:first-child {
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ border-left-width: 1px;
+ }
+
+ button.toolbutton:last-child {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ }
+}
+
+[dir=rtl] .dokuwiki div.toolbar {
+ button.toolbutton:last-child {
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ border-left-width: 1px;
+ }
+
+ button.toolbutton:first-child {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-left-width: 0;
+ border-right-width: 1px;
+ }
+}
+
+.dokuwiki div.section_highlight {
+ margin: 0 -2em;
+ padding: 0 1em;
+ border-width: 0 1em;
+}
+
+.dokuwiki textarea.edit {
+ font-family: Consolas, "Andale Mono WT", "Andale Mono", "Bitstream Vera Sans Mono", "Nimbus Mono L", Monaco, "Courier New", monospace;
+}
+
+.dokuwiki div.preview {
+ margin: 0 -2em;
+ padding: 0 2em;
+}
+
+.dokuwiki.hasSidebar div.preview {
+ border-right: @ini_sidebar_width solid @ini_background_alt;
+}
+
+[dir=rtl] .dokuwiki.hasSidebar div.preview {
+ border-right-width: 0;
+ border-left: @ini_sidebar_width solid @ini_background_alt;
+}
+
+.dokuwiki div.preview div.pad {
+ padding: 1.556em 0 2em;
+}
+
+/*____________ changes to _toc ____________*/
+
+#dw__toc {
+ margin: -1.556em -2em .5em 1.4em;
+ width: @ini_sidebar_width;
+ border-left: 1px solid @ini_border;
+ background: @ini_background;
+ color: inherit;
+}
+
+[dir=rtl] #dw__toc {
+ margin: -1.556em 1.4em .5em -2em;
+ border-left-width: 0;
+ border-right: 1px solid @ini_border;
+}
+
+.dokuwiki h3.toggle {
+ padding: .5em 1em;
+ margin-bottom: 0;
+ font-size: .875em;
+ letter-spacing: .1em;
+}
+
+#dokuwiki__aside h3.toggle {
+ display: none;
+}
+
+.dokuwiki .toggle strong {
+ background: transparent url(images/toc-arrows.png) 0 0;
+ width: 8px;
+ height: 5px;
+ margin: .4em 0 0;
+}
+
+.dokuwiki .toggle.closed strong {
+ background-position: 0 -5px;
+}
+
+.dokuwiki .toggle strong span {
+ display: none;
+}
+
+#dw__toc {
+ > div {
+ font-size: 0.875em;
+ padding: .5em 1em 1em;
+ }
+
+ ul {
+ padding: 0 0 0 1.2em;
+
+ li {
+ list-style-image: url(images/toc-bullet.png);
+ }
+ }
+
+ ul li.clear {
+ list-style: none;
+ }
+
+ ul li div.li {
+ padding: .2em 0;
+ }
+}
+
+[dir=rtl] #dw__toc ul {
+ padding: 0 1.5em 0 0;
+}
+
+/*____________ changes to _imgdetail ____________*/
+
+#dokuwiki__detail {
+ padding: 0;
+
+ img {
+ float: none;
+ margin-bottom: 1.4em;
+ }
+
+ div.img_detail {
+ float: none;
+ }
+
+ div.img_detail dl {
+ overflow: hidden;
+ }
+
+ div.img_detail dl dt {
+ float: left;
+ width: 9em;
+ text-align: right;
+ clear: left;
+ }
+
+ div.img_detail dl dd {
+ margin-left: 9.5em;
+ }
+}
+
+[dir=rtl] #dokuwiki__detail div.img_detail {
+ dl dt {
+ float: right;
+ text-align: left;
+ clear: right;
+ }
+
+ dl dd {
+ margin-left: 0;
+ margin-right: 9.5em;
+ }
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/design.less b/platform/www/lib/tpl/dokuwiki/css/design.less
new file mode 100644
index 0000000..86315d2
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/design.less
@@ -0,0 +1,354 @@
+/**
+ * This file provides the main design styles for the
+ * bits that surround the content.
+ *
+ * @author Anika Henke <anika@selfthinker.org>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Clarence Lee <clarencedglee@gmail.com>
+ */
+
+/* header
+********************************************************************/
+
+#dokuwiki__header {
+ padding: 2em 0 1.5em;
+
+ .headings,
+ .tools {
+ margin-bottom: 1.5em;
+ width: 49%;
+ }
+ .tools {
+ margin-top: .2em;
+ }
+
+ h1 {
+ margin: 0;
+ font-size: 1.5em;
+ font-weight: normal;
+
+ img {
+ float: left;
+ margin-right: .5em;
+ }
+
+ span {
+ display: block;
+ padding-top: 10px;
+ }
+
+ a {
+ text-decoration: none;
+ color: @ini_text;
+ background-color: inherit;
+ }
+ }
+
+ p.claim {
+ margin-bottom: 0;
+ font-size: 0.875em;
+ }
+
+ /* make all links in header (including breadcrumb and interwiki) same colour as the rest */
+ a {
+ color: @ini_link;
+ background-color: inherit;
+ }
+}
+
+[dir=rtl] #dokuwiki__header h1 img {
+ float: right;
+ margin-left: .5em;
+ margin-right: 0;
+}
+
+/* tools
+********************************************************************/
+
+/* highlight selected tool */
+.mode_admin .action.admin a,
+.mode_login .action.login a,
+.mode_register .action.register a,
+.mode_profile .action.profile a,
+.mode_recent .action.recent a,
+.mode_index .action.index a,
+.mode_media .action.media a,
+.mode_revisions .action.revs a,
+.mode_backlink .action.backlink a,
+.mode_subscribe .action.subscribe a {
+ font-weight: bold;
+}
+
+#dokuwiki__header .tools {
+ ul {
+ padding-left: 0;
+ margin-bottom: 0;
+ }
+
+ li {
+ font-size: 0.875em;
+ margin-left: 1em;
+ list-style: none;
+ display: inline;
+ }
+
+ form.search div.ajax_qsearch li {
+ font-size: 1em;
+ margin-left: 0;
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+}
+
+[dir=rtl] #dokuwiki__header .tools li {
+ margin-right: 1em;
+ margin-left: 0;
+}
+
+#dokuwiki__header .mobileTools {
+ display: none; /* hide mobile tools dropdown to only show in mobile view */
+}
+
+/*____________ site tools ____________*/
+
+#dokuwiki__sitetools {
+ text-align: right;
+
+ form.search {
+ font-size: 0.875em;
+ }
+}
+
+[dir=rtl] #dokuwiki__sitetools {
+ text-align: left;
+}
+
+form.search {
+ display: block;
+ position: relative;
+ margin-bottom: 0.5em;
+
+ input {
+ width: 18em;
+ padding: .35em 22px .35em .1em;
+ }
+
+ button {
+ background: transparent url(images/search.png) no-repeat 0 0;
+ border-width: 0;
+ width: 19px;
+ height: 14px;
+ text-indent: -99999px;
+ margin-left: -20px;
+ box-shadow: none;
+ padding: 0;
+ }
+}
+
+[dir=rtl] form.search {
+ input {
+ padding: .35em .1em .35em 22px;
+ }
+
+ button {
+ background-position: 5px 0;
+ margin-left: 0;
+ margin-right: -20px;
+ position: relative;
+ }
+}
+
+/*____________ breadcrumbs ____________*/
+
+.dokuwiki div.breadcrumbs {
+ border-top: 1px solid @ini_border;
+ border-bottom: 1px solid @ini_background;
+ margin-bottom: .5em;
+ font-size: 0.875em;
+ clear: both;
+
+ div {
+ padding: .1em .35em;
+ }
+
+ div:only-child {
+ border-top: 1px solid @ini_background;
+ border-bottom: 1px solid @ini_border;
+ }
+
+ div:first-child {
+ border-top: 1px solid @ini_background;
+ }
+
+ div:last-child {
+ border-bottom: 1px solid @ini_border;
+ }
+
+ .bcsep {
+ font-size: 0.75em;
+ }
+}
+
+/* sidebar
+********************************************************************/
+
+.dokuwiki .aside {
+ font-size: 0.875em;
+ overflow: hidden;
+ word-wrap: break-word;
+
+ /* make sidebar more condensed */
+
+ h1 {
+ font-size: 1.714em;
+ margin-bottom: .292em;
+ }
+
+ h2 {
+ margin-bottom: .333em;
+ }
+
+ h3 {
+ margin-bottom: .444em;
+ }
+
+ h4 {
+ margin-bottom: .5em;
+ }
+
+ h5 {
+ margin-bottom: .5714em;
+ }
+
+ p,
+ ul,
+ ol,
+ dl,
+ pre,
+ table,
+ fieldset,
+ hr,
+ blockquote,
+ address {
+ margin-bottom: .7em;
+ }
+
+ ul,
+ ol {
+ padding-left: .5em;
+ }
+
+ li ul,
+ li ol {
+ margin-bottom: 0;
+ padding: 0;
+ }
+
+ a:link,
+ a:visited {
+ color: @ini_link;
+ background-color: inherit;
+ }
+}
+
+[dir=rtl] .dokuwiki .aside ul,
+[dir=rtl] .dokuwiki .aside ol {
+ padding-right: .5em;
+}
+
+/* content
+********************************************************************/
+
+.dokuwiki .pageId {
+ float: right;
+ margin-right: -1em;
+ margin-bottom: -1px;
+ margin-top: -1.5em;
+ overflow: hidden;
+ padding: 0.5em 1em 0;
+
+ span {
+ font-size: 0.875em;
+ border: solid @ini_background_alt;
+ border-width: 1px 1px 0;
+ background-color: @ini_background;
+ color: @ini_text_alt;
+ padding: .1em .35em;
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+ box-shadow: 0 0 .5em @ini_text_alt;
+ display: block;
+ }
+}
+
+.dokuwiki div.page {
+ clear: both;
+ background: @ini_background;
+ color: inherit;
+ border: 1px solid @ini_background_alt;
+ box-shadow: 0 0 .5em @ini_text_alt;
+ border-radius: 2px;
+ padding: 1.556em 2em 2em;
+ margin-bottom: .5em;
+ overflow: hidden;
+ word-wrap: break-word;
+}
+
+.dokuwiki .docInfo {
+ font-size: 0.875em;
+ text-align: right;
+}
+
+/* license note under edit window */
+.dokuwiki div.license {
+ font-size: 93.75%;
+}
+
+[dir=rtl] .dokuwiki .docInfo {
+ text-align: left;
+}
+
+[dir=rtl] .dokuwiki .pageId {
+ float: left;
+ margin-left: -1em;
+ margin-right: 0;
+}
+
+/* footer
+********************************************************************/
+
+.dokuwiki .wrapper {
+ margin-bottom: 1.4em;
+}
+
+#dokuwiki__footer {
+ margin-bottom: 1em;
+ text-align: center;
+
+ > .pad {
+ font-size: 0.875em;
+ }
+
+ div.license {
+ margin-bottom: 0.5em;
+ font-size: 100%;
+ }
+
+ div.buttons a {
+ img {
+ opacity: 0.5;
+ }
+
+ &:hover img,
+ &:active img,
+ &:focus img {
+ opacity: 1;
+ }
+ }
+
+}
+
+[dir=rtl] #dokuwiki__footer .license img {
+ margin: 0 0 0 .5em;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/mobile.less b/platform/www/lib/tpl/dokuwiki/css/mobile.less
new file mode 100644
index 0000000..a52c723
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/mobile.less
@@ -0,0 +1,332 @@
+/**
+ * This file provides styles for mobile devices
+ * and smaller screens (up to 480px and 768px width).
+ *
+ * @author Anika Henke <anika@selfthinker.org>
+ */
+
+/* for detecting media queries in JavaScript (see script.js): */
+#screen__mode {
+ position: relative;
+ z-index: 0;
+}
+
+/* for screen widths in the tablet range
+********************************************************************/
+@media only screen and (max-width: @ini_tablet_width) {
+
+#screen__mode {
+ z-index: 1; /* for detecting media queries in JavaScript (see script.js) */
+}
+
+/* structure */
+#dokuwiki__aside {
+ width: 100%;
+ float: none;
+ margin-bottom: 1.5em;
+}
+
+#dokuwiki__aside > .pad,
+[dir=rtl] #dokuwiki__aside > .pad {
+ margin: 0 0 .5em;
+ /* style like .page */
+ background: @ini_background;
+ color: inherit;
+ border: 1px solid #eee;
+ box-shadow: 0 0 .5em @ini_text_alt;
+ border-radius: 2px;
+ padding: 1em;
+ margin-bottom: .5em;
+}
+
+#dokuwiki__aside h3.toggle {
+ font-size: 1em;
+
+ &.closed {
+ margin-bottom: 0;
+ padding-bottom: 0;
+ }
+ &.open {
+ border-bottom: 1px solid @ini_border;
+ }
+}
+
+.showSidebar #dokuwiki__content {
+ float: none;
+ margin-left: 0;
+ width: 100%;
+
+ > .pad {
+ margin-left: 0;
+ }
+}
+
+[dir=rtl] .showSidebar #dokuwiki__content,
+[dir=rtl] .showSidebar #dokuwiki__content > .pad {
+ margin-right: 0;
+}
+
+/* preview */
+.dokuwiki.hasSidebar div.preview {
+ border-right: none;
+}
+
+[dir=rtl] .dokuwiki.hasSidebar div.preview {
+ border-left: none;
+}
+
+/* toc */
+#dw__toc {
+ float: none;
+ margin: 0 0 1em 0;
+ width: auto;
+ border-left-width: 0;
+ border-bottom: 1px solid @ini_border;
+}
+[dir=rtl] #dw__toc {
+ float: none;
+ margin: 0 0 1em 0;
+ border-right-width: 0;
+}
+
+.dokuwiki h3.toggle {
+ padding: 0 .5em .5em 0;
+}
+#dw__toc > div,
+#dokuwiki__aside div.content {
+ padding: .2em 0 .5em;
+}
+
+/* page */
+.dokuwiki div.page {
+ padding: 1em;
+}
+/* enable horizontal scrolling in media manager */
+.mode_media div.page {
+ overflow: auto;
+}
+
+/* push pagetools closer to content */
+#dokuwiki__pagetools {
+ top: 0;
+}
+.showSidebar #dokuwiki__pagetools {
+ top: 3.5em;
+}
+
+
+/* _edit */
+.dokuwiki div.section_highlight {
+ margin: 0 -1em;
+ padding: 0 .5em;
+ border-width: 0 .5em;
+}
+.dokuwiki div.preview {
+ margin: 0 -1em;
+ padding: 1em;
+}
+
+/* _recent */
+.dokuwiki form.changes ul {
+ padding-left: 0;
+}
+[dir=rtl] .dokuwiki form.changes ul {
+ padding-right: 0;
+}
+
+
+} /* /@media */
+
+
+/* for screen widths in the smartphone range
+********************************************************************/
+@media only screen and (max-width: @ini_phone_width) {
+
+#screen__mode {
+ z-index: 2; /* for detecting media queries in JavaScript (see script.js) */
+}
+
+body {
+ font-size: 100%;
+}
+
+/*____________ structure ____________*/
+
+#dokuwiki__site {
+ max-width: 100%;
+
+ > .site {
+ padding: 0 .5em;
+ }
+}
+
+#dokuwiki__aside {
+ margin-bottom: 0;
+}
+
+#dokuwiki__header {
+ padding: .5em 0;
+}
+
+
+/*____________ header ____________*/
+
+#dokuwiki__header ul.a11y.skip {
+ position: static !important;
+ left: 0 !important;
+ width: auto !important;
+ height: auto !important;
+ float: right;
+ font-size: 0.875em;
+ list-style: none;
+ padding-left: 0;
+ margin: 0;
+
+ li {
+ margin-left: .35em;
+ display: inline;
+ }
+}
+[dir=rtl] #dokuwiki__header ul.a11y.skip {
+ left: auto !important;
+ right: 0 !important;
+ float: left;
+ padding-right: 0;
+
+ li {
+ margin: 0 .35em 0 0;
+ }
+}
+
+#dokuwiki__header .headings,
+#dokuwiki__header .tools {
+ float: none;
+ text-align: left;
+ width: auto;
+ margin-bottom: .5em;
+}
+[dir=rtl] #dokuwiki__header .headings,
+[dir=rtl] #dokuwiki__header .tools {
+ float: none;
+ text-align: right;
+ width: auto;
+}
+#dokuwiki__sitetools {
+ text-align: left;
+}
+[dir=rtl] #dokuwiki__sitetools {
+ text-align: right;
+}
+#dokuwiki__usertools,
+#dokuwiki__sitetools ul,
+#dokuwiki__sitetools h3,
+#dokuwiki__pagetools,
+.dokuwiki div.breadcrumbs, /* @todo: maybe move breadcrumbs to the bottom? */
+.dokuwiki .pageId {
+ display: none;
+}
+
+/* search form */
+#dokuwiki__sitetools form.search {
+ float: left;
+ margin: 0 .2em .2em 0;
+ width: 49%;
+}
+[dir=rtl] #dokuwiki__sitetools form.search {
+ float: right;
+ margin: 0 0 .2em .2em;
+}
+
+#dokuwiki__sitetools form.search input {
+ width: 100% !important;
+}
+.dokuwiki form.search div.ajax_qsearch {
+ display: none !important;
+}
+
+/* action dropdown is alternative for all hidden tools */
+#dokuwiki__header .mobileTools {
+ display: block;
+ font-size: 0.875em;
+ margin: 0 0 .2em 0;
+ float: right;
+ width: 49%;
+}
+[dir=rtl] #dokuwiki__header .mobileTools {
+ float: left;
+}
+#dokuwiki__header .mobileTools select {
+ padding: .3em .1em;
+ width: 100% !important;
+}
+
+/* force same height on search input and tools select */
+#dokuwiki__sitetools form.search input,
+#dokuwiki__header .mobileTools select {
+ height: 2.1em;
+ line-height: 2.1em;
+ overflow: visible;
+}
+
+
+/*____________ content ____________*/
+
+#dokuwiki__aside > .pad,
+.dokuwiki div.page {
+ padding: .5em;
+}
+
+/* form elements */
+#config__manager fieldset td.value,
+#config__manager td .input,
+.dokuwiki fieldset,
+.dokuwiki input.edit,
+.dokuwiki textarea {
+ width: auto !important;
+ max-width: 100% !important;
+}
+.dokuwiki select {
+ max-width: 100% !important;
+}
+#config__manager fieldset {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.dokuwiki label.block {
+ text-align: left;
+
+ span {
+ display: block;
+ }
+}
+[dir=rtl] .dokuwiki label.block {
+ text-align: right;
+}
+
+/* _edit */
+.dokuwiki div.section_highlight {
+ margin: 0;
+ padding: 0;
+ border-width: 0;
+}
+.dokuwiki div.preview {
+ margin: 0 -.5em;
+ padding: .5em;
+}
+
+
+} /* /@media */
+
+
+/* for screen heights smaller than the pagetools permit
+********************************************************************/
+@media only screen and (max-height: 400px) {
+// 400px is only roughly the required value, this may be wrong under non-standard circumstances
+
+#dokuwiki__pagetools div.tools {
+ position: static;
+}
+
+
+} /* /@media */
diff --git a/platform/www/lib/tpl/dokuwiki/css/pagetools.less b/platform/www/lib/tpl/dokuwiki/css/pagetools.less
new file mode 100644
index 0000000..5473594
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/pagetools.less
@@ -0,0 +1,124 @@
+/**
+ * This file provides the styles for the page tools
+ * (fly out navigation beside the page to edit, etc).
+ *
+ * @author Anika Henke <anika@selfthinker.org>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+#dokuwiki__site > .site {
+ /* give space to the right so the tools won't disappear on smaller screens */
+ /* it's 40px because the 30px wide icons will have 5px more spacing to the left and right */
+ padding-right: 40px;
+ /* give the same space to the left to balance it out */
+ padding-left: 40px;
+}
+
+.dokuwiki div.page {
+ height: 190px;
+ min-height: 190px; /* 30 (= height of icons) x 6 (= maximum number of possible tools) + 2x5 */
+ height: auto;
+}
+
+#dokuwiki__pagetools {
+ @ico-width: 28px;
+ @ico-margin: 8px;
+ @item-width: (@ico-width + @ico-margin + @ico-margin);
+ @item-height: (@ico-width + @ico-margin);
+
+ position: absolute;
+ right: (-1 * @item-width);
+ /* on same vertical level as first headline, because .page has 2em padding */
+ top: 2em;
+ width: @item-width;
+
+ div.tools {
+ position: fixed;
+ width: @item-width;
+
+ ul {
+ position: absolute;
+ right: 0;
+ text-align: right;
+ margin: 0;
+ padding: 0;
+ /* add transparent border to prevent jumping when proper border is added on hover */
+ border: 1px solid transparent;
+ z-index: 10;
+
+ li {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ font-size: 0.875em;
+
+ a {
+
+ display: block;
+ /* add transparent border to prevent jumping when proper border is added on focus */
+ border: 1px solid transparent;
+ white-space: nowrap;
+ line-height: @item-height;
+ vertical-align: middle;
+ height: @item-height;
+
+ span {
+ display: none; // hide label until hover
+ margin: 0 @ico-margin;
+ }
+
+ svg {
+ width: @ico-width;
+ height: @ico-width;
+ margin: 0 @ico-margin;
+ display: inline-block;
+ vertical-align: middle;
+ fill: @ini_border;
+ }
+ }
+
+ // on interaction show the full item
+ a:active,
+ a:focus,
+ a:hover {
+ background-color: @ini_background_alt;
+
+ span {
+ display: inline-block;
+ }
+
+ svg {
+ fill: @ini_link;
+ }
+ }
+ }
+ }
+ }
+
+ [dir=rtl] & {
+ right: auto;
+ left: (-1 * @item-width);
+
+ div.tools {
+ ul {
+ right: auto;
+ left: 0;
+ text-align: left;
+ }
+ }
+ }
+}
+
+// on hover or focus show all items
+#dokuwiki__pagetools:hover, #dokuwiki__pagetools:focus-within {
+ div.tools ul {
+ background-color: @ini_background;
+ border-color: @ini_border;
+ border-radius: 2px;
+ box-shadow: 2px 2px 2px @ini_text_alt;
+
+ li a span {
+ display: inline-block;
+ }
+ }
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/print.css b/platform/www/lib/tpl/dokuwiki/css/print.css
new file mode 100644
index 0000000..7197ac1
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/print.css
@@ -0,0 +1,177 @@
+/**
+ * This file provides the styles for printing.
+ *
+ * @todo: improve and finish
+ */
+
+body {
+ font: normal 87.5%/1.3 Garamond, Baskerville, "Hoefler Text", "Nimbus Roman No9 L", serif;
+ background-color: #fff;
+ color: #000;
+}
+
+/* hide certain sections */
+.a11y,
+audio,
+video,
+#dokuwiki__header .tools,
+#dokuwiki__aside,
+.dokuwiki .breadcrumbs,
+.dokuwiki .pageId,
+#dw__toc,
+h3.toggle,
+#dokuwiki__pagetools,
+#dokuwiki__footer {
+ display: none;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+caption,
+legend {
+ clear: both;
+}
+ul {
+ list-style: disc outside;
+}
+ol {
+ list-style: decimal outside;
+}
+ol ol {
+ list-style-type: lower-alpha;
+}
+ol ol ol {
+ list-style-type: upper-roman;
+}
+ol ol ol ol {
+ list-style-type: upper-alpha;
+}
+ol ol ol ol ol {
+ list-style-type: lower-roman;
+}
+
+a:link,
+a:visited {
+ text-decoration: none;
+ border-bottom: 1pt dotted;
+ color: #333;
+ background-color: inherit;
+}
+
+/* display href after link */
+a.urlextern:after,
+a.interwiki:after,
+a.mail:after {
+ content: " [" attr(href) "]";
+ font-size: 90%;
+}
+
+/* code blocks */
+pre {
+ font-family: monospace;
+}
+dl.code dt,
+dl.file dt {
+ font-weight: bold;
+}
+
+mark {
+ font-weight: bold;
+}
+
+/* images */
+img {
+ border-width: 0;
+ vertical-align: middle;
+}
+img.media {
+ margin: .2em 0;
+}
+img.medialeft {
+ margin: .2em 1em .2em 0;
+}
+img.mediaright {
+ margin: .2em 0 .2em 1em;
+}
+img.mediacenter {
+ margin: .2em auto;
+}
+
+blockquote {
+ padding: 0 10pt;
+ margin: 0;
+ border: solid #ccc;
+ border-width: 0 0 0 2pt;
+}
+[dir=rtl] blockquote {
+ border-width: 0 2pt 0 0;
+}
+
+/* tables */
+.dokuwiki div.table {
+ margin-bottom: 1.4em;
+}
+table {
+ border-collapse: collapse;
+ empty-cells: show;
+ border-spacing: 0;
+ border: 1pt solid #ccc;
+}
+th,
+td {
+ padding: 3pt 5pt;
+ margin: 0;
+ vertical-align: top;
+ border: 1pt solid #666;
+}
+th {
+ font-weight: bold;
+ text-align: left;
+}
+[dir=rtl] th {
+ text-align: right;
+}
+
+
+/*____________ a bit of layout ____________*/
+
+#dokuwiki__header {
+ border-bottom: 2pt solid #ccc;
+}
+#dokuwiki__header h1 {
+ font-size: 1.5em;
+}
+#dokuwiki__header h1 a {
+ text-decoration: none;
+ border-width: 0;
+}
+#dokuwiki__header h1 img {
+ float: left;
+ margin-right: .5em;
+}
+[dir=rtl] #dokuwiki__header h1 img {
+ float: right;
+ margin-right: 0;
+ margin-left: .5em;
+}
+
+.dokuwiki div.footnotes {
+ clear: both;
+ border-top: 1pt dotted #999;
+ margin-top: 10pt;
+}
+
+.dokuwiki div.docInfo {
+ font-size: 90%;
+ text-align: right;
+ clear: both;
+ padding-top: 2pt;
+ border-top: 1pt solid #999;
+ margin-top: 10pt;
+}
+[dir=rtl] .dokuwiki div.docInfo {
+ text-align: left;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/structure.less b/platform/www/lib/tpl/dokuwiki/css/structure.less
new file mode 100644
index 0000000..3ea2f83
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/structure.less
@@ -0,0 +1,89 @@
+/**
+ * This file provides styles for the general layout structure.
+ *
+ * @author Anika Henke <anika@selfthinker.org>
+ */
+body {
+ margin: 0 auto;
+}
+
+#dokuwiki__site {
+ margin: 0 auto;
+ max-width: @ini_site_width;
+}
+
+#dokuwiki__site > .site {
+ padding: 0 .5em;
+}
+
+#dokuwiki__header {
+ width: 100%;
+
+ .headings {
+ float: left;
+ }
+
+ .tools {
+ float: right;
+ text-align: right;
+ }
+}
+
+[dir=rtl] #dokuwiki__header {
+ .headings {
+ float: right;
+ text-align: right;
+ }
+
+ .tools {
+ float: left;
+ text-align: left;
+ }
+}
+
+#dokuwiki__site .wrapper {
+ position: relative;
+}
+
+#dokuwiki__aside {
+ width: @ini_sidebar_width;
+ float: left;
+ position: relative;
+ display: block;
+
+ > .pad {
+ margin: 0 1.5em 0 0;
+ }
+}
+
+[dir=rtl] #dokuwiki__aside {
+ float: right;
+ > .pad {
+ margin: 0 0 0 1.5em;
+ }
+}
+
+.showSidebar #dokuwiki__content {
+ float: right;
+ margin-left: (-1 * @ini_sidebar_width);
+ width: 100%;
+
+ > .pad {
+ margin-left: @ini_sidebar_width;
+ }
+}
+
+[dir=rtl] .showSidebar #dokuwiki__content {
+ float: left;
+ margin-left: 0;
+ margin-right: (-1 * @ini_sidebar_width);
+
+ > .pad {
+ margin-left: 0;
+ margin-right: @ini_sidebar_width;
+ }
+}
+
+#dokuwiki__footer {
+ clear: both;
+}
diff --git a/platform/www/lib/tpl/dokuwiki/css/usertools.less b/platform/www/lib/tpl/dokuwiki/css/usertools.less
new file mode 100644
index 0000000..efdf16c
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/css/usertools.less
@@ -0,0 +1,50 @@
+#dokuwiki__usertools {
+ position: absolute;
+ top: .5em;
+ right: 40px; // pagetool width
+ text-align: right;
+ width: 100%;
+
+ ul {
+ margin: 0 auto;
+ padding: 0;
+ max-width: @ini_site_width;
+ }
+
+ li.action a {
+ display: inline-flex;
+ flex-direction: row-reverse;
+ flex-wrap: nowrap;
+
+ svg {
+ height: 1.4em;
+ width: 1.4em;
+ vertical-align: middle;
+ fill: @ini_border;
+ margin-right: 0.2em;
+ }
+ }
+
+ li.action a:hover,
+ li.action a:active {
+ svg {
+ fill: @ini_link;
+ }
+ }
+
+}
+
+[dir=rtl] #dokuwiki__usertools {
+ text-align: left;
+ left: 40px; // pagetool width
+ right: auto;
+
+
+ li.action a {
+
+ svg {
+ margin-right: 0;
+ margin-left: 0.2em;
+ }
+ }
+}
diff --git a/platform/www/lib/tpl/dokuwiki/detail.php b/platform/www/lib/tpl/dokuwiki/detail.php
new file mode 100644
index 0000000..8e65410
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/detail.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * DokuWiki Image Detail Page
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Anika Henke <anika@selfthinker.org>
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ */
+
+// must be run from within DokuWiki
+if (!defined('DOKU_INC')) die();
+header('X-UA-Compatible: IE=edge,chrome=1');
+
+?><!DOCTYPE html>
+<html lang="<?php echo $conf['lang']?>" dir="<?php echo $lang['direction'] ?>" class="no-js">
+<head>
+ <meta charset="utf-8" />
+ <title>
+ <?php echo hsc(tpl_img_getTag('IPTC.Headline',$IMG))?>
+ [<?php echo strip_tags($conf['title'])?>]
+ </title>
+ <script>(function(H){H.className=H.className.replace(/\bno-js\b/,'js')})(document.documentElement)</script>
+ <?php tpl_metaheaders()?>
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <?php echo tpl_favicon(array('favicon', 'mobile')) ?>
+ <?php tpl_includeFile('meta.html') ?>
+</head>
+
+<body>
+ <div id="dokuwiki__site"><div id="dokuwiki__top" class="site <?php echo tpl_classes(); ?>">
+
+ <?php include('tpl_header.php') ?>
+
+ <div class="wrapper group" id="dokuwiki__detail">
+
+ <!-- ********** CONTENT ********** -->
+ <div id="dokuwiki__content"><div class="pad group">
+ <?php html_msgarea() ?>
+
+ <?php if(!$ERROR): ?>
+ <div class="pageId"><span><?php echo hsc(tpl_img_getTag('IPTC.Headline',$IMG)); ?></span></div>
+ <?php endif; ?>
+
+ <div class="page group">
+ <?php tpl_flush() ?>
+ <?php tpl_includeFile('pageheader.html') ?>
+ <!-- detail start -->
+ <?php
+ if($ERROR):
+ echo '<h1>'.$ERROR.'</h1>';
+ else: ?>
+ <?php if($REV) echo p_locale_xhtml('showrev');?>
+ <h1><?php echo nl2br(hsc(tpl_img_getTag('simple.title'))); ?></h1>
+
+ <?php tpl_img(900,700); /* parameters: maximum width, maximum height (and more) */ ?>
+
+ <div class="img_detail">
+ <?php tpl_img_meta(); ?>
+ <dl>
+ <?php
+ echo '<dt>'.$lang['reference'].':</dt>';
+ $media_usage = ft_mediause($IMG,true);
+ if(count($media_usage) > 0){
+ foreach($media_usage as $path){
+ echo '<dd>'.html_wikilink($path).'</dd>';
+ }
+ }else{
+ echo '<dd>'.$lang['nothingfound'].'</dd>';
+ }
+ ?>
+ </dl>
+ <p><?php echo $lang['media_acl_warning']; ?></p>
+ </div>
+ <?php //Comment in for Debug// dbg(tpl_img_getTag('Simple.Raw'));?>
+ <?php endif; ?>
+ </div>
+ <!-- detail stop -->
+ <?php tpl_includeFile('pagefooter.html') ?>
+ <?php tpl_flush() ?>
+
+ <?php /* doesn't make sense like this; @todo: maybe add tpl_imginfo()?
+ <div class="docInfo"><?php tpl_pageinfo(); ?></div>
+ */ ?>
+
+ </div></div><!-- /content -->
+
+ <hr class="a11y" />
+
+ <!-- PAGE ACTIONS -->
+ <?php if (!$ERROR): ?>
+ <div id="dokuwiki__pagetools">
+ <h3 class="a11y"><?php echo $lang['page_tools']; ?></h3>
+ <div class="tools">
+ <ul>
+ <?php echo (new \dokuwiki\Menu\DetailMenu())->getListItems(); ?>
+ </ul>
+ </div>
+ </div>
+ <?php endif; ?>
+ </div><!-- /wrapper -->
+
+ <?php include('tpl_footer.php') ?>
+ </div></div><!-- /site -->
+</body>
+</html>
diff --git a/platform/www/lib/tpl/dokuwiki/images/apple-touch-icon.png b/platform/www/lib/tpl/dokuwiki/images/apple-touch-icon.png
new file mode 100644
index 0000000..87c99a9
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/apple-touch-icon.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/button-css.png b/platform/www/lib/tpl/dokuwiki/images/button-css.png
new file mode 100644
index 0000000..5c0f5a9
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/button-css.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/button-donate.gif b/platform/www/lib/tpl/dokuwiki/images/button-donate.gif
new file mode 100644
index 0000000..bba284e
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/button-donate.gif
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/button-dw.png b/platform/www/lib/tpl/dokuwiki/images/button-dw.png
new file mode 100644
index 0000000..8d6aea8
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/button-dw.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/button-html5.png b/platform/www/lib/tpl/dokuwiki/images/button-html5.png
new file mode 100644
index 0000000..f7b0688
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/button-html5.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/button-php.gif b/platform/www/lib/tpl/dokuwiki/images/button-php.gif
new file mode 100644
index 0000000..19aefb0
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/button-php.gif
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/button-rss.png b/platform/www/lib/tpl/dokuwiki/images/button-rss.png
new file mode 100644
index 0000000..aa6b7fc
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/button-rss.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/favicon.ico b/platform/www/lib/tpl/dokuwiki/images/favicon.ico
new file mode 100644
index 0000000..8b9616a
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/favicon.ico
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/license.txt b/platform/www/lib/tpl/dokuwiki/images/license.txt
new file mode 100644
index 0000000..7d12604
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/license.txt
@@ -0,0 +1,5 @@
+Icons for: sitetools.png
+Icon set: Dusseldorf
+Designer: pc.de
+License: Creative Commons Attribution License [http://creativecommons.org/licenses/by/3.0/]
+URL: http://pc.de/icons/#Dusseldorf
diff --git a/platform/www/lib/tpl/dokuwiki/images/logo.png b/platform/www/lib/tpl/dokuwiki/images/logo.png
new file mode 100644
index 0000000..a1f4995
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/logo.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/page-background.svg b/platform/www/lib/tpl/dokuwiki/images/page-background.svg
new file mode 100644
index 0000000..086341d
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/page-background.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
+ <linearGradient id="g" x1="0%" y1="0%" x2="0%" y2="100%">
+ <stop offset="0" stop-color="#dddddd" />
+ <stop offset="0.1" stop-color="#eeeeee" />
+ <stop offset="0.4" stop-color="#fbfaf9" />
+ </linearGradient>
+ <rect x="0" y="0" width="100%" height="100%" fill="url(#g)" />
+</svg> \ No newline at end of file
diff --git a/platform/www/lib/tpl/dokuwiki/images/page-gradient.png b/platform/www/lib/tpl/dokuwiki/images/page-gradient.png
new file mode 100644
index 0000000..38c7419
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/page-gradient.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools-build.php b/platform/www/lib/tpl/dokuwiki/images/pagetools-build.php
new file mode 100644
index 0000000..e19d750
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools-build.php
@@ -0,0 +1,125 @@
+<?php
+// phpcs:ignoreFile -- deprecated and will be removed
+/**
+ * This script generates a sprite from the unprocessed pagetool icons by combining them
+ * and overlaying a color layer for the active state.
+ *
+ * This script requires a current libGD to be available.
+ *
+ * The color for the active state is read from the style.ini's __link__ replacement
+ *
+ * The final sprite is optimized with optipng if available.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @deprecated 2018-06-15 we no longer use PNG based icons
+ * @todo Maybe add some more error checking
+ */
+$GAMMA = 0.8;
+$OPTIPNG = '/usr/bin/optipng';
+
+if('cli' != php_sapi_name()) die('please run from commandline');
+
+// load input images
+$input = glob('pagetools/*.png');
+sort($input);
+$cnt = count($input);
+if(!$cnt){
+ die("No input images found. This script needs to be called from within the image directory!\n");
+}
+
+// create destination image
+$DST = imagecreatetruecolor(30,$cnt*45*2);
+imagesavealpha($DST, true);
+$C_trans = imagecolorallocatealpha($DST, 0, 0, 0, 127);
+imagefill($DST, 0, 0, $C_trans);
+
+// load highlight color from style.ini
+$ini = parse_ini_file('../style.ini',true);
+$COLOR = hex2rgb($ini['replacements']['__link__']);
+$C_active = imagecolorallocate($DST, $COLOR['r'],$COLOR['g'],$COLOR['b']);
+
+// add all the icons to the sprite image
+for($i=0; $i<$cnt; $i++){
+ $base = $i*90;
+
+ $IN = imagecreatefrompng($input[$i]);
+ imagesavealpha($IN, true);
+ imagecolorscale($IN,$GAMMA);
+ imagecopy($DST,$IN, 0,$base, 0,0, 30,30);
+ imagedestroy($IN);
+
+ $IN = imagecreatefrompng($input[$i]);
+ imagesavealpha($IN, true);
+ imagecolorscale($IN,$GAMMA);
+ imagecopy($DST,$IN, 0,$base+45, 0,0, 30,30);
+ imagedestroy($IN);
+
+ imagelayereffect($DST, IMG_EFFECT_OVERLAY);
+ imagefilledrectangle($DST, 0,$base+45, 30,$base+45+30, $C_active);
+ imagelayereffect($DST, IMG_EFFECT_NORMAL);
+}
+
+// output sprite
+imagepng($DST,'pagetools-sprite.png');
+imagedestroy($DST);
+
+// optimize if possible
+if(is_executable($OPTIPNG)){
+ system("$OPTIPNG -o5 'pagetools-sprite.png'");
+}
+
+/**
+ * Convert a hex color code to an rgb array
+ */
+function hex2rgb($hex) {
+ // strip hash
+ $hex = str_replace('#', '', $hex);
+
+ // normalize short codes
+ if(strlen($hex) == 3){
+ $hex = substr($hex,0,1).
+ substr($hex,0,1).
+ substr($hex,1,1).
+ substr($hex,1,1).
+ substr($hex,2,1).
+ substr($hex,2,1);
+ }
+
+ // calc rgb
+ return array(
+ 'r' => hexdec(substr($hex, 0, 2)),
+ 'g' => hexdec(substr($hex, 2, 2)),
+ 'b' => hexdec(substr($hex, 4, 2))
+ );
+}
+
+/**
+ * Scale (darken/lighten) a given image
+ *
+ * @param resource $img The truetype GD image to work on
+ * @param float $scale Scale the colors by this value ( <1 darkens, >1 lightens)
+ */
+function imagecolorscale(&$img, $scale){
+ $w = imagesx($img);
+ $h = imagesy($img);
+
+ imagealphablending($img, false);
+ for($x = 0; $x < $w; $x++){
+ for($y = 0; $y < $h; $y++){
+ $rgba = imagecolorat($img, $x, $y);
+ $a = ($rgba >> 24) & 0xFF;
+ $r = ($rgba >> 16) & 0xFF;
+ $g = ($rgba >> 8) & 0xFF;
+ $b = $rgba & 0xFF;
+
+ $r = max(min(round($r*$scale),255),0);
+ $g = max(min(round($g*$scale),255),0);
+ $b = max(min(round($b*$scale),255),0);
+
+ $color = imagecolorallocatealpha($img, $r, $g, $b, $a);
+ imagesetpixel($img, $x, $y, $color);
+ }
+ }
+ imagealphablending($img, true);
+}
+
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools-sprite.png b/platform/www/lib/tpl/dokuwiki/images/pagetools-sprite.png
new file mode 100644
index 0000000..8e7f7f8
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools-sprite.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/00_default.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/00_default.png
new file mode 100644
index 0000000..bcb2de0
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/00_default.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/01_edit.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/01_edit.png
new file mode 100644
index 0000000..99f3093
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/01_edit.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/02_create.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/02_create.png
new file mode 100644
index 0000000..57fa68d
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/02_create.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/03_draft.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/03_draft.png
new file mode 100644
index 0000000..ce1c6cf
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/03_draft.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/04_show.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/04_show.png
new file mode 100644
index 0000000..1ced340
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/04_show.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/05_source.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/05_source.png
new file mode 100644
index 0000000..dffe193
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/05_source.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/06_revert.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/06_revert.png
new file mode 100644
index 0000000..18c6444
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/06_revert.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/07_revisions.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/07_revisions.png
new file mode 100644
index 0000000..e599d01
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/07_revisions.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/08_backlink.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/08_backlink.png
new file mode 100644
index 0000000..aa34e27
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/08_backlink.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/09_subscribe.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/09_subscribe.png
new file mode 100644
index 0000000..36254ff
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/09_subscribe.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/10_top.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/10_top.png
new file mode 100644
index 0000000..b930fd2
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/10_top.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/11_mediamanager.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/11_mediamanager.png
new file mode 100644
index 0000000..71b5a33
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/11_mediamanager.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/12_back.png b/platform/www/lib/tpl/dokuwiki/images/pagetools/12_back.png
new file mode 100644
index 0000000..6d6093e
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/12_back.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/pagetools/license.txt b/platform/www/lib/tpl/dokuwiki/images/pagetools/license.txt
new file mode 100644
index 0000000..299624c
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/pagetools/license.txt
@@ -0,0 +1,4 @@
+Icon set: iPhone toolbar icons
+Designer: TheWorkingGroup.ca
+License: Creative Commons Attribution-Share Alike License [http://creativecommons.org/licenses/by-sa/3.0/]
+URL: http://blog.twg.ca/2009/09/free-iphone-toolbar-icons/
diff --git a/platform/www/lib/tpl/dokuwiki/images/search.png b/platform/www/lib/tpl/dokuwiki/images/search.png
new file mode 100644
index 0000000..a07a721
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/search.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/toc-arrows.png b/platform/www/lib/tpl/dokuwiki/images/toc-arrows.png
new file mode 100644
index 0000000..4a353e4
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/toc-arrows.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/toc-bullet.png b/platform/www/lib/tpl/dokuwiki/images/toc-bullet.png
new file mode 100644
index 0000000..a2dfa47
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/toc-bullet.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/images/usertools.png b/platform/www/lib/tpl/dokuwiki/images/usertools.png
new file mode 100644
index 0000000..6810227
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/images/usertools.png
Binary files differ
diff --git a/platform/www/lib/tpl/dokuwiki/lang/en/lang.php b/platform/www/lib/tpl/dokuwiki/lang/en/lang.php
new file mode 100644
index 0000000..7c890c6
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/lang/en/lang.php
@@ -0,0 +1,13 @@
+<?php
+
+// style.ini values
+
+$lang['__background_site__'] = 'Color for the very background (behind the content box)';
+$lang['__link__'] = 'The general link color';
+$lang['__existing__'] = 'The color for links to existing pages';
+$lang['__missing__'] = 'The color for links to non-existing pages';
+$lang['__site_width__'] = 'The width of the full site (can be any length unit: %, px, em, ...)';
+$lang['__sidebar_width__'] = 'The width of the sidebar, if any (can be any length unit: %, px, em, ...)';
+$lang['__tablet_width__'] = 'Below screensizes of this width, the site switches to tablet mode';
+$lang['__phone_width__'] = 'Below screensizes of this width, the site switches to phone mode';
+$lang['__theme_color__'] = 'Theme color of the web app';
diff --git a/platform/www/lib/tpl/dokuwiki/lang/en/style.txt b/platform/www/lib/tpl/dokuwiki/lang/en/style.txt
new file mode 100644
index 0000000..7bf3e1a
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/lang/en/style.txt
@@ -0,0 +1,4 @@
+If you want to adjust the logo, simply use the Media Manager to upload a ''logo.png'' into the ''wiki'' or the root namespace and it
+will be automatically used. You can also upload a ''favicon.ico'' there. If you use a closed
+wiki it is recommended to make the ''wiki'' (or root) namespace world readable in the ACL settings or
+your logo is not shown to not logged in users.
diff --git a/platform/www/lib/tpl/dokuwiki/main.php b/platform/www/lib/tpl/dokuwiki/main.php
new file mode 100644
index 0000000..67b6e61
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/main.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * DokuWiki Default Template 2012
+ *
+ * @link http://dokuwiki.org/template
+ * @author Anika Henke <anika@selfthinker.org>
+ * @author Clarence Lee <clarencedglee@gmail.com>
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ */
+
+if (!defined('DOKU_INC')) die(); /* must be run from within DokuWiki */
+
+$hasSidebar = page_findnearest($conf['sidebar']);
+$showSidebar = $hasSidebar && ($ACT=='show');
+?><!DOCTYPE html>
+<html lang="<?php echo $conf['lang'] ?>" dir="<?php echo $lang['direction'] ?>" class="no-js">
+<head>
+ <meta charset="utf-8" />
+ <title><?php tpl_pagetitle() ?> [<?php echo strip_tags($conf['title']) ?>]</title>
+ <script>(function(H){H.className=H.className.replace(/\bno-js\b/,'js')})(document.documentElement)</script>
+ <?php tpl_metaheaders() ?>
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <?php echo tpl_favicon(array('favicon', 'mobile')) ?>
+ <?php tpl_includeFile('meta.html') ?>
+</head>
+
+<body>
+ <div id="dokuwiki__site"><div id="dokuwiki__top" class="site <?php echo tpl_classes(); ?> <?php
+ echo ($showSidebar) ? 'showSidebar' : ''; ?> <?php echo ($hasSidebar) ? 'hasSidebar' : ''; ?>">
+
+ <?php include('tpl_header.php') ?>
+
+ <div class="wrapper group">
+
+ <?php if($showSidebar): ?>
+ <!-- ********** ASIDE ********** -->
+ <div id="dokuwiki__aside"><div class="pad aside include group">
+ <h3 class="toggle"><?php echo $lang['sidebar'] ?></h3>
+ <div class="content"><div class="group">
+ <?php tpl_flush() ?>
+ <?php tpl_includeFile('sidebarheader.html') ?>
+ <?php tpl_include_page($conf['sidebar'], true, true) ?>
+ <?php tpl_includeFile('sidebarfooter.html') ?>
+ </div></div>
+ </div></div><!-- /aside -->
+ <?php endif; ?>
+
+ <!-- ********** CONTENT ********** -->
+ <div id="dokuwiki__content"><div class="pad group">
+ <?php html_msgarea() ?>
+
+ <div class="pageId"><span><?php echo hsc($ID) ?></span></div>
+
+ <div class="page group">
+ <?php tpl_flush() ?>
+ <?php tpl_includeFile('pageheader.html') ?>
+ <!-- wikipage start -->
+ <?php tpl_content() ?>
+ <!-- wikipage stop -->
+ <?php tpl_includeFile('pagefooter.html') ?>
+ </div>
+
+ <div class="docInfo"><?php tpl_pageinfo() ?></div>
+
+ <?php tpl_flush() ?>
+ </div></div><!-- /content -->
+
+ <hr class="a11y" />
+
+ <!-- PAGE ACTIONS -->
+ <div id="dokuwiki__pagetools">
+ <h3 class="a11y"><?php echo $lang['page_tools']; ?></h3>
+ <div class="tools">
+ <ul>
+ <?php echo (new \dokuwiki\Menu\PageMenu())->getListItems(); ?>
+ </ul>
+ </div>
+ </div>
+ </div><!-- /wrapper -->
+
+ <?php include('tpl_footer.php') ?>
+ </div></div><!-- /site -->
+
+ <div class="no"><?php tpl_indexerWebBug() /* provide DokuWiki housekeeping, required in all templates */ ?></div>
+ <div id="screen__mode" class="no"></div><?php /* helper to detect CSS media query in script.js */ ?>
+</body>
+</html>
diff --git a/platform/www/lib/tpl/dokuwiki/mediamanager.php b/platform/www/lib/tpl/dokuwiki/mediamanager.php
new file mode 100644
index 0000000..53ed062
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/mediamanager.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * DokuWiki Media Manager Popup
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ */
+// must be run from within DokuWiki
+if (!defined('DOKU_INC')) die();
+header('X-UA-Compatible: IE=edge,chrome=1');
+
+?><!DOCTYPE html>
+<html lang="<?php echo $conf['lang']?>" dir="<?php echo $lang['direction'] ?>" class="popup no-js">
+<head>
+ <meta charset="utf-8" />
+ <title>
+ <?php echo hsc($lang['mediaselect'])?>
+ [<?php echo strip_tags($conf['title'])?>]
+ </title>
+ <script>(function(H){H.className=H.className.replace(/\bno-js\b/,'js')})(document.documentElement)</script>
+ <?php tpl_metaheaders()?>
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <?php echo tpl_favicon(array('favicon', 'mobile')) ?>
+ <?php tpl_includeFile('meta.html') ?>
+</head>
+
+<body>
+ <div id="media__manager" class="dokuwiki">
+ <?php html_msgarea() ?>
+ <div id="mediamgr__aside"><div class="pad">
+ <h1><?php echo hsc($lang['mediaselect'])?></h1>
+
+ <?php /* keep the id! additional elements are inserted via JS here */?>
+ <div id="media__opts"></div>
+
+ <?php tpl_mediaTree() ?>
+ </div></div>
+
+ <div id="mediamgr__content"><div class="pad">
+ <?php tpl_mediaContent() ?>
+ </div></div>
+ </div>
+</body>
+</html>
diff --git a/platform/www/lib/tpl/dokuwiki/script.js b/platform/www/lib/tpl/dokuwiki/script.js
new file mode 100644
index 0000000..88dae90
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/script.js
@@ -0,0 +1,89 @@
+/**
+ * We handle several device classes based on browser width.
+ *
+ * - desktop: > __tablet_width__ (as set in style.ini)
+ * - mobile:
+ * - tablet <= __tablet_width__
+ * - phone <= __phone_width__
+ */
+var device_class = ''; // not yet known
+var device_classes = 'desktop mobile tablet phone';
+
+function tpl_dokuwiki_mobile(){
+
+ // the z-index in mobile.css is (mis-)used purely for detecting the screen mode here
+ var screen_mode = jQuery('#screen__mode').css('z-index') + '';
+
+ // determine our device pattern
+ // TODO: consider moving into dokuwiki core
+ switch (screen_mode) {
+ case '1':
+ if (device_class.match(/tablet/)) return;
+ device_class = 'mobile tablet';
+ break;
+ case '2':
+ if (device_class.match(/phone/)) return;
+ device_class = 'mobile phone';
+ break;
+ default:
+ if (device_class == 'desktop') return;
+ device_class = 'desktop';
+ }
+
+ jQuery('html').removeClass(device_classes).addClass(device_class);
+
+ // handle some layout changes based on change in device
+ var $handle = jQuery('#dokuwiki__aside h3.toggle');
+ var $toc = jQuery('#dw__toc h3');
+
+ if (device_class == 'desktop') {
+ // reset for desktop mode
+ if($handle.length) {
+ $handle[0].setState(1);
+ $handle.hide();
+ }
+ if($toc.length) {
+ $toc[0].setState(1);
+ }
+ }
+ if (device_class.match(/mobile/)){
+ // toc and sidebar hiding
+ if($handle.length) {
+ $handle.show();
+ $handle[0].setState(-1);
+ }
+ if($toc.length) {
+ $toc[0].setState(-1);
+ }
+ }
+}
+
+jQuery(function(){
+ var resizeTimer;
+ dw_page.makeToggle('#dokuwiki__aside h3.toggle','#dokuwiki__aside div.content');
+
+ tpl_dokuwiki_mobile();
+ jQuery(window).on('resize',
+ function(){
+ if (resizeTimer) clearTimeout(resizeTimer);
+ resizeTimer = setTimeout(tpl_dokuwiki_mobile,200);
+ }
+ );
+
+ // increase sidebar length to match content (desktop mode only)
+ var sidebar_height = jQuery('.desktop #dokuwiki__aside').height();
+ var pagetool_height = jQuery('.desktop #dokuwiki__pagetools ul:first').height();
+ // pagetools div has no height; ul has a height
+ var content_min = Math.max(sidebar_height || 0, pagetool_height || 0);
+
+ var content_height = jQuery('#dokuwiki__content div.page').height();
+ if(content_min && content_min > content_height) {
+ var $content = jQuery('#dokuwiki__content div.page');
+ $content.css('min-height', content_min);
+ }
+
+ // blur when clicked
+ jQuery('#dokuwiki__pagetools div.tools>ul>li>a').on('click', function(){
+ this.blur();
+ });
+});
diff --git a/platform/www/lib/tpl/dokuwiki/style.ini b/platform/www/lib/tpl/dokuwiki/style.ini
new file mode 100644
index 0000000..723e8bc
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/style.ini
@@ -0,0 +1,89 @@
+; Please see http://php.net/manual/en/function.parse-ini-file.php
+; for limitations of the ini format used here
+
+; To extend this file or make changes to it, it is recommended to create
+; a local conf/tpl/<template-folder-name>/style.ini file to prevent losing
+; any changes after an upgrade.
+; Please don't forget to copy the section your changes should be under
+; (i.e. [stylesheets] or [replacements]) into that file as well.
+
+; Define the stylesheets your template uses here. The second value
+; defines for which output media the style should be loaded. Currently
+; print, screen and all are supported.
+; You can reference CSS and LESS files here. Files referenced here will
+; be checked for updates when considering a cache rebuild while files
+; included through LESS' @import statements are not
+
+[stylesheets]
+
+css/basic.less = screen
+css/_imgdetail.css = screen
+css/_media_popup.css = screen
+css/_media_fullscreen.css = screen
+css/_fileuploader.css = screen
+css/_tabs.css = screen
+css/_links.css = screen
+css/_toc.css = screen
+css/_footnotes.css = screen
+css/_search.less = screen
+css/_recent.css = screen
+css/_diff.css = screen
+css/_edit.css = screen
+css/_modal.css = screen
+css/_forms.css = screen
+css/_admin.less = screen
+css/structure.less = screen
+css/design.less = screen
+css/usertools.less = screen
+css/pagetools.less = screen
+css/content.less = screen
+
+css/mobile.less = all
+css/print.css = print
+
+
+; This section is used to configure some placeholder values used in
+; the stylesheets. Changing this file is the simplest method to
+; give your wiki a new look.
+; Placeholders defined here will also be made available as LESS variables
+; (with surrounding underscores removed, and the prefix @ini_ added)
+
+[replacements]
+
+;--------------------------------------------------------------------------
+;------ guaranteed dokuwiki color placeholders that every plugin can use
+
+; main text and background colors
+__text__ = "#333" ; @ini_text
+__background__ = "#fff" ; @ini_background
+; alternative text and background colors
+__text_alt__ = "#999" ; @ini_text_alt
+__background_alt__ = "#eee" ; @ini_background_alt
+; neutral text and background colors
+__text_neu__ = "#666" ; @ini_text_neu
+__background_neu__ = "#ddd" ; @ini_background_neu
+; border color
+__border__ = "#ccc" ; @ini_border
+
+; highlighted text (e.g. search snippets)
+__highlight__ = "#ff9" ; @ini_highlight
+
+; default link color
+__link__ = "#2b73b7" ; @ini_link
+
+;--------------------------------------------------------------------------
+
+__background_site__ = "#fbfaf9" ; @ini_background_site
+
+; these are used for wiki links
+__existing__ = "#080" ; @ini_existing
+__missing__ = "#d30" ; @ini_missing
+
+; site and sidebar widths
+__site_width__ = "75em" ; @ini_site_width
+__sidebar_width__ = "16em" ; @ini_sidebar_width
+; cut off points for mobile devices
+__tablet_width__ = "800px" ; @ini_tablet_width
+__phone_width__ = "480px" ; @ini_phone_width
+
+__theme_color__ = "#008800" ; @_ini_theme_color: theme_color of the web app
diff --git a/platform/www/lib/tpl/dokuwiki/template.info.txt b/platform/www/lib/tpl/dokuwiki/template.info.txt
new file mode 100644
index 0000000..73ad939
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/template.info.txt
@@ -0,0 +1,7 @@
+base dokuwiki
+author Anika Henke
+email anika@selfthinker.org
+date 2015-07-26
+name DokuWiki Template
+desc DokuWiki's default template since 2012
+url http://www.dokuwiki.org/template:dokuwiki
diff --git a/platform/www/lib/tpl/dokuwiki/tpl_footer.php b/platform/www/lib/tpl/dokuwiki/tpl_footer.php
new file mode 100644
index 0000000..c7a04e1
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/tpl_footer.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Template footer, included in the main and detail files
+ */
+
+// must be run from within DokuWiki
+if (!defined('DOKU_INC')) die();
+?>
+
+<!-- ********** FOOTER ********** -->
+<div id="dokuwiki__footer"><div class="pad">
+ <?php tpl_license(''); // license text ?>
+
+ <div class="buttons">
+ <?php
+ tpl_license('button', true, false, false); // license button, no wrapper
+ $target = ($conf['target']['extern']) ? 'target="'.$conf['target']['extern'].'"' : '';
+ ?>
+ <a href="https://www.dokuwiki.org/donate" title="Donate" <?php echo $target?>><img
+ src="<?php echo tpl_basedir(); ?>images/button-donate.gif" width="80" height="15" alt="Donate" /></a>
+ <a href="https://php.net" title="Powered by PHP" <?php echo $target?>><img
+ src="<?php echo tpl_basedir(); ?>images/button-php.gif" width="80" height="15" alt="Powered by PHP" /></a>
+ <a href="//validator.w3.org/check/referer" title="Valid HTML5" <?php echo $target?>><img
+ src="<?php echo tpl_basedir(); ?>images/button-html5.png" width="80" height="15" alt="Valid HTML5" /></a>
+ <a href="//jigsaw.w3.org/css-validator/check/referer?profile=css3" title="Valid CSS" <?php echo $target?>><img
+ src="<?php echo tpl_basedir(); ?>images/button-css.png" width="80" height="15" alt="Valid CSS" /></a>
+ <a href="https://dokuwiki.org/" title="Driven by DokuWiki" <?php echo $target?>><img
+ src="<?php echo tpl_basedir(); ?>images/button-dw.png" width="80" height="15"
+ alt="Driven by DokuWiki" /></a>
+ </div>
+</div></div><!-- /footer -->
+
+<?php
+tpl_includeFile('footer.html');
diff --git a/platform/www/lib/tpl/dokuwiki/tpl_header.php b/platform/www/lib/tpl/dokuwiki/tpl_header.php
new file mode 100644
index 0000000..bb8732b
--- /dev/null
+++ b/platform/www/lib/tpl/dokuwiki/tpl_header.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Template header, included in the main and detail files
+ */
+
+// must be run from within DokuWiki
+if (!defined('DOKU_INC')) die();
+?>
+
+<!-- ********** HEADER ********** -->
+<div id="dokuwiki__header"><div class="pad group">
+
+ <?php tpl_includeFile('header.html') ?>
+
+ <div class="headings group">
+ <ul class="a11y skip">
+ <li><a href="#dokuwiki__content"><?php echo $lang['skip_to_content']; ?></a></li>
+ </ul>
+
+ <h1><?php
+ // get logo either out of the template images folder or data/media folder
+ $logoSize = array();
+ $logo = tpl_getMediaFile(array(':wiki:logo.png', ':logo.png', 'images/logo.png'), false, $logoSize);
+
+ // display logo and wiki title in a link to the home page
+ tpl_link(
+ wl(),
+ '<img src="'.$logo.'" '.$logoSize[3].' alt="" /> <span>'.$conf['title'].'</span>',
+ 'accesskey="h" title="[H]"'
+ );
+ ?></h1>
+ <?php if ($conf['tagline']): ?>
+ <p class="claim"><?php echo $conf['tagline']; ?></p>
+ <?php endif ?>
+ </div>
+
+ <div class="tools group">
+ <!-- USER TOOLS -->
+ <?php if ($conf['useacl']): ?>
+ <div id="dokuwiki__usertools">
+ <h3 class="a11y"><?php echo $lang['user_tools']; ?></h3>
+ <ul>
+ <?php
+ if (!empty($_SERVER['REMOTE_USER'])) {
+ echo '<li class="user">';
+ tpl_userinfo(); /* 'Logged in as ...' */
+ echo '</li>';
+ }
+ echo (new \dokuwiki\Menu\UserMenu())->getListItems('action ');
+ ?>
+ </ul>
+ </div>
+ <?php endif ?>
+
+ <!-- SITE TOOLS -->
+ <div id="dokuwiki__sitetools">
+ <h3 class="a11y"><?php echo $lang['site_tools']; ?></h3>
+ <?php tpl_searchform(); ?>
+ <div class="mobileTools">
+ <?php echo (new \dokuwiki\Menu\MobileMenu())->getDropdown($lang['tools']); ?>
+ </div>
+ <ul>
+ <?php echo (new \dokuwiki\Menu\SiteMenu())->getListItems('action ', false); ?>
+ </ul>
+ </div>
+
+ </div>
+
+ <!-- BREADCRUMBS -->
+ <?php if($conf['breadcrumbs'] || $conf['youarehere']): ?>
+ <div class="breadcrumbs">
+ <?php if($conf['youarehere']): ?>
+ <div class="youarehere"><?php tpl_youarehere() ?></div>
+ <?php endif ?>
+ <?php if($conf['breadcrumbs']): ?>
+ <div class="trace"><?php tpl_breadcrumbs() ?></div>
+ <?php endif ?>
+ </div>
+ <?php endif ?>
+
+
+
+ <hr class="a11y" />
+</div></div><!-- /header -->
diff --git a/platform/www/lib/tpl/index.php b/platform/www/lib/tpl/index.php
new file mode 100644
index 0000000..4d48d51
--- /dev/null
+++ b/platform/www/lib/tpl/index.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * This file reads the style.ini of the used template and displays the
+ * replacements defined in it. Color replacements will be displayed
+ * visually. This should help with adjusting and using the styles
+ * specified in the style.ini
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Anika Henke <anika@selfthinker.org>
+ */
+// phpcs:disable PSR1.Files.SideEffects
+if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../');
+if(!defined('NOSESSION')) define('NOSESSION',1);
+require_once(DOKU_INC.'inc/init.php');
+?>
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <title>Template Replacements</title>
+ <style>
+ body {
+ background-color: #fff;
+ color: #000;
+ }
+ caption {
+ font-weight: bold;
+ }
+ td {
+ margin: 0;
+ padding: 0.5em 2em;
+ font-family: monospace;
+ font-size: 120%;
+ border: 1px solid #fff;
+ }
+ tr:hover td {
+ border: 1px solid #ccc;
+ }
+ .color {
+ padding: 0.25em 1em;
+ border: 1px #000 solid;
+ }
+ </style>
+</head>
+<body>
+<?php
+// get merged style.ini
+$styleUtils = new \dokuwiki\StyleUtils($conf['template']);
+$ini = $styleUtils->cssStyleini();
+
+if (!empty($ini)) {
+ echo '<table>';
+ echo "<caption>".hsc($conf['template'])."'s style.ini</caption>";
+ foreach($ini['replacements'] as $key => $val){
+ echo '<tr>';
+ echo '<td>'.hsc($key).'</td>';
+ echo '<td>'.hsc($val).'</td>';
+ echo '<td>';
+ if(preg_match('/^#[0-f]{3,6}$/i',$val)){
+ echo '<div class="color" style="background-color:'.$val.';">&#160;</div>';
+ }
+ echo '</td>';
+ echo '</tr>';
+ }
+ echo '</table>';
+} else {
+ echo "<p>Non-existent or invalid template or style.ini: <strong>".hsc($conf['template'])."</strong></p>";
+}
+?>
+</body>
+</html>