static-copy.pl 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. # Copyright (C) 2004-2014 Alex Schroeder <alex@gnu.org>
  2. #
  3. # This program is free software: you can redistribute it and/or modify it under
  4. # the terms of the GNU General Public License as published by the Free Software
  5. # Foundation, either version 3 of the License, or (at your option) any later
  6. # version.
  7. #
  8. # This program is distributed in the hope that it will be useful, but WITHOUT
  9. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  10. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License along with
  13. # this program. If not, see <http://www.gnu.org/licenses/>.
  14. use strict;
  15. use v5.10;
  16. AddModuleDescription('static-copy.pl', 'Static Copy Extension');
  17. our ($q, %Page, %IndexHash, $OpenPageName, $ScriptName, $SiteName, $UsePathInfo, %Action, $CommentsPrefix, $FreeLinks, $WikiLinks, $LinkPattern, $FreeLinkPattern, $StyleSheet, $StyleSheetPage, $TopLinkBar, $UserGotoBar, $LogoUrl, $SidebarName);
  18. $Action{static} = \&DoStatic;
  19. our ($StaticDir, $StaticAlways, %StaticMimeTypes, $StaticUrl);
  20. $StaticDir = '/tmp/static';
  21. $StaticUrl = ''; # change this!
  22. $StaticAlways = 0; # 1 = uploaded files only, 2 = all pages
  23. my $StaticMimeTypes = '/etc/mime.types';
  24. my %StaticFiles;
  25. sub DoStatic {
  26. return unless UserIsAdminOrError();
  27. my $raw = GetParam('raw', 0);
  28. if ($raw) {
  29. print GetHttpHeader('text/plain');
  30. } else {
  31. print GetHeader('', T('Static Copy'), '');
  32. }
  33. CreateDir($StaticDir);
  34. %StaticFiles = ();
  35. print '<p>' unless $raw;
  36. StaticWriteFiles();
  37. print '</p>' unless $raw;
  38. PrintFooter() unless $raw;
  39. }
  40. sub StaticMimeTypes {
  41. my %hash;
  42. # the default mapping matches the default @UploadTypes...
  43. open(my $fh, '<', $StaticMimeTypes)
  44. or return ('image/jpeg' => 'jpg', 'image/png' => 'png', );
  45. while (<$fh>) {
  46. s/\#.*//; # remove comments
  47. my($type, $ext) = split;
  48. $hash{$type} = $ext if $ext;
  49. }
  50. close($fh);
  51. return %hash;
  52. }
  53. sub StaticWriteFiles {
  54. my $raw = GetParam('raw', 0);
  55. my $html = GetParam('html', 0);
  56. local *ScriptLink = \&StaticScriptLink;
  57. local *GetDownloadLink = \&StaticGetDownloadLink;
  58. foreach my $id (AllPagesList()) {
  59. if ($StaticAlways > 1
  60. or $html
  61. or PageIsUploadedFile($id)) {
  62. StaticWriteFile($id, $html);
  63. }
  64. }
  65. if ($StaticAlways > 1 or $html) {
  66. StaticWriteCss();
  67. }
  68. }
  69. sub StaticScriptLink {
  70. my ($action, $text, $class, $name, $title, $accesskey) = @_;
  71. my %params;
  72. if ($action !~ /=/) {
  73. # the page might not exist, eg. if called via GetAuthorLink
  74. $params{'-href'} = StaticFileName($action) if $IndexHash{UrlDecode($action)};
  75. }
  76. $params{'-class'} = $class if $class;
  77. $params{'-name'} = UrlEncode($name) if $name;
  78. $params{'-title'} = $title if $title;
  79. $params{'-accesskey'} = $accesskey if $accesskey;
  80. return $q->a(\%params, $text);
  81. }
  82. sub StaticGetDownloadLink {
  83. my ($name, $image, $revision, $alt) = @_; # ignore $revision
  84. $alt = $name unless $alt;
  85. $alt =~ s/_/ /g;
  86. my $id = FreeToNormal($name);
  87. # if the page does not exist
  88. return '[' . ($image ? 'image' : 'link') . ':' . $name . ']' unless $IndexHash{$id};
  89. if ($image) {
  90. return StaticFileName($id) if $image == 2;
  91. my $result = $q->img({-src=>StaticFileName($id), -alt=>$alt, -class=>'upload'});
  92. $result = ScriptLink($id, $result, 'image');
  93. return $result;
  94. } else {
  95. return ScriptLink($id, $alt, 'upload');
  96. }
  97. }
  98. sub StaticFileName {
  99. my $id = shift;
  100. $id =~ s/#.*//; # remove named anchors for the filename test
  101. return $StaticFiles{$id} if $StaticFiles{$id}; # cache filenames
  102. # Don't clober current open page so don't use OpenPage. UrlDecode
  103. # the $id to open the file because when called from
  104. # StaticScriptLink, for example, the $action is already encoded.
  105. my ($status, $data) = ReadFile(GetPageFile(UrlDecode($id)));
  106. # If the link points to a wanted page, we cannot make this static.
  107. return $id unless $status;
  108. my $hash = ParseData($data);
  109. my $ext = '.html';
  110. if ($hash->{text} =~ /^\#FILE ([^ \n]+ ?[^ \n]*)\n(.*)/s) {
  111. %StaticMimeTypes = StaticMimeTypes() unless %StaticMimeTypes;
  112. $ext = $StaticMimeTypes{"$1"};
  113. $ext = '.' . $ext if $ext;
  114. }
  115. $StaticFiles{$id} = $id . $ext;
  116. return $StaticFiles{$id};
  117. }
  118. sub StaticWriteFile {
  119. my ($id, $html) = @_;
  120. my $raw = GetParam('raw', 0);
  121. OpenPage($id);
  122. my ($mimetype, $encoding, $data) =
  123. $Page{text} =~ /^\#FILE ([^ \n]+) ?([^ \n]*)\n(.*)/s;
  124. my $filename = StaticFileName($id);
  125. open(my $fh, '>', encode_utf8("$StaticDir/$filename"))
  126. or ReportError(Ts('Cannot write %s', $filename));
  127. if ($data) {
  128. binmode($fh);
  129. StaticFile($id, $fh, $mimetype, $data);
  130. } elsif ($html) {
  131. binmode($fh, ':encoding(UTF-8)');
  132. StaticHtml($id, $fh);
  133. } else {
  134. print "no data for ";
  135. }
  136. close($fh);
  137. chmod 0644,"$StaticDir/$filename";
  138. print $filename, $raw ? "\n" : $q->br();
  139. }
  140. sub StaticFile {
  141. my ($id, $fh, $type, $data) = @_;
  142. require MIME::Base64;
  143. print $fh (MIME::Base64::decode($data));
  144. }
  145. sub StaticHtml {
  146. my ($id, $fh) = @_; # assume open page
  147. # redirect
  148. if (($FreeLinks and $Page{text} =~ /^\#REDIRECT\s+\[\[$FreeLinkPattern\]\]/)
  149. or ($WikiLinks and $Page{text} =~ /^\#REDIRECT\s+$LinkPattern/)) {
  150. my $target = StaticFileName($1);
  151. print $fh <<"EOT";
  152. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  153. <html>
  154. <head>
  155. <title>$SiteName: $id</title>
  156. <link type="text/css" rel="stylesheet" href="static.css" />
  157. <meta http-equiv="refresh" content="0; url=$target">
  158. <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  159. </head>
  160. <body>
  161. <p>Redirected to <a href="$target">$1</a>.</p>
  162. </body>
  163. </html>
  164. EOT
  165. return;
  166. }
  167. print $fh <<"EOT";
  168. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  169. <html>
  170. <head>
  171. <title>$SiteName: $id</title>
  172. <link type="text/css" rel="stylesheet" href="static.css" />
  173. <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  174. </head>
  175. <body>
  176. EOT
  177. my $header = '';
  178. # logo
  179. if ($LogoUrl) {
  180. my $logo = $LogoUrl;
  181. $logo =~ s|.*/||; # just the filename
  182. my $alt = T('[Home]');
  183. $header .= $q->img({-src=>$logo, -alt=>$alt, -class=>'logo'}) if $logo;
  184. }
  185. # top toolbar
  186. local $UserGotoBar = ''; # only allow @UserGotoBarPages
  187. my $toolbar = GetGotoBar($id);
  188. $header .= $toolbar if GetParam('toplinkbar', $TopLinkBar);
  189. # title
  190. my $name = $id;
  191. $name =~ s|_| |g;
  192. $header .= $q->h1($name);
  193. print $fh $q->div({-class=>'header'}, $header);
  194. # sidebar, if the module is loaded
  195. print $fh $q->div({-class=>'sidebar'}, PageHtml($SidebarName)) if $SidebarName;
  196. # content
  197. print $fh $q->div({-class=>'content'}, PageHtml($id)); # this reopens the page currently open
  198. # footer
  199. my $links = '';
  200. if ($OpenPageName !~ /^$CommentsPrefix/ # fails if $CommentsPrefix is empty!
  201. and $IndexHash{$CommentsPrefix . $OpenPageName}) { # TODO use $CommentsPattern
  202. $links .= ScriptLink(UrlEncode($CommentsPrefix . $OpenPageName),
  203. T('Comments on this page'));
  204. }
  205. if ($CommentsPrefix and $id =~ /^$CommentsPrefix(.*)/) {
  206. $links .= ' | ' if $links;
  207. $links .= Ts('Back to %s', GetPageLink($1, $1));
  208. }
  209. $links = $q->br() . $links if $links;
  210. print $fh $q->div({-class=>'footer'}, $q->hr(), $toolbar,
  211. $q->span({-class=>'edit'}, $links),
  212. $q->span({-class=>'time'}, GetFooterTimestamp($id)));
  213. # finish
  214. print $fh '</body></html>';
  215. }
  216. sub StaticWriteCss {
  217. my $css;
  218. if ($StyleSheet) {
  219. if (ref $StyleSheet) {
  220. $css = join '', map { GetRaw($_) } @$StyleSheet;
  221. } else {
  222. $css = GetRaw($StyleSheet);
  223. }
  224. }
  225. if (not $css and $IndexHash{$StyleSheetPage}) {
  226. $css = GetPageContent($StyleSheetPage);
  227. }
  228. if (not $css) {
  229. $css = GetRaw('https://oddmuse.org/default.css');
  230. }
  231. WriteStringToFile("$StaticDir/static.css", $css) if $css;
  232. chmod 0644,"$StaticDir/static.css";
  233. }
  234. *StaticFilesOldSave = \&Save;
  235. *Save = \&StaticFilesNewSave;
  236. sub StaticFilesNewSave {
  237. my ($id, $new) = @_;
  238. StaticFilesOldSave(@_);
  239. if ($StaticAlways) {
  240. # always delete
  241. StaticDeleteFile($id);
  242. if ($new =~ /^\#FILE / # if a file was uploaded
  243. or $StaticAlways > 1) {
  244. CreateDir($StaticDir);
  245. StaticWriteFile($OpenPageName);
  246. }
  247. }
  248. }
  249. *StaticOldDeletePage = \&DeletePage;
  250. *DeletePage = \&StaticNewDeletePage;
  251. sub StaticNewDeletePage {
  252. my $id = shift;
  253. StaticDeleteFile($id) if ($StaticAlways);
  254. return StaticOldDeletePage($id);
  255. }
  256. sub StaticDeleteFile {
  257. my $id = shift;
  258. %StaticMimeTypes = StaticMimeTypes() unless %StaticMimeTypes;
  259. # we don't care if the files or $StaticDir don't exist -- just delete!
  260. for my $f (map { "$StaticDir/$id.$_" } (values %StaticMimeTypes, 'html')) {
  261. Unlink($f); # delete copies with different extensions
  262. }
  263. }
  264. # override the default!
  265. sub GetDownloadLink {
  266. my ($name, $image, $revision, $alt) = @_;
  267. $alt = $name unless $alt;
  268. my $id = FreeToNormal($name);
  269. AllPagesList();
  270. # if the page does not exist
  271. return '[' . ($image ? T('image') : T('download')) . ':' . $name
  272. . ']' . GetEditLink($id, '?', 1) unless $IndexHash{$id};
  273. my $action;
  274. if ($revision) {
  275. $action = "action=download;id=" . UrlEncode($id) . ";revision=$revision";
  276. } elsif ($UsePathInfo) {
  277. $action = "download/" . UrlEncode($id);
  278. } else {
  279. $action = "action=download;id=" . UrlEncode($id);
  280. }
  281. if ($image) {
  282. if ($UsePathInfo and not $revision) {
  283. if ($StaticAlways and $StaticUrl) {
  284. my $url = $StaticUrl;
  285. my $img = UrlEncode(StaticFileName($id));
  286. $url =~ s/\%s/$img/g or $url .= $img;
  287. $action = $url;
  288. } else {
  289. $action = $ScriptName . '/' . $action;
  290. }
  291. } else {
  292. $action = $ScriptName . '?' . $action;
  293. }
  294. return $action if $image == 2;
  295. my $result = $q->img({-src=>$action, -alt=>$alt, -class=>'upload'});
  296. $result = ScriptLink(UrlEncode($id), $result, 'image') unless $id eq $OpenPageName;
  297. return $result;
  298. } else {
  299. return ScriptLink($action, $alt, 'upload');
  300. }
  301. }
  302. # override function from Image Extension to support advanced image tags
  303. sub ImageGetInternalUrl {
  304. my $id = FreeToNormal(shift);
  305. if ($UsePathInfo) {
  306. if ($StaticAlways and $StaticUrl) {
  307. my $url = $StaticUrl;
  308. my $img = UrlEncode(StaticFileName($id));
  309. $url =~ s/\%s/$img/g or $url .= $img;
  310. return $url;
  311. } else {
  312. return $ScriptName . '/download/' . UrlEncode($id);
  313. }
  314. }
  315. return $ScriptName . '?action=download;id=' . UrlEncode($id);
  316. }