]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/plugin/WikiBlog.php
Use substring search for Comment basepages, do not tokenize
[SourceForge/phpwiki.git] / lib / plugin / WikiBlog.php
1 <?php // -*-php-*-
2 rcs_id('$Id: WikiBlog.php,v 1.29 2007-09-12 19:56:21 rurban Exp $');
3 /*
4  Copyright 2002,2003,2007 $ThePhpWikiProgrammingTeam
5  
6  This file is part of PhpWiki.
7
8  PhpWiki is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation; either version 2 of the License, or
11  (at your option) any later version.
12
13  PhpWiki is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  GNU General Public License for more details.
17
18  You should have received a copy of the GNU General Public License
19  along with PhpWiki; if not, write to the Free Software
20  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22 /**
23  * @author: MichaelVanDam, major refactor by JeffDairiki (as AddComment)
24  * @author: Changed as baseclass to AddComment and WikiForum and EditToolbar integration by ReiniUrban.
25  */
26
27 require_once('lib/TextSearchQuery.php');
28
29 /**
30  * This plugin shows 'blogs' (comments/news) associated with a
31  * particular page and provides an input form for adding a new blog.
32  *
33  * USAGE:
34  * Add <?plugin WikiBlog ?> at your PersonalPage and BlogArchive and 
35  * BlogJournal will find the Blog entries automatically.
36  *
37  * Now it is also the base class for all attachable pagetypes: 
38  *    "wikiblog", "comment" and "wikiforum"
39  *
40  * HINTS/COMMENTS:
41  *
42  * To have the blog show up on a seperate page:
43  * On TopPage, use
44  *   <?plugin WikiBlog mode=add?>
45  * Create TopPage/Blog with this page as actionpage:
46  *   <?plugin WikiBlog pagename=TopPage mode=show?>
47  *
48  * To have the main ADMIN_USER Blog appear under Blog and not under WikiBlog/Blog
49  * or UserName/Blog as for other users blogs,
50  * define BLOG_DEFAULT_EMPTY_PREFIX=true 
51  * use the page Blog as basepage
52  * and user="" (as default for ADMIN or current user) and pagename="Blog" 
53  * in the various blog plugins (BlogArchives, BlogJournal)
54  *
55  * TODO:
56  *
57  * It also works as an action-page if you create a page called 'WikiBlog'
58  * containing this plugin.  This allows adding comments to any page
59  * by linking "PageName?action=WikiBlog".  Maybe a nice feature in
60  * lib/display.php would be to automatically check if there are
61  * blogs for the given page, then provide a link to them somewhere on
62  * the page.  Or maybe this just creates a huge mess...
63  *
64  * Maybe it would be a good idea to ENABLE blogging of only certain
65  * pages by setting metadata or something...?  If a page is non-bloggable
66  * the plugin is ignored (perhaps with a warning message).
67  *
68  * Should blogs be by default filtered out of RecentChanges et al???
69  *
70  * Think of better name for this module: Blog? WikiLog? WebLog? WikiDot?
71  *
72  * Have other 'styles' for the plugin?... e.g. 'quiet'.  Display only
73  * 'This page has 23 associated comments. Click here to view / add.'
74  *
75  * For admin user, put checkboxes beside comments to allow for bulk removal.
76  *
77  * Permissions for who can add blogs?  Display entry box only if
78  * user meets these requirements...?
79  *
80  * Code cleanup: break into functions, use templates (or at least remove CSS)
81  */
82
83 class WikiPlugin_WikiBlog
84 extends WikiPlugin
85 {
86     function getName () {
87         return _("WikiBlog");
88     }
89
90     function getDescription () {
91         return sprintf(_("Show and add blogs for %s"),'[pagename]');
92     }
93
94     function getVersion() {
95         return preg_replace("/[Revision: $]/", '',
96                             "\$Revision: 1.29 $");
97     }
98
99     // Arguments:
100     //  page - page which is blogged to (default current page)
101     //
102     //  order - 'normal' - place in chronological order
103     //        - 'reverse' - place in reverse chronological order
104     //
105     //  mode - 'show' - only show old blogs
106     //         'add' - only show entry box for new blog
107     //         'show,add' - show old blogs then entry box
108     //         'add,show' - show entry box followed by old blogs
109     //
110     // TODO:
111     //
112     // - arguments to allow selection of time range to display
113     // - arguments to display only XX blogs per page (can this 'paging'
114     //    co-exist with the wiki??  difficult)
115     // - arguments to allow comments outside this range to be
116     //    display as e.g. June 2002 archive, July 2002 archive, etc..
117     // - captions for 'show' and 'add' sections
118
119
120     function getDefaultArguments() {
121         return array('pagename'   => '[pagename]',
122                      'order'      => 'normal',
123                      'mode'       => 'show,add',
124                      'noheader'   => false
125                     );
126     }
127
128     function run($dbi, $argstr, &$request, $basepage) {
129         $args = $this->getArgs($argstr, $request);
130         // allow empty pagenames for ADMIN_USER style blogs: "Blog/day"
131         //if (!$args['pagename'])
132         //    return $this->error(_("No pagename specified"));
133
134         // Get our form args.
135         $blog = $request->getArg("edit");
136         $request->setArg("edit", false);
137             
138         if ($request->isPost() and !empty($blog['save'])) {
139             $this->add($request, $blog, 'wikiblog', $basepage); // noreturn
140         }
141         //TODO: preview
142
143         // Now we display previous comments and/or provide entry box
144         // for new comments
145         $html = HTML();
146         foreach (explode(',', $args['mode']) as $show) {
147             if (!empty($seen[$show]))
148                 continue;
149             $seen[$show] = 1;
150                 
151             switch ($show) {
152             case 'show':
153                 $html->pushContent($this->showAll($request, $args));
154                 break;
155             case 'add':
156                 $html->pushContent($this->showForm($request, $args));
157                 break;
158             default:
159                 return $this->error(sprintf("Bad mode ('%s')", $show));
160             }
161         }
162         return $html;
163     }
164
165     /**
166      * posted: required: pagename, content. optional: summary
167      */
168     function add (&$request, $posted, $type='wikiblog') {
169         // This is similar to editpage. Shouldn't we use just this for preview?
170         $parent = $posted['pagename'];
171         if (empty($parent)) {
172             $prefix = "";   // allow empty parent for default "Blog/day"
173             $parent = HOME_PAGE;
174         } elseif (($parent == 'Blog' or $parent == 'WikiBlog') and $type == 'wikiblog')
175         { // avoid Blog/Blog/2003-01-11/14:03:02+00:00
176             $prefix = "";
177             $parent = ''; // 'Blog';
178         } elseif ($parent == 'Comment' and $type == "comment")
179         {
180             $prefix = "";
181             $parent = ''; // 'Comment';
182         } elseif ($parent == 'Forum' and $type == "wikiforum")
183         {
184             $prefix = "";
185             $parent = ''; // 'Forum';
186         } else {
187             $prefix = $parent . SUBPAGE_SEPARATOR;
188         }
189         //$request->finish(fmt("No pagename specified for %s",$type));
190
191         $now = time();
192         $dbi = $request->getDbh();
193         $user = $request->getUser();
194         
195         /*
196          * Page^H^H^H^H Blog meta-data
197          * This method is reused for all attachable pagetypes: wikiblog, comment and wikiforum
198          *
199          * This is info that won't change for each revision.
200          * Nevertheless, it's now stored in the revision meta-data.
201          * Several reasons:
202          *  o It's more convenient to have all information required
203          *    to render a page revision in the revision meta-data.
204          *  o We can avoid a race condition, since version meta-data
205          *    updates are atomic with the version creation.
206          */
207
208         $blog_meta = array('ctime'      => $now,
209                            'creator'    => $user->getId(),
210                            'creator_id' => $user->getAuthenticatedId(),
211                            );
212         
213
214         // Version meta-data
215         $summary = trim($posted['summary']);
216         $version_meta = array('author'    => $blog_meta['creator'],
217                               'author_id' => $blog_meta['creator_id'],
218                               'markup'    => 2.0,   // assume new markup
219                               'summary'   => $summary ? $summary : _("New comment."),
220                               'mtime'     => $now,
221                               'pagetype'  => $type,
222                               $type       => $blog_meta,
223                               );
224         if ($type == 'comment')
225             unset($version_meta['summary']);
226
227         // Comment body.
228         $body = trim($posted['content']);
229
230         $saved = false;
231         if ($type != 'wikiforum')
232             $pagename = $this->_blogPrefix($type);
233         else {
234             $pagename = substr($summary,0,12);
235             if (empty($pagename)) {
236                 $saved = true;
237                 trigger_error("Empty title", E_USER_WARNING);
238             }
239         }
240         while (!$saved) {
241             // Generate the page name.  For now, we use the format:
242             //   Rootname/Blog/2003-01-11/14:03:02+00:00
243             // Rootname = $prefix, Blog = $pagename, 
244             // This gives us natural chronological order when sorted
245             // alphabetically. "Rootname/" is optional.
246             // Esp. if Rootname is named Blog, it is omitted.
247
248             $time = Iso8601DateTime();
249             // Check intermediate pages. If not existing they should RedirectTo the parent page.
250             // Maybe add the BlogArchives plugin instead for the new interim subpage.
251             $redirected = $prefix . $pagename;
252             if (!$dbi->isWikiPage($redirected)) {
253                 if (!$parent) $parent = HOME_PAGE;
254                 require_once('lib/loadsave.php');
255                 $pageinfo = array('pagename' => $redirected,
256                                   'content'  => '<?plugin RedirectTo page="'.$parent.'" ?>',
257                                   'pagedata' => array(),
258                                   'versiondata' => array('author' => $blog_meta['creator'], 'is_minor_edit' => 1),
259                                   );
260                 SavePage($request, $pageinfo, '', '');
261             }
262             $redirected = $prefix . $pagename . SUBPAGE_SEPARATOR . preg_replace("/T.*/", "", "$time");
263             if (!$dbi->isWikiPage($redirected)) {
264                 if (!$parent) $parent = HOME_PAGE;
265                 require_once('lib/loadsave.php');
266                 $pageinfo = array('pagename' => $redirected,
267                                   'content'  => '<?plugin RedirectTo page="'.$parent.'" ?>',
268                                   'pagedata' => array(),
269                                   'versiondata' => array('author' => $blog_meta['creator'], 'is_minor_edit' => 1),
270                                   );
271                 SavePage($request, $pageinfo, '', '');
272             }
273
274             $p = $dbi->getPage($prefix . $pagename . SUBPAGE_SEPARATOR 
275                                . str_replace("T", SUBPAGE_SEPARATOR, "$time"));
276             $pr = $p->getCurrentRevision();
277
278             // Version should be zero.  If not, page already exists
279             // so increment timestamp and try again.
280             if ($pr->getVersion() > 0) {
281                 $now++;
282                 continue;
283             }
284
285             // FIXME: there's a slight, but currently unimportant
286             // race condition here.  If someone else happens to
287             // have just created a blog with the same name,
288             // we'll have locked it before we discover that the name
289             // is taken.
290             /*
291              * FIXME:  For now all blogs are locked.  It would be
292              * nice to allow only the 'creator' to edit by default.
293              */
294             $p->set('locked', true); //lock by default
295             // TOOD: use ACL perms
296             $saved = $p->save($body, 1, $version_meta);
297
298             $now++;
299         }
300         
301         $dbi->touch();
302         $request->setArg("mode", "show");
303         $request->redirect($request->getURLtoSelf()); // noreturn
304
305         // FIXME: when submit a comment from preview mode,
306         // adds the comment properly but jumps to browse mode.
307         // Any way to jump back to preview mode???
308     }
309
310     function showAll (&$request, $args, $type="wikiblog") {
311         // FIXME: currently blogSearch uses WikiDB->titleSearch to
312         // get results, so results are in alphabetical order.
313         // When PageTypes fully implemented, could have smarter
314         // blogSearch implementation / naming scheme.
315         
316         $dbi = $request->getDbh();
317         $basepage = $args['pagename'];
318         $blogs = $this->findBlogs($dbi, $basepage, $type);
319         $html = HTML();
320         if ($blogs) {
321             // First reorder
322             usort($blogs, array("WikiPlugin_WikiBlog",
323                                 "cmp"));
324             if ($args['order'] == 'reverse')
325                 $blogs = array_reverse($blogs);
326
327             $name = $this->_blogPrefix($type);
328             if (!$args['noheader'])
329                 $html->pushContent(HTML::h4(array('class' => "$type-heading"),
330                                             fmt("%s on %s:", $name, WikiLink($basepage))));
331             foreach ($blogs as $rev) {
332                 if (!$rev->get($type)) {
333                     // Ack! this is an old-style blog with data ctime in page meta-data.
334                     $content = $this->_transformOldFormatBlog($rev, $type);
335                 }
336                 else {
337                     $content = $rev->getTransformedContent($type);
338                 }
339                 $html->pushContent($content);
340             }
341             
342         }
343         return $html;
344     }
345
346     // Subpage for the basepage. All Blogs/Forum/Comment entries are 
347     // Subpages under this pagename, to find them faster.
348     function _blogPrefix($type='wikiblog') {
349         if ($type == 'wikiblog')
350             $basepage = "Blog";
351         elseif ($type == 'comment')
352             $basepage = "Comment";
353         elseif ($type == 'wikiforum')
354             $basepage = substr($summary,0,12);
355             //$basepage = _("Message"); // FIXME: we use now the first 12 chars of the summary
356         return $basepage;
357     }
358
359     function _transformOldFormatBlog($rev, $type='wikiblog') {
360         $page = $rev->getPage();
361         $metadata = array();
362         foreach (array('ctime', 'creator', 'creator_id') as $key)
363             $metadata[$key] = $page->get($key);
364         if (empty($metadata) and $type != 'wikiblog')
365             $metadata[$key] = $page->get('wikiblog');
366         $meta = $rev->getMetaData();
367         $meta[$type] = $metadata;
368         return new TransformedText($page, $rev->getPackedContent(), $meta, $type);
369     }
370
371     function findBlogs (&$dbi, $basepage='', $type='wikiblog') {
372         $prefix = (empty($basepage) 
373                    ? "" 
374                    :  $basepage . SUBPAGE_SEPARATOR) . $this->_blogPrefix($type);
375         $pages = $dbi->titleSearch(new TextSearchQuery('"'.$prefix.'"', true, 'none'));
376
377         $blogs = array();
378         while ($page = $pages->next()) {
379             if (!string_starts_with($page->getName(), $prefix))
380                 continue;
381             $current = $page->getCurrentRevision();
382             if ($current->get('pagetype') == $type) {
383                 $blogs[] = $current;
384             }
385         }
386         return $blogs;
387     }
388
389     function cmp($a, $b) {
390         return(strcmp($a->get('mtime'),
391                       $b->get('mtime')));
392     }
393     
394     function showForm (&$request, $args, $template='blogform') {
395         // Show blog-entry form.
396         $args = array('PAGENAME' => $args['pagename'],
397                       'HIDDEN_INPUTS' => 
398                       HiddenInputs($request->getArgs()));
399         if (ENABLE_EDIT_TOOLBAR and !ENABLE_WYSIWYG and ($template != 'addcomment')) {
400             include_once("lib/EditToolbar.php");
401             $toolbar = new EditToolbar();
402             $args = array_merge($args, $toolbar->getTokens());
403         }
404         return new Template($template, $request, $args);
405     }
406
407     // "2004-12" => "December 2004"
408     function _monthTitle($month){
409         if (!$month) $month = strftime("%Y-%m");
410         //list($year,$mon) = explode("-",$month);
411         return strftime("%B %Y", strtotime($month."-01"));
412     }
413
414     // "UserName/Blog/2004-12-13/12:28:50+01:00" => array('month' => "2004-12", ...)
415     function _blog($rev_or_page) {
416         $pagename = $rev_or_page->getName();
417         if (preg_match("/^(.*Blog)\/(\d\d\d\d-\d\d)-(\d\d)\/(.*)/", $pagename, $m))
418             list(,$prefix,$month,$day,$time) = $m;
419         return array('pagename' => $pagename,
420                      // page (list pages per month) or revision (list months)?
421                      //'title' => isa($rev_or_page,'WikiDB_PageRevision') ? $rev_or_page->get('summary') : '',
422                      //'monthtitle' => $this->_monthTitle($month),
423                      'month'   => $month,
424                      'day'     => $day,
425                      'time'    => $time,
426                      'prefix'  => $prefix);
427     }
428
429     function _nonDefaultArgs($args) {
430         return array_diff_assoc($args, $this->getDefaultArguments());
431     }
432
433 };
434
435 // $Log: not supported by cvs2svn $
436 // Revision 1.28  2007/07/14 12:05:22  rurban
437 // create interim pages as minor for RecentChanges
438 //
439 // Revision 1.27  2007/06/01 06:39:07  rurban
440 // no edittoolbar for comments
441 //
442 // Revision 1.26  2007/02/17 14:17:03  rurban
443 // redirect page=""
444 //
445 // Revision 1.25  2007/01/20 11:23:37  rurban
446 // Fix forum parent. Fix addcomment header.
447 //
448 // Revision 1.24  2007/01/07 18:46:40  rurban
449 // Add EditToolbar support. Omit Blog/Blog/
450 //
451 // Revision 1.23  2005/10/29 09:06:37  rurban
452 // move common blog methods to WikiBlog
453 //
454 // Revision 1.22  2004/12/15 15:33:18  rurban
455 // Blogs => Blog
456 //
457 // Revision 1.21  2004/12/14 21:35:15  rurban
458 // support new BLOG_EMPTY_DEFAULT_PREFIX
459 //
460 // Revision 1.20  2004/12/13 13:22:57  rurban
461 // new BlogArchives plugin for the new blog theme. enable default box method
462 // for all plugins. Minor search improvement.
463 //
464 // Revision 1.19  2004/11/26 18:39:02  rurban
465 // new regex search parser and SQL backends (90% complete, glob and pcre backends missing)
466 //
467 // Revision 1.18  2004/05/14 20:55:04  rurban
468 // simplified RecentComments
469 //
470 // Revision 1.17  2004/05/14 17:33:12  rurban
471 // new plugin RecentChanges
472 //
473 // Revision 1.16  2004/04/19 18:27:46  rurban
474 // Prevent from some PHP5 warnings (ref args, no :: object init)
475 //   php5 runs now through, just one wrong XmlElement object init missing
476 // Removed unneccesary UpgradeUser lines
477 // Changed WikiLink to omit version if current (RecentChanges)
478 //
479 // Revision 1.15  2004/04/18 05:42:17  rurban
480 // more fixes for page="0"
481 // better WikiForum support
482 //
483 // Revision 1.14  2004/03/29 21:33:32  rurban
484 // possible fix for problem reported by Whit Blauvelt
485 //   Message-ID: <20040327211707.GA22374@free.transpect.com>
486 // create intermediate redirect subpages for blog/comment/forum
487 //
488 // Revision 1.13  2004/03/15 10:59:40  rurban
489 // fix also early attach type bug, attached as wikiblog pagetype
490 //
491 // Revision 1.11  2004/03/12 20:59:31  rurban
492 // important cookie fix by Konstantin Zadorozhny
493 // new editpage feature: JS_SEARCHREPLACE
494 //
495 // Revision 1.10  2004/03/12 17:32:41  rurban
496 // new base class PageType_attach as base class for WikiBlog, Comment, and WikiForum.
497 // new plugin AddComment, which is a WikiBlog with different pagetype and template,
498 //   based on WikiBlog. WikiForum comes later.
499 //
500 // Revision 1.9  2004/02/27 02:10:50  rurban
501 // Patch #891133 by pablom517
502 //   "WikiBlog Plugin now sorts logs correctly"
503 //
504 // Revision 1.8  2004/02/17 12:11:36  rurban
505 // added missing 4th basepage arg at plugin->run() to almost all plugins. This caused no harm so far, because it was silently dropped on normal usage. However on plugin internal ->run invocations it failed. (InterWikiSearch, IncludeSiteMap, ...)
506 //
507 // Revision 1.7  2003/11/17 16:23:55  carstenklapp
508 // Switched to Iso8601DateTime and more use of SUBPAGE_SEPARATOR. This
509 // allows plugin UnfoldSubpages (for example) to be added to page
510 // XxYy/Blog/ where desired, for a view of all Blogs in one day. This
511 // change should not break existing BLOGs, we are only checking for
512 // pagetype == 'wikiblog' now instead of relying on the subpage name to
513 // collect blog subpages. (** WARNING: Do not add UnfoldSubpages to both
514 // XxYy/Blog/ and XxYy/Blog/2003-11/16/ pages, due to recursion bug in
515 // UnfoldSubpages plugin.)
516 //
517 // Revision 1.6  2003/02/21 04:20:09  dairiki
518 // Big refactor. Formatting now done by the stuff in PageType.php.
519 // Split the template into two separate ones: one for the add comment form,
520 // one for comment display.
521 //
522 // Revision 1.5  2003/02/16 19:47:17  dairiki
523 // Update WikiDB timestamp when editing or deleting pages.
524 //
525 // Revision 1.4  2003/01/11 22:23:00  carstenklapp
526 // More refactoring to use templated output. Use page meta "summary" field.
527 //
528 // Revision 1.3  2003/01/06 02:29:02  carstenklapp
529 // New: use blog.tmpl template to format output. Some cosmetic
530 // issues, it mostly works but code still needs cleanup. Added
531 // getVersion() for PluginManager.
532 //
533
534 // For emacs users
535 // Local Variables:
536 // mode: php
537 // tab-width: 8
538 // c-basic-offset: 4
539 // c-hanging-comment-ender-p: nil
540 // indent-tabs-mode: nil
541 // End:
542 ?>