1Wiki::Toolkit(3) User Contributed Perl Documentation Wiki::Toolkit(3)
2
3
4
6 Wiki::Toolkit - A toolkit for building Wikis.
7
9 Helps you develop Wikis quickly by taking care of the boring bits for
10 you. You will still need to write some code - this isn't an instant
11 Wiki.
12
14 # Set up a wiki object with an SQLite storage backend, and an
15 # inverted index/DB_File search backend. This store/search
16 # combination can be used on systems with no access to an actual
17 # database server.
18 #
19 # The database should already exist; it can be created using
20 # the supplied wiki-toolkit-setupdb script.
21
22 my $store = Wiki::Toolkit::Store::SQLite->new(
23 dbname => "/home/wiki/store.db" );
24 my $indexdb = Search::InvertedIndex::DB::DB_File_SplitHash->new(
25 -map_name => "/home/wiki/indexes.db",
26 -lock_mode => "EX" );
27 my $search = Wiki::Toolkit::Search::SII->new(
28 indexdb => $indexdb );
29
30 my $wiki = Wiki::Toolkit->new( store => $store,
31 search => $search );
32
33 # Do all the CGI stuff.
34 my $q = CGI->new;
35 my $action = $q->param("action");
36 my $node = $q->param("node");
37
38 if ($action eq 'display') {
39 my $raw = $wiki->retrieve_node($node);
40 my $cooked = $wiki->format($raw);
41 print_page(node => $node,
42 content => $cooked);
43 } elsif ($action eq 'preview') {
44 my $submitted_content = $q->param("content");
45 my $preview_html = $wiki->format($submitted_content);
46 print_editform(node => $node,
47 content => $submitted_content,
48 preview => $preview_html);
49 } elsif ($action eq 'commit') {
50 my $submitted_content = $q->param("content");
51 my $cksum = $q->param("checksum");
52 my $written = $wiki->write_node($node, $submitted_content, $cksum);
53 if ($written) {
54 print_success($node);
55 } else {
56 handle_conflict($node, $submitted_content);
57 }
58 }
59
61 new
62 # Set up store, search and formatter objects.
63 my $store = Wiki::Toolkit::Store::SQLite->new(
64 dbname => "/home/wiki/store.db" );
65 my $indexdb = Search::InvertedIndex::DB::DB_File_SplitHash->new(
66 -map_name => "/home/wiki/indexes.db",
67 -lock_mode => "EX" );
68 my $search = Wiki::Toolkit::Search::SII->new(
69 indexdb => $indexdb );
70 my $formatter = My::HomeMade::Formatter->new;
71
72 my $wiki = Wiki::Toolkit->new(
73 store => $store, # mandatory
74 search => $search, # defaults to undef
75 formatter => $formatter # defaults to something suitable
76 );
77
78 "store" must be an object of type "Wiki::Toolkit::Store::*" and
79 "search" if supplied must be of type "Wiki::Toolkit::Search::*"
80 (though this isn't checked yet - FIXME). If "formatter" isn't
81 supplied, it defaults to an object of class
82 Wiki::Toolkit::Formatter::Default.
83
84 You can get a searchable Wiki up and running on a system without an
85 actual database server by using the SQLite storage backend with the
86 SII/DB_File search backend - cut and paste the lines above for a
87 quick start, and see Wiki::Toolkit::Store::SQLite,
88 Wiki::Toolkit::Search::SII, and
89 Search::InvertedIndex::DB::DB_File_SplitHash when you want to learn
90 the details.
91
92 "formatter" can be any object that behaves in the right way; this
93 essentially means that it needs to provide a "format" method which
94 takes in raw text and returns the formatted version. See
95 Wiki::Toolkit::Formatter::Default for a simple example. Note that
96 you can create a suitable object from a sub very quickly by using
97 Test::MockObject like so:
98
99 my $formatter = Test::MockObject->new();
100 $formatter->mock( 'format', sub { my ($self, $raw) = @_;
101 return uc( $raw );
102 } );
103
104 I'm not sure whether to put this in the module or not - it'd let
105 you just supply a sub instead of an object as the formatter, but it
106 feels wrong to be using a Test::* module in actual code.
107
108 retrieve_node
109 my $content = $wiki->retrieve_node($node);
110
111 # Or get additional data about the node as well.
112 my %node = $wiki->retrieve_node("HomePage");
113 print "Current Version: " . $node{version};
114
115 # Maybe we stored some of our own custom metadata too.
116 my $categories = $node{metadata}{category};
117 print "Categories: " . join(", ", @$categories);
118 print "Postcode: $node{metadata}{postcode}[0]";
119
120 # Or get an earlier version:
121 my %node = $wiki->retrieve_node( name => "HomePage",
122 version => 2,
123 );
124 print $node{content};
125
126 In scalar context, returns the current (raw Wiki language) contents
127 of the specified node. In list context, returns a hash containing
128 the contents of the node plus additional data:
129
130 last_modified
131 version
132 checksum
133 metadata - a reference to a hash containing any caller-supplied
134 metadata sent along the last time the node was written
135
136 The "node" parameter is mandatory. The "version" parameter is
137 optional and defaults to the newest version. If the node hasn't
138 been created yet, it is considered to exist but be empty (this
139 behaviour might change).
140
141 Note on metadata - each hash value is returned as an array ref,
142 even if that type of metadata only has one value.
143
144 moderate_node
145 my $ok = $wiki->moderate_node(name => $node, version => $version);
146
147 Marks the given version of the node as moderated. If this is the
148 highest moderated version, then update the node's contents to hold
149 this version.
150
151 set_node_moderation
152 my $ok = $wiki->set_node_moderation(name => $node, required => $required);
153
154 Sets if a node requires moderation or not. (Moderation is required
155 when $required is true).
156
157 When moderation is required, new versions of a node will sit about
158 until they're tagged as moderated, when they will become the new
159 node.
160
161 rename_node
162 my $ok = $wiki->rename_node(old_name => $old_name, new_name => $new_name, create_new_versions => $create_new_versions );
163
164 Renames a node, updating any references to it as required.
165
166 Uses the internal_links table to identify the nodes that link to
167 this one, and re-writes any wiki links in these to point to the new
168 name. If required, it can mark these updates to other pages as a
169 new version.
170
171 verify_checksum
172 my $ok = $wiki->verify_checksum($node, $checksum);
173
174 Sees whether your checksum is current for the given node. Returns
175 true if so, false if not.
176
177 NOTE: Be aware that when called directly and without locking, this
178 might not be accurate, since there is a small window between the
179 checking and the returning where the node might be changed, so
180 don't rely on it for safe commits; use "write_node" for that. It
181 can however be useful when previewing edits, for example.
182
183 list_backlinks
184 # List all nodes that link to the Home Page.
185 my @links = $wiki->list_backlinks( node => "Home Page" );
186
187 list_dangling_links
188 # List all nodes that have been linked to from other nodes but don't
189 # yet exist.
190 my @links = $wiki->list_dangling_links;
191
192 Each node is returned once only, regardless of how many other nodes
193 link to it.
194
195 list_all_nodes
196 my @nodes = $wiki->list_all_nodes;
197
198 Returns a list containing the name of every existing node. The
199 list won't be in any kind of order; do any sorting in your calling
200 script.
201
202 list_nodes_by_metadata
203 # All documentation nodes.
204 my @nodes = $wiki->list_nodes_by_metadata(
205 metadata_type => "category",
206 metadata_value => "documentation",
207 ignore_case => 1, # optional but recommended (see below)
208 );
209
210 # All pubs in Hammersmith.
211 my @pubs = $wiki->list_nodes_by_metadata(
212 metadata_type => "category",
213 metadata_value => "Pub",
214 );
215 my @hsm = $wiki->list_nodes_by_metadata(
216 metadata_type => "category",
217 metadata_value => "Hammersmith",
218 );
219 my @results = my_l33t_method_for_ANDing_arrays( \@pubs, \@hsm );
220
221 Returns a list containing the name of every node whose caller-
222 supplied metadata matches the criteria given in the parameters.
223
224 By default, the case-sensitivity of both "metadata_type" and
225 "metadata_value" depends on your database - if it will return rows
226 with an attribute value of "Pubs" when you asked for "pubs", or
227 not. If you supply a true value to the "ignore_case" parameter,
228 then you can be sure of its being case-insensitive. This is
229 recommended.
230
231 If you don't supply any criteria then you'll get an empty list.
232
233 This is a really really really simple way of finding things; if you
234 want to be more complicated then you'll need to call the method
235 multiple times and combine the results yourself, or write a plugin.
236
237 list_nodes_by_missing_metadata Returns nodes where either the metadata
238 doesn't exist, or is blank
239 Unlike list_nodes_by_metadata(), the metadata value is optional
240 (the metadata type is required).
241
242 # All nodes missing documentation
243 my @nodes = $store->list_nodes_by_missing_metadata(
244 metadata_type => "category",
245 metadata_value => "documentation",
246 ignore_case => 1, # optional but recommended (see below)
247 );
248
249 # All nodes which don't have a latitude defined
250 my @nodes = $store->list_nodes_by_missing_metadata(
251 metadata_type => "latitude"
252 );
253
254 list_recent_changes
255 This is documented in Wiki::Toolkit::Store::Database; see there for
256 parameters and return values. All parameters are passed through
257 directly to the store object, so, for example,
258
259 my @nodes = $wiki->list_recent_changes( days => 7 );
260
261 does exactly the same thing as
262
263 my @nodes = $wiki->store->list_recent_changes( days => 7 );
264
265 list_unmoderated_nodes
266 my @nodes = $wiki->list_unmoderated_nodes();
267 my @nodes = $wiki->list_unmoderated_nodes(
268 only_where_latest => 1
269 );
270
271 $nodes[0]->{'name'} # The name of the node
272 $nodes[0]->{'node_id'} # The id of the node
273 $nodes[0]->{'version'} # The version in need of moderation
274 $nodes[0]->{'moderated_version'} # The newest moderated version
275
276 Fetches details of all the node versions that require moderation (id,
277 name, version, and latest moderated version).
278
279 If only_where_latest is set, then only the latest version of nodes where
280 the latest version needs moderating are returned.
281 Otherwise, all node versions (including old ones, and possibly multiple
282 per node) are returned.
283
284 list_node_all_versions
285 my @versions = $wiki->list_node_all_versions("HomePage");
286
287 my @versions = $wiki->list_node_all_versions(
288 name => 'HomePage',
289 with_content => 1,
290 with_metadata => 0
291 );
292
293 Returns all the versions of a node, optionally including the
294 content and metadata, as an array of hashes (newest versions
295 first).
296
297 list_last_version_before List the last version of every node before a
298 given date. If no version existed before that date, will return undef
299 for version. Returns a hash of id, name, version and date
300 my @nv = $wiki->list_last_version_before('2007-01-02 10:34:11')
301 foreach my $data (@nv) {
302
303 }
304
305 node_exists
306 my $ok = $wiki->node_exists( "Wombat Defenestration" );
307
308 # or ignore case - optional but recommended
309 my $ok = $wiki->node_exists(
310 name => "monkey brains",
311 ignore_case => 1,
312 );
313
314 Returns true if the node has ever been created (even if it is
315 currently empty), and false otherwise.
316
317 By default, the case-sensitivity of "node_exists" depends on your
318 store backend. If you supply a true value to the "ignore_case"
319 parameter, then you can be sure of its being case-insensitive.
320 This is recommended.
321
322 node_required_moderation
323 my $needs = $wiki->node_required_moderation( "Wombat Defenestration" );
324
325 Returns true if the node exists and requires moderation, and false
326 otherwise.
327
328 delete_node
329 $wiki->delete_node( name => "Home Page", version => 15 );
330
331 "version" is optional. If it is supplied then only that version of
332 the node will be deleted. Otherwise the node and all its history
333 will be completely deleted.
334
335 Doesn't do any locking though - to fix? You probably don't want to
336 let anyone except Wiki admins call this. You may not want to use it
337 at all.
338
339 Croaks on error, silently does nothing if the node or version
340 doesn't exist, returns true if no error.
341
342 search_nodes
343 # Find all the nodes which contain the word 'expert'.
344 my %results = $wiki->search_nodes('expert');
345
346 Returns a (possibly empty) hash whose keys are the node names and
347 whose values are the scores in some kind of relevance-scoring
348 system I haven't entirely come up with yet. For OR searches, this
349 could initially be the number of terms that appear in the node,
350 perhaps.
351
352 Defaults to AND searches (if $and_or is not supplied, or is
353 anything other than "OR" or "or").
354
355 Searches are case-insensitive.
356
357 Croaks if you haven't defined a search backend.
358
359 supports_phrase_searches
360 if ( $wiki->supports_phrase_searches ) {
361 return $wiki->search_nodes( '"fox in socks"' );
362 }
363
364 Returns true if your chosen search backend supports phrase
365 searching, and false otherwise.
366
367 supports_fuzzy_searches
368 if ( $wiki->supports_fuzzy_searches ) {
369 return $wiki->fuzzy_title_match( 'Kings Cross, St Pancreas' );
370 }
371
372 Returns true if your chosen search backend supports fuzzy title
373 searching, and false otherwise.
374
375 fuzzy_title_match
376 NOTE: This section of the documentation assumes you are using a
377 search engine which supports fuzzy matching. (See above.) The
378 Wiki::Toolkit::Search::DBIxFTS backend in particular does not.
379
380 $wiki->write_node( "King's Cross St Pancras", "A station." );
381 my %matches = $wiki->fuzzy_title_match( "Kings Cross St. Pancras" );
382
383 Returns a (possibly empty) hash whose keys are the node names and
384 whose values are the scores in some kind of relevance-scoring
385 system I haven't entirely come up with yet.
386
387 Note that even if an exact match is found, any other similar enough
388 matches will also be returned. However, any exact match is
389 guaranteed to have the highest relevance score.
390
391 The matching is done against "canonicalised" forms of the search
392 string and the node titles in the database: stripping vowels,
393 repeated letters and non-word characters, and lowercasing.
394
395 Croaks if you haven't defined a search backend.
396
397 register_plugin
398 my $plugin = Wiki::Toolkit::Plugin::Foo->new;
399 $wiki->register_plugin( plugin => $plugin );
400
401 Registers the plugin with the wiki as one that needs to be informed
402 when we write a node.
403
404 If the plugin "isa" Wiki::Toolkit::Plugin, calls the methods set up
405 by that parent class to let it know about the backend store, search
406 and formatter objects.
407
408 Finally, calls the plugin class's "on_register" method, which
409 should be used to check tables are set up etc. Note that because of
410 the order these things are done in, "on_register" for
411 Wiki::Toolkit::Plugin subclasses can use the "datastore", "indexer"
412 and "formatter" methods as it needs to.
413
414 get_registered_plugins
415 my @plugins = $wiki->get_registered_plugins;
416
417 Returns an array of plugin objects.
418
419 write_node
420 my $written = $wiki->write_node($node, $content, $checksum, \%metadata, $requires_moderation);
421 if ($written) {
422 display_node($node);
423 } else {
424 handle_conflict();
425 }
426
427 Writes the specified content into the specified node in the backend
428 storage; and indexes/reindexes the node in the search indexes (if a
429 search is set up); calls "post_write" on any registered plugins.
430
431 Note that you can blank out a node without deleting it by passing
432 the empty string as $content, if you want to.
433
434 If you expect the node to already exist, you must supply a
435 checksum, and the node is write-locked until either your checksum
436 has been proved old, or your checksum has been accepted and your
437 change committed. If no checksum is supplied, and the node is
438 found to already exist and be nonempty, a conflict will be raised.
439
440 The first two parameters are mandatory, the others optional. If you
441 want to supply metadata but have no checksum (for a newly-created
442 node), supply a checksum of "undef".
443
444 The final parameter, $requires_moderation (which defaults to
445 false), is ignored except on new nodes. For existing nodes, use
446 $wiki->toggle_node_moderation to change the node moderation flag.
447
448 Returns the version of the updated node on success, 0 on conflict,
449 croaks on error.
450
451 Note on the metadata hashref: Any data in here that you wish to
452 access directly later must be a key-value pair in which the value
453 is either a scalar or a reference to an array of scalars. For
454 example:
455
456 $wiki->write_node( "Calthorpe Arms", "nice pub", $checksum,
457 { category => [ "Pubs", "Bloomsbury" ],
458 postcode => "WC1X 8JR" } );
459
460 # and later
461
462 my @nodes = $wiki->list_nodes_by_metadata(
463 metadata_type => "category",
464 metadata_value => "Pubs" );
465
466 For more advanced usage (passing data through to registered
467 plugins) you may if you wish pass key-value pairs in which the
468 value is a hashref or an array of hashrefs. The data in the
469 hashrefs will not be stored as metadata; it will be checksummed and
470 the checksum will be stored instead. Such data can only be accessed
471 via plugins.
472
473 format
474 my $cooked = $wiki->format($raw, $metadata);
475
476 Passed straight through to your chosen formatter object. You do not
477 have to supply the $metadata hashref, but if your formatter allows
478 node metadata to affect the rendering of the node then you will
479 want to.
480
481 store
482 my $store = $wiki->store;
483 my $dbname = eval { $wiki->store->dbname; }
484 or warn "Not a DB backend";
485
486 Returns the storage backend object.
487
488 search_obj
489 my $search_obj = $wiki->search_obj;
490
491 Returns the search backend object.
492
493 formatter
494 my $formatter = $wiki->formatter;
495
496 Returns the formatter backend object.
497
499 For a very quick Wiki startup without any of that icky programming
500 stuff, see Tom Insam's Wiki::Toolkit::Kwiki, an instant wiki based on
501 Wiki::Toolkit.
502
503 Or for the specialised application of a wiki about a city, see the
504 OpenGuides distribution.
505
506 Wiki::Toolkit allows you to use different formatting modules.
507 Text::WikiFormat might be useful for anyone wanting to write a custom
508 formatter. Existing formatters include:
509
510 • Wiki::Toolkit::Formatter::Default (in this distro)
511
512 • Wiki::Toolkit::Formatter::Pod
513
514 • Wiki::Toolkit::Formatter::UseMod
515
516 There's currently a choice of three storage backends - all database-
517 backed.
518
519 • Wiki::Toolkit::Store::MySQL (in this distro)
520
521 • Wiki::Toolkit::Store::Pg (in this distro)
522
523 • Wiki::Toolkit::Store::SQLite (in this distro)
524
525 • Wiki::Toolkit::Store::Database (parent class for the above - in
526 this distro)
527
528 A search backend is optional:
529
530 • Wiki::Toolkit::Search::DBIxFTS (in this distro, uses
531 DBIx::FullTextSearch)
532
533 • Wiki::Toolkit::Search::SII (in this distro, uses
534 Search::InvertedIndex)
535
536 Standalone plugins can also be written - currently they should only
537 read from the backend storage, but write access guidelines are coming
538 soon. Plugins written so far and available from CPAN:
539
540 • Wiki::Toolkit::Plugin::GeoCache
541
542 • Wiki::Toolkit::Plugin::Categoriser
543
544 • Wiki::Toolkit::Plugin::Locator::UK
545
546 • Wiki::Toolkit::Plugin::RSS::ModWiki
547
548 If writing a plugin you might want an easy way to run tests for it on
549 all possible backends:
550
551 • Wiki::Toolkit::TestConfig::Utilities (in this distro)
552
553 Other ways to implement Wikis in Perl include:
554
555 • CGI::Kwiki (an instant wiki)
556
557 • CGI::pWiki
558
559 • AxKit::XSP::Wiki
560
561 • Apache::MiniWiki
562
563 • UseModWiki <http://usemod.com>
564
565 • Chiq Chaq <http://chiqchaq.sourceforge.net/>
566
568 Kake Pugh (kake@earth.li) and the Wiki::Toolkit team (including Nick
569 Burch and Dominic Hargreaves)
570
572 Questions should go to cgi-wiki-dev@earth.li.
573
575 Copyright (C) 2002-2004 Kake Pugh. All Rights Reserved.
576 Copyright (C) 2006-2013 the Wiki::Toolkit team. All Rights Reserved.
577
578 This module is free software; you can redistribute it and/or modify it
579 under the same terms as Perl itself.
580
582 The developer web site and bug tracker is at
583 http://www.wiki-toolkit.org/ - please file bugs there as appropriate.
584
585 You could also subscribe to the dev list at
586 http://www.earth.li/cgi-bin/mailman/listinfo/cgi-wiki-dev
587
589 Bugs are documented at
590 <https://github.com/OpenGuides/Wiki-Toolkit/issues>
591
593 Various London.pm types helped out with code review, encouragement,
594 JFDI, style advice, code snippets, module recommendations, and so on;
595 far too many to name individually, but particularly Richard Clamp, Tony
596 Fisher, Mark Fowler, and Chris Ball.
597
598 blair christensen sent patches and gave me some good ideas. chromatic
599 continues to patiently apply my patches to Text::WikiFormat and help me
600 get it working in just the way I need. Paul Makepeace helped me add
601 support for connecting to non-local databases. Shevek has been prodding
602 me a lot lately. The OpenGuides team keep me well-supplied with
603 encouragement and bug reports.
604
605 Nick Burch has been leading the way with development leading up to the
606 release under the Wiki::Toolkit name.
607
609 I'm only obsessed with Wikis because of the Open Guide to London --
610 <http://openguides.org/london/>
611
612
613
614perl v5.32.1 2021-01-27 Wiki::Toolkit(3)