summaryrefslogtreecommitdiff
path: root/IkiWiki/Plugin/inline.pm
blob: caef98ef2fc32fdd2c39c16b9f6a084af7dcfcbc (plain)
  1. #!/usr/bin/perl
  2. # Page inlining and blogging.
  3. package IkiWiki::Plugin::inline;
  4. use warnings;
  5. use strict;
  6. use IkiWiki 1.00;
  7. use IkiWiki::Render; # for displaytime
  8. use URI;
  9. sub import { #{{{
  10. hook(type => "preprocess", id => "inline",
  11. call => \&IkiWiki::preprocess_inline);
  12. hook(type => "pagetemplate", id => "inline",
  13. call => \&IkiWiki::pagetemplate_inline);
  14. # Hook to change to do pinging since it's called late.
  15. # This ensures each page only pings once and prevents slow
  16. # pings interrupting page builds.
  17. hook(type => "change", id => "inline",
  18. call => \&IkiWiki::pingurl);
  19. } # }}}
  20. # Back to ikiwiki namespace for the rest, this code is very much
  21. # internal to ikiwiki even though it's separated into a plugin.
  22. package IkiWiki;
  23. my %toping;
  24. my %feedlinks;
  25. sub yesno ($) { #{{{
  26. my $val=shift;
  27. return (defined $val && lc($val) eq "yes");
  28. } #}}}
  29. sub preprocess_inline (@) { #{{{
  30. my %params=@_;
  31. if (! exists $params{pages}) {
  32. return "";
  33. }
  34. my $raw=yesno($params{raw});
  35. my $archive=yesno($params{archive});
  36. my $rss=($config{rss} && exists $params{rss}) ? yesno($params{rss}) : $config{rss};
  37. my $atom=($config{atom} && exists $params{atom}) ? yesno($params{atom}) : $config{atom};
  38. my $feeds=exists $params{feeds} ? yesno($params{feeds}) : 1;
  39. if (! exists $params{show} && ! $archive) {
  40. $params{show}=10;
  41. }
  42. my $desc;
  43. if (exists $params{description}) {
  44. $desc = $params{description}
  45. } else {
  46. $desc = $config{wikiname};
  47. }
  48. my $actions=yesno($params{actions});
  49. my @list;
  50. foreach my $page (keys %pagesources) {
  51. next if $page eq $params{page};
  52. if (pagespec_match($page, $params{pages})) {
  53. push @list, $page;
  54. }
  55. }
  56. if (exists $params{sort} && $params{sort} eq 'title') {
  57. @list=sort @list;
  58. }
  59. elsif (! exists $params{sort} || $params{sort} eq 'age') {
  60. @list=sort { $pagectime{$b} <=> $pagectime{$a} } @list;
  61. }
  62. else {
  63. return "unknown sort type $params{sort}";
  64. }
  65. if ($params{show} && @list > $params{show}) {
  66. @list=@list[0..$params{show} - 1];
  67. }
  68. add_depends($params{page}, $params{pages});
  69. my $rssurl=rsspage(basename($params{page}));
  70. my $atomurl=atompage(basename($params{page}));
  71. my $ret="";
  72. if (exists $params{rootpage} && $config{cgiurl}) {
  73. # Add a blog post form, with feed buttons.
  74. my $formtemplate=template("blogpost.tmpl", blind_cache => 1);
  75. $formtemplate->param(cgiurl => $config{cgiurl});
  76. $formtemplate->param(rootpage => $params{rootpage});
  77. $formtemplate->param(rssurl => $rssurl) if $feeds && $rss;
  78. $formtemplate->param(atomurl => $atomurl) if $feeds && $atom;
  79. $ret.=$formtemplate->output;
  80. }
  81. elsif ($feeds) {
  82. # Add feed buttons.
  83. my $linktemplate=template("feedlink.tmpl", blind_cache => 1);
  84. $linktemplate->param(rssurl => $rssurl) if $rss;
  85. $linktemplate->param(atomurl => $atomurl) if $atom;
  86. $ret.=$linktemplate->output;
  87. }
  88. my $template=template(
  89. ($archive ? "inlinepagetitle.tmpl" : "inlinepage.tmpl"),
  90. blind_cache => 1,
  91. ) unless $raw;
  92. foreach my $page (@list) {
  93. my $file = $pagesources{$page};
  94. my $type = pagetype($file);
  95. if (! $raw || ($raw && ! defined $type)) {
  96. # Get the content before populating the template,
  97. # since getting the content uses the same template
  98. # if inlines are nested.
  99. # TODO: if $archive=1, the only reason to do this
  100. # is to let the meta plugin get page title info; so stop
  101. # calling this next line then once the meta plugin can
  102. # store that accross runs (also tags plugin).
  103. my $content=get_inline_content($page, $params{destpage});
  104. # Don't use htmllink because this way the title is separate
  105. # and can be overridden by other plugins.
  106. my $link=bestlink($params{page}, $page);
  107. $link=htmlpage($link) if defined $type;
  108. $link=abs2rel($link, dirname($params{destpage}));
  109. $template->param(pageurl => $link);
  110. $template->param(title => pagetitle(basename($page)));
  111. $template->param(content => $content);
  112. $template->param(ctime => displaytime($pagectime{$page}));
  113. if ($actions) {
  114. my $file = $pagesources{$page};
  115. my $type = pagetype($file);
  116. if ($config{discussion}) {
  117. $template->param(have_actions => 1);
  118. $template->param(discussionlink => htmllink($page, $page, "Discussion", 1, 1));
  119. }
  120. if (length $config{cgiurl} && defined $type) {
  121. $template->param(have_actions => 1);
  122. $template->param(editurl => cgiurl(do => "edit", page => $page));
  123. }
  124. }
  125. run_hooks(pagetemplate => sub {
  126. shift->(page => $page, destpage => $params{page},
  127. template => $template,);
  128. });
  129. $ret.=$template->output;
  130. $template->clear_params;
  131. }
  132. else {
  133. if (defined $type) {
  134. $ret.="\n".
  135. linkify($page, $params{page},
  136. preprocess($page, $params{page},
  137. filter($page,
  138. readfile(srcfile($file)))));
  139. }
  140. }
  141. }
  142. if ($feeds && $rss) {
  143. will_render($params{page}, rsspage($params{page}));
  144. writefile(rsspage($params{page}), $config{destdir},
  145. genfeed("rss", $rssurl, $desc, $params{page}, @list));
  146. $toping{$params{page}}=1 unless $config{rebuild};
  147. $feedlinks{$params{destpage}}=qq{<link rel="alternate" type="application/rss+xml" title="RSS" href="$rssurl" />};
  148. }
  149. if ($feeds && $atom) {
  150. will_render($params{page}, atompage($params{page}));
  151. writefile(atompage($params{page}), $config{destdir},
  152. genfeed("atom", $atomurl, $desc, $params{page}, @list));
  153. $toping{$params{page}}=1 unless $config{rebuild};
  154. $feedlinks{$params{destpage}}=qq{<link rel="alternate" type="application/atom+xml" title="Atom" href="$atomurl" />};
  155. }
  156. return $ret;
  157. } #}}}
  158. sub pagetemplate_inline (@) { #{{{
  159. my %params=@_;
  160. my $page=$params{page};
  161. my $template=$params{template};
  162. $template->param(feedlinks => $feedlinks{$page})
  163. if exists $feedlinks{$page} && $template->query(name => "feedlinks");
  164. } #}}}
  165. sub get_inline_content ($$) { #{{{
  166. my $page=shift;
  167. my $destpage=shift;
  168. my $file=$pagesources{$page};
  169. my $type=pagetype($file);
  170. if (defined $type) {
  171. return htmlize($page, $type,
  172. linkify($page, $destpage,
  173. preprocess($page, $destpage,
  174. filter($page,
  175. readfile(srcfile($file))))));
  176. }
  177. else {
  178. return "";
  179. }
  180. } #}}}
  181. sub date_822 ($) { #{{{
  182. my $time=shift;
  183. eval q{use POSIX};
  184. my $lc_time= POSIX::setlocale(&POSIX::LC_TIME);
  185. POSIX::setlocale(&POSIX::LC_TIME, "C");
  186. my $ret=POSIX::strftime("%a, %d %b %Y %H:%M:%S %z", localtime($time));
  187. POSIX::setlocale(&POSIX::LC_TIME, $lc_time);
  188. return $ret;
  189. } #}}}
  190. sub date_3339 ($) { #{{{
  191. my $time=shift;
  192. eval q{use POSIX};
  193. my $lc_time= POSIX::setlocale(&POSIX::LC_TIME);
  194. POSIX::setlocale(&POSIX::LC_TIME, "C");
  195. my $ret=POSIX::strftime("%Y-%m-%dT%H:%M:%SZ", localtime($time));
  196. POSIX::setlocale(&POSIX::LC_TIME, $lc_time);
  197. return $ret;
  198. } #}}}
  199. sub absolute_urls ($$) { #{{{
  200. # sucky sub because rss sucks
  201. my $content=shift;
  202. my $url=shift;
  203. $url=~s/[^\/]+$//;
  204. $content=~s/<a\s+href="(?![^:]+:\/\/)([^"]+)"/<a href="$url$1"/ig;
  205. $content=~s/<img\s+src="(?![^:]+:\/\/)([^"]+)"/<img src="$url$1"/ig;
  206. return $content;
  207. } #}}}
  208. sub rsspage ($) { #{{{
  209. my $page=shift;
  210. return $page.".rss";
  211. } #}}}
  212. sub atompage ($) { #{{{
  213. my $page=shift;
  214. return $page.".atom";
  215. } #}}}
  216. sub genfeed ($$$$@) { #{{{
  217. my $feedtype=shift;
  218. my $feedurl=shift;
  219. my $feeddesc=shift;
  220. my $page=shift;
  221. my @pages=@_;
  222. my $url=URI->new(encode_utf8($config{url}."/".htmlpage($page)));
  223. my $itemtemplate=template($feedtype."item.tmpl", blind_cache => 1);
  224. my $content="";
  225. my $lasttime = 0;
  226. foreach my $p (@pages) {
  227. my $u=URI->new(encode_utf8($config{url}."/".htmlpage($p)));
  228. $itemtemplate->param(
  229. title => pagetitle(basename($p)),
  230. url => $u,
  231. permalink => $u,
  232. date_822 => date_822($pagectime{$p}),
  233. date_3339 => date_3339($pagectime{$p}),
  234. );
  235. my $pcontent = absolute_urls(get_inline_content($p, $page), $url);
  236. if ($itemtemplate->query(name => "enclosure")) {
  237. my $file=$pagesources{$p};
  238. my $type=pagetype($file);
  239. if (defined $type) {
  240. $itemtemplate->param(content => $pcontent);
  241. }
  242. else {
  243. my ($a, $b, $c, $d, $e, $f, $g, $size) = stat(srcfile($file));
  244. my $mime="unknown";
  245. eval q{use File::MimeInfo};
  246. if (! $@) {
  247. $mime = mimetype($file);
  248. }
  249. $itemtemplate->param(
  250. enclosure => $u,
  251. type => $mime,
  252. length => $size,
  253. );
  254. }
  255. }
  256. else {
  257. $itemtemplate->param(content => $pcontent);
  258. }
  259. run_hooks(pagetemplate => sub {
  260. shift->(page => $p, destpage => $page,
  261. template => $itemtemplate);
  262. });
  263. $content.=$itemtemplate->output;
  264. $itemtemplate->clear_params;
  265. $lasttime = $pagectime{$p} if $pagectime{$p} > $lasttime;
  266. }
  267. my $template=template($feedtype."page.tmpl", blind_cache => 1);
  268. $template->param(
  269. title => $page ne "index" ? pagetitle($page) : $config{wikiname},
  270. wikiname => $config{wikiname},
  271. pageurl => $url,
  272. content => $content,
  273. feeddesc => $feeddesc,
  274. feeddate => date_3339($lasttime),
  275. feedurl => $feedurl,
  276. version => $IkiWiki::version,
  277. );
  278. run_hooks(pagetemplate => sub {
  279. shift->(page => $page, destpage => $page,
  280. template => $template);
  281. });
  282. return $template->output;
  283. } #}}}
  284. sub pingurl (@) { #{{{
  285. return unless $config{pingurl} && %toping;
  286. eval q{require RPC::XML::Client};
  287. if ($@) {
  288. debug("RPC::XML::Client not found, not pinging");
  289. return;
  290. }
  291. # TODO: daemonize here so slow pings don't slow down wiki updates
  292. foreach my $page (keys %toping) {
  293. my $title=pagetitle(basename($page));
  294. my $url="$config{url}/".htmlpage($page);
  295. foreach my $pingurl (@{$config{pingurl}}) {
  296. debug("Pinging $pingurl for $page");
  297. eval {
  298. my $client = RPC::XML::Client->new($pingurl);
  299. my $req = RPC::XML::request->new('weblogUpdates.ping',
  300. $title, $url);
  301. my $res = $client->send_request($req);
  302. if (! ref $res) {
  303. debug("Did not receive response to ping");
  304. }
  305. my $r=$res->value;
  306. if (! exists $r->{flerror} || $r->{flerror}) {
  307. debug("Ping rejected: ".(exists $r->{message} ? $r->{message} : "[unknown reason]"));
  308. }
  309. };
  310. if ($@) {
  311. debug "Ping failed: $@";
  312. }
  313. }
  314. }
  315. } #}}}
  316. 1