The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#
#   Copyright (C) 1998, 1999 Loic Dachary
#
#   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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. 
#
# 
# $Header: /cvsroot/Catalog/Catalog/lib/Catalog.pm,v 1.58 2000/01/27 18:08:37 loic Exp $
#
# 
package Catalog;
use vars qw(@ISA $head %default_templates
	    %datemap
	    $VERSION);
use strict;

use CGI;
use CGI::Carp;
use File::Basename;
use MD5;
use Catalog::implementation;
use Catalog::external;
use Catalog::path qw(path_simplify_component);
use Catalog::tools::sqledit;
use Catalog::tools::tools;

@ISA = qw(Catalog::tools::sqledit Catalog::implementation);

$VERSION = "1.02";
sub Version { $VERSION; }

#
# Yerk. Change to use Locale::Date or something
#
%datemap = (
	     'french' => {
		 'days' => {
		     'Monday' => 'Lundi',
		     'Tuesday' => 'Mardi',
		     'Wednesday' => 'Mercredi',
		     'Thursday' => 'Jeudi',
		     'Friday' => 'Vendredi',
		     'Saturday' => 'Samedi',
		     'Sunday' => 'Dimanche',
		 },
		 'months' => {
		     'January' => 'Janvier',
		     'February' => 'Février',
		     'March' => 'Mars',
		     'April' => 'Avril',
		     'May' => 'Mai',
		     'June' => 'Juin',
		     'July' => 'Juillet',
		     'August' => 'Ao&ucric;t',
		     'September' => 'Septembre',
		     'October' => 'Octobre',
		     'November' => 'Novembre',
		     'December' => 'Decembre',
		 },
	     },
	     );
$head = "
<body bgcolor=#ffffff>
";

#
# Built in templates
#
%default_templates
    = (
       'error.html' => template_parse('inline error',
"$head
<title>Error message</title>
<H1>Error:</H1>
<PRE>_MESSAGE_</PRE>
"),
       'calpha_root.html' => template_parse('inline calpha_root',
"$head
<title>Alphabetical Navigation</title>

<h3>Alphabetical Navigation</h3>

_A_ _B_ _C_ _D_ _E_ _F_ _G_ _H_ _I_ _J_ _K_ _L_ <p>
_M_ _N_ _O_ _P_ _Q_ _R_ _S_ _T_ _U_ _V_ _W_ _X_ <p>
_Y_ _Z_ _0_ _1_ _2_ _3_ _4_ _5_ _6_ _7_ _8_ _9_ <p>
"),
       'calpha.html' => template_parse('inline calpha',
"$head
<title>Alphabetical Navigation _LETTER_</title>

<h3>Alphabetical Navigation _LETTER_</h3>

<table border=1>
<!-- start entry -->
<tr>_DEFAULTROW_</tr>
<!-- end entry --> 
</table>

<!-- start pager -->
Number of pages _MAXPAGES_
<p>
_PAGES_
<!-- end pager -->

"),
       'csetup.html' => template_parse('inline csetup',
"$head
<center>
<h3>The catalog has not been setup</h3>
<p>
Shall I set it up for you ? It will create a table named <b>catalog</b>.
<p>
<form>
<input type=hidden name=context value=csetup_confirm>
<input type=submit value='Yes, setup a catalog'>
</form>

</center>
"),
       'ccontrol_panel.html' => template_parse('inline ccontrol_panel',
qq{$head
<title>Catalog Control Panel</title>

<h3>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Catalog Control Panel</h3>
<p>
<h3><font color=red>_COMMENT_</font></h3>
<p>

<table border=1 cellpadding=6>
<tr><td colspan=9 align=middle><b>Maintain Existing Catalogs</b></td></tr>
<!-- start catalogs -->
<tr>
 <td><b>_NAME_</b></td>
 <td><a href=_SCRIPT_?context=cbrowse&name=_NAME__ID_>browse</a></td>
 <td><a href=_SCRIPT_?context=_COUNT_&name=_NAME_>count</a></td>
 <td><a href=_SCRIPT_?context=cdestroy&name=_NAME_>destroy</a></td>
 <td><a href=_SCRIPT_?context=ccatalog_edit&name=_NAME_>configure</a></td>
 <!-- start theme -->
 <td><a href=_SCRIPT_?context=cedit&name=_NAME__ID_>edit</a></td>
 <td><a href=_SCRIPT_?context=cdump&name=_NAME_>dump</a></td>
 <td><a href=_SCRIPT_?context=cimport&name=_NAME_>load</a></td>
 <td><a href=_SCRIPT_?context=cexport&name=_NAME_>unload</a></td>
 <!-- end theme -->
</tr>
<!-- end catalogs -->
</table>
<p>
<table cellpadding=6><tr><td>
<a href=_SCRIPT_/>Simplified browsing on default catalog</a><br>
<a href=_SCRIPT_?context=cimport>Load catalog from file</a><br>
<a href=_SCRIPT_?context=ccontrol_panel>Redisplay control panel</a><br>
<a href=_SCRIPT_?context=cdemo>Create a demo table (urldemo)</a><br>
</td><td>
<a href=_HTMLPATH_/catalog_toc.html><img src=_HTMLPATH_/images/help.gif alt=Help border=0 align=middle></a>
</td></tr></table>
<form action=_SCRIPT_ method=POST>
<input type=hidden name=context value=cbuild>
Create _NAVIGATION_ catalog on table _TABLES_
<input type=submit value='Create it!'>
</form>
<p>
<table border=1 cellpadding=2>
<tr><td colspan=2 align=middle><b>&nbsp; Configuration Files &nbsp;</b></td></tr>
<tr><td>MySQL</td><td><a href=_SCRIPT_?context=confedit&file=mysql.conf>edit</a></td></tr>
<tr><td>CGI</td><td><a href=_SCRIPT_?context=confedit&file=cgi.conf>edit</a></td></tr>
<tr><td>Catalog</td><td><a href=_SCRIPT_?context=confedit&file=catalog.conf>edit</a></td></tr>
<tr><td>sqledit</td><td><a href=_SCRIPT_?context=confedit&file=sqledit.conf>edit</a></td></tr>
</table>
<p>
<pre></b><i>
<font size=-1>
Catalog-$VERSION <a href=http://www.senga.org/>http://www.senga.org</a>
Copyright 1998, 1999 Loic Dachary

    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, 675 Mass Ave, Cambridge, MA 02139, USA.
</font>
</i></pre>
}),
       'csearch.html' => template_parse('inline csearch',
"$head
<title>Search results for _TEXT_</title>

<!-- start simple -->
<center>
<form action=_SCRIPT_ method=POST>
_HIDDEN_
<input type=text size=40 name=text value='_TEXT-QUOTED_'>
<input type=submit value='search'><br>
_WHAT-MENU_
<a href=_SCRIPT_?context=csearch_form&querymode=advanced&_PARAMS_>Advanced Search</a>
<br>
Example: <b>+catalog senga -query</b>
</form>
</center>
<!-- end simple -->

<!-- start advanced -->
<form action=_SCRIPT_ method=POST>
_HIDDEN_
<b>Your search query</b>
<br>
<textarea name=text cols=50 rows=4 wrap>_TEXT-QUOTED_</textarea>
<br>
_WHAT-MENU_
_QUERYMODE-MENU_
<input type=submit value='search'>
<p>
Advanced search syntax examples:
<dl>
<dt> Boolean operators
<dd> <b>catalog and senga and not query or freeware near software</b>
<dt> Precedence
<dd> <b>catalog and ( query or freeware )</b>
<dt> Fields
<dd> <b>comment: ( catalog and query ) or url: edu</b>
</dl>
</form>
<!-- end advanced -->

<!-- start categories -->
<center>Categories matching <b>_TEXT_</b> (_COUNT_)</center>
<ul>
<!-- start entry -->
<li> <a href=_URL_>_PATHNAME_</a>
<!-- end entry -->
</ul>

<!-- start pager -->
Number of pages _MAXPAGES_
<p>
_PAGES_
<!-- end pager -->
<!-- end categories -->
<!-- start nocategories -->
<center>No category matches the search criterion</center>
<!-- end nocategories -->

<!-- start records -->

<center>Records matching <b>_TEXT_</b> (_COUNT_)</center>

<table border=1>
<!-- start entry -->

<!-- start category -->
<tr><td colspan=20><a href=_URL_>_PATHNAME_</a></td></tr>
<!-- end category -->

<tr>_DEFAULTROW_</tr>
<!-- end entry --> 
</table>

<!-- start pager -->
Number of pages _MAXPAGES_
<p>
_PAGES_
<!-- end pager -->
<!-- end records -->
<!-- start norecords -->
<center>No record matches the search criterion</center>
<!-- end norecords -->

"),
       'cedit.html' => template_parse('inline cedit',
"$head
<title>Edit category _CATEGORY_</title>

<center><h3><font color=red>_COMMENT_</font></h3></center>

<center>
<form action=_SCRIPT_ method=POST>
<input type=hidden name=name value=_NAME_>
<input type=hidden name=context value=csearch>
<input type=hidden name=mode value=_CONTEXT_>
<input type=text size=40 name=text value='_TEXT-QUOTED_'>
<input type=submit value='search'><br>
<a href=_SCRIPT_?_PARAMS_&mode=cedit&querymode=advanced&context=csearch_form>Advanced Search</a>
</form>
</center>

<h3>Edit category _CATEGORY_</h3> 
<a href='_CENTRYINSERT_'><img src=_HTMLPATH_/images/new.gif alt='Insert a new record and link it to this category' border=0></a>
<a href='_CENTRYSELECT_'><img src=_HTMLPATH_/images/link.gif alt='Link an existing record to this category' border=0></a>
<a href='_CATEGORYINSERT_'><img src=_HTMLPATH_/images/open.gif alt='Create a sub category' border=0></a>
<a href='_CATEGORYSYMLINK_'><img src=_HTMLPATH_/images/plus.gif alt='Create a symbolic link to another category' border=0></a>
<a href='_CONTROLPANEL_'><img src=_HTMLPATH_/images/control.gif alt='Control panel' border=0></a>
<p>
<p>
_PATH_
<p>

<!-- start categories -->
<h3>Sub categories</h3>
<table>
<!-- params 'style' => 'table', 'columns' => 2 -->
<!-- start row --> 
<tr>
<!-- start entry -->
<td> _LINKS_ <a href='_URL_'>_NAME_</a> (_COUNT_) </td>
<!-- end entry -->
</tr>
<!-- end row --> 
</table>
<!-- end categories -->
<p>

<h3>Records in this category</h3>
<!-- start entry -->
<table border=1><tr><td>_LINKS_</td> _DEFAULTROW_</tr></table>
<p>
<!-- end entry -->

<!-- start pager -->
Number of pages _MAXPAGES_
<p>
_PAGES_
<!-- end pager -->
"),
       'catalog_category_select.html' => template_parse('inline catalog_category_select',
"$head
<title>Select category _CATEGORY_</title>

<h3>Select category _CATEGORY_</h3> 
_PATH_
<!-- start symlink -->
<a href='_CATEGORYSYMLINK_'><img src=_HTMLPATH_/images/select.gif alt='Select this category as a symbolic link' border=0></a>
<!-- end symlink -->
<p>

<!-- start categories -->
<h3>Sub categories</h3>
<table>
<!-- params 'style' => 'table', 'columns' => 2 -->
<!-- start row --> 
<tr>
<!-- start entry -->
<td> <a href='_URL_'>_NAME_</a> (_COUNT_) </td>
<!-- end entry -->
</tr>
<!-- end row --> 
</table>
<!-- end categories -->
<p>
"),
       'centryremove_all.html' => template_parse('inline centryremove_all', "$head
<body bgcolor=#ffffff>

<center>

<h3>Confirm removal of record from  _TABLE_</h3>

<form action=_SCRIPT_ method=POST>
<input type=submit name=remove value=remove>
_HIDDEN_
</form>

</center>
"),
       'cbrowse_root.html' => template_parse('inline cbrowse_root',
"$head
<title>Root</title>

<center>
<form action=_SCRIPT_ method=POST>
<input type=hidden name=name value=_NAME_>
<input type=hidden name=context value=csearch>
<input type=hidden name=mode value=_CONTEXT_>
<input type=text size=40 name=text value='_TEXT-QUOTED_'>
<input type=submit value='search'><br>
<a href=_SCRIPT_?_PARAMS_&mode=cbrowse&querymode=advanced&context=csearch_form>Advanced Search</a>
</form>
</center>

<h3>Root</h3>

<!-- start categories -->
<h3>Sub categories</h3>
<ul>
<!-- start entry -->
<li> <a href='_URL_'>_NAME_</a> (_COUNT_)
<!-- end entry -->
</ul>
<!-- end categories -->
<p>
<!-- start entry -->
<p> <table border=1><tr>_DEFAULTROW_<tr></table>
<!-- end entry -->

<!-- start pager -->
Number of pages _MAXPAGES_
<p>
_PAGES_
<!-- end pager -->
"),
       'cbrowse.html' => template_parse('inline cbrowse',
"$head
<title>_CATEGORY_</title>

<center>
<form action=_SCRIPT_ method=POST>
<input type=hidden name=name value=_NAME_>
<input type=hidden name=context value=csearch>
<input type=hidden name=mode value=_CONTEXT_>
<input type=text size=40 name=text value='_TEXT-QUOTED_'>
<input type=submit value='search'><br>
<a href=_SCRIPT_?_PARAMS_&mode=cbrowse&querymode=advanced&context=csearch_form>Advanced Search</a>
</form>
</center>

<h3>_CATEGORY_</h3>
<p>
_PATH_
<p>

<!-- start categories -->
<h3>Sub categories</h3>
<ul>
<!-- start entry -->
<li> <a href='_URL_'>_NAME_</a> (_COUNT_)
<!-- end entry -->
</ul>
<!-- end categories -->
<p>
<!-- start entry -->
<p> <table border=1><tr>_DEFAULTROW_<tr></table>
<!-- end entry -->

<!-- start pager -->
Number of pages _MAXPAGES_
<p>
_PAGES_
<!-- end pager -->
"),
       'cdestroy.html' => template_parse('inline cdestroy', "$head
<body bgcolor=#ffffff>

<center>

<h3>Confirm removal of catalog _NAME_</h3>

<form action=_SCRIPT_ method=POST>
<input type=submit name=remove value=remove>
_HIDDEN_
</form>

</center>
"),
       'edit.html' => template_parse('inline catalog edit', "$head
<html>
<body bgcolor=#ffffff>
<title>Edit _FILE_</title>
<center><a href=_SCRIPT_?context=ccontrol_panel>Back to Catalog Control Panel</a></center>
<form action=_SCRIPT_ method=POST>
<input type=hidden name=context value=confedit>
<input type=hidden name=file value=_FILE_>
<input type=hidden name=rows value=_ROWS_>
<input type=hidden name=cols value=_COLS_>
<textarea name=text cols=_COLS_ rows=_ROWS_>_TEXT_</textarea>
<p>
<center>
<input type=submit name=action value=save>
<input type=submit name=action value=refresh>
</center>
<p>
_COMMENT_
</form>
</html>
"),
       'cdate_default.html' => template_parse('inline catalog cdate_default', "$head
<html>
<body bgcolor=#ffffff>
<title>Date catalog</title>
<!-- start years -->
  <a href=_YEARLINK_>_YEARFORMATED_</a> (_COUNT_)

  <blockquote>
  <!-- start months -->
    <!-- params format => '%M' -->
    <a href=_MONTHLINK_>_MONTHFORMATED_</a> (_COUNT_)

    <ul>
    <!-- start days -->
      <!-- params format => '%W, %d' -->
      <li> <a href=_DAYLINK_>_DAYFORMATED_</a> (_COUNT_)
    <!-- end days -->
    </ul>

  <!-- end months -->
  </blockquote>

<!-- end years -->

<!-- start records -->
Records
<!-- start entry -->
<p> <table border=1><tr>_DEFAULTROW_<tr></table>
<!-- end entry -->

<!-- start pager -->
Number of pages _MAXPAGES_
<p>
_PAGES_
<!-- end pager -->

<!-- end records -->
</html>
"),
       'catalog_category_insert.html' => template_parse('inline catalog_category_insert', "$head
<title>Create a sub category</title>

<h3>Create a sub category</h3>
<form action=_SCRIPT_ method=POST>
_HIDDEN_
<table>
<tr><td><b>Category name*</b></td><td><input type=text name=name></td></tr>
</table>
<input type=submit value='Create it!'>
</form>
"),
       'catalog_category_edit.html' => template_parse('inline catalog_category_edit', "$head
<title>Edit category _NAME_</title>

<h3>Edit category _NAME_</h3>
<form action=_SCRIPT_ method=POST>
<input type=submit name=update value=update>
_HIDDEN_
<table>
<tr><td><b>Category name*</b></td><td><input type=text name=name value='_NAME-QUOTED_'></td></tr>
<tr><td><b>Total records</b></td><td>_COUNT_</td></tr>
<tr><td><b>Rowid</b></td><td>_ROWID_</td></tr>
<tr><td><b>Created</b></td><td>_CREATED_</td></tr>
<tr><td><b>Last modified</b></td><td>_MODIFIED_</td></tr>
</table>
</form>
"),
       'catalog_theme_insert.html' => template_parse('inline catalog_theme_insert', "$head
<title>Create _NAVIGATION_ catalog on table _TABLENAME_</title>

<h3>Create _NAVIGATION_ catalog on table _TABLENAME_</h3>

<form action=_SCRIPT_ method=POST>
_HIDDEN_
<input type=hidden name=tablename value=_TABLENAME_>
<input type=hidden name=navigation value=_NAVIGATION_>
<table>
<tr><td><b>Catalog name*</b></td><td><input type=text name=name></td></tr>
<tr><td><b>ORDER BY</b></td><td><input type=text name=corder size=60></td></tr>
<tr><td><b>WHERE</b></td><td><input type=text name=cwhere size=60></td></tr>
<tr><td><b>Dump path</b></td><td><input type=text name=dump size=60></td></tr>
<tr><td><b>Dump location</b></td><td><input type=text name=dumplocation size=60></td></tr>
<tr><td><b>Options</b></td><td>_INFO-CHECKBOX_</td></tr>
</table>
<input type=submit value='Create it!'>

</form>
"),
       'catalog_theme_edit.html' => template_parse('inline catalog_theme_edit', "$head
<title>Edit _NAVIGATION_ catalog _NAME_</title>
<h3>Edit _NAVIGATION_ catalog _NAME_</h3>

_EDITCOMMENT_
<form action=_SCRIPT_ method=POST>
<input type=submit name=update value=update>
_HIDDEN_
<table>
<tr><td><b>Table name</b></td><td>_TABLENAME_</td></tr>
<tr><td><b>ORDER BY</b></td><td><input type=text name=corder size=60 value='_CORDER-QUOTED_'></td></tr>
<tr><td><b>WHERE</b></td><td><input type=text name=cwhere size=60 value='_CWHERE-QUOTED_'></td></tr>
<tr><td><b>Dump path</b></td><td><input type=text name=dump size=60 value='_DUMP-QUOTED_'></td></tr>
<tr><td><b>Dump location</b></td><td><input type=text name=dumplocation size=60 value='_DUMPLOCATION-QUOTED_'></td></tr>
<tr><td><b>Options</b></td><td>_INFO-CHECKBOX_</td></tr>
<tr><td><b>Created</b></td><td>_CREATED_</td></tr>
<tr><td><b>Last modified</b></td><td>_MODIFIED_</td></tr>
</table>
</form>
"),
       'catalog_alpha_insert.html' => template_parse('inline catalog_alpha_insert', "$head
<title>Create _NAVIGATION_ catalog on table _TABLENAME_</title>

<h3>Create _NAVIGATION_ catalog on table _TABLENAME_</h3>

<form action=_SCRIPT_ method=POST>
_HIDDEN_
<input type=hidden name=tablename value=_TABLENAME_>
<input type=hidden name=navigation value=_NAVIGATION_>
<table>
<tr><td><b>Catalog name*</b></td><td><input type=text name=name></td></tr>
<tr><td><b>Field name*</b></td><td><input type=text name=fieldname></td></tr>
<tr><td><b>ORDER BY</b></td><td><input type=text name=corder size=60></td></tr>
<tr><td><b>WHERE</b></td><td><input type=text name=cwhere size=60></td></tr>
<tr><td><b>Options</b></td><td>_INFO-CHECKBOX_</td></tr>
</table>
<input type=submit value='Create it!'>

</form>
"),
       'catalog_alpha_edit.html' => template_parse('inline catalog_alpha_edit', "$head
<title>Edit _NAVIGATION_ catalog _NAME_</title>
<h3>Edit _NAVIGATION_ catalog _NAME_</h3>

_EDITCOMMENT_
<form action=_SCRIPT_ method=POST>
<input type=submit name=update value=update>
_HIDDEN_
<table>
<tr><td><b>Table name</b></td><td>_TABLENAME_</td></tr>
<tr><td><b>Field name</b></td><td><input type=text name=fieldname value='_FIELDNAME_'></td></tr>
<tr><td><b>ORDER BY</b></td><td><input type=text name=corder size=60 value='_CORDER-QUOTED_'></td></tr>
<tr><td><b>WHERE</b></td><td><input type=text name=cwhere size=60 value='_CWHERE-QUOTED_'></td></tr>
<tr><td><b>Options</b></td><td>_INFO-CHECKBOX_</td></tr>
<tr><td><b>Last cache update</b></td><td><input type=text name=updated value='_UPDATED_'</td></tr>
<tr><td><b>Created</b></td><td>_CREATED_</td></tr>
<tr><td><b>Last modified</b></td><td>_MODIFIED_</td></tr>
</table>
</form>
"),
       'catalog_date_insert.html' => template_parse('inline catalog_date_insert', "$head
<title>Create _NAVIGATION_ catalog on table _TABLENAME_</title>

<h3>Create _NAVIGATION_ catalog on table _TABLENAME_</h3>

<form action=_SCRIPT_ method=POST>
_HIDDEN_
<input type=hidden name=tablename value=_TABLENAME_>
<input type=hidden name=navigation value=_NAVIGATION_>
<table>
<tr><td><b>Catalog name*</b></td><td><input type=text name=name></td></tr>
<tr><td><b>Field name*</b></td><td><input type=text name=fieldname></td></tr>
<tr><td><b>ORDER BY</b></td><td><input type=text name=corder size=60></td></tr>
<tr><td><b>WHERE</b></td><td><input type=text name=cwhere size=60></td></tr>
<tr><td><b>Options</b></td><td>_INFO-CHECKBOX_</td></tr>
</table>
<input type=submit value='Create it!'>

</form>
"),
       'catalog_date_edit.html' => template_parse('inline catalog_date_edit', "$head
<title>Edit _NAVIGATION_ catalog _NAME_</title>
<h3>Edit _NAVIGATION_ catalog _NAME_</h3>

_EDITCOMMENT_
<form action=_SCRIPT_ method=POST>
<input type=submit name=update value=update>
_HIDDEN_
<table>
<tr><td><b>Table name</b></td><td>_TABLENAME_</td></tr>
<tr><td><b>Field name</b></td><td><input type=text name=fieldname value='_FIELDNAME_'></td></tr>
<tr><td><b>ORDER BY</b></td><td><input type=text name=corder size=60 value='_CORDER-QUOTED_'></td></tr>
<tr><td><b>WHERE</b></td><td><input type=text name=cwhere size=60 value='_CWHERE-QUOTED_'></td></tr>
<tr><td><b>Options</b></td><td>_INFO-CHECKBOX_</td></tr>
<tr><td><b>Last cache update</b></td><td><input type=text name=updated value='_UPDATED_'</td></tr>
<tr><td><b>Created</b></td><td>_CREATED_</td></tr>
<tr><td><b>Last modified</b></td><td>_MODIFIED_</td></tr>
</table>
</form>
"),
       'cdump.html' => template_parse('inline cdump', "$head
<title>Dump _NAME_ catalog in HTML</title>

<h3>Dump _NAME_ catalog in HTML</h3>

<center><h3><font color=red>Warning! All files and subdirectories of the specified path will first be removed.</font></h3></center>
<form action=_SCRIPT_ method=POST>
_HIDDEN_
<table>
<tr><td><b>Full path name*</b></td><td><input type=text name=path size=50 value='_PATH_'></td></tr>
<tr><td><b>Location*</b></td><td><input type=text name=location size=50 value='_LOCATION_'></td></tr>
</table>
<input type=submit value='Dump it!'>

</form>
"),
       'cimport.html' => template_parse('inline cimport', "$head
<title>Load a thematic catalog</title>
<form action=_SCRIPT_ method=POST>
<input type=hidden name=context value=cimport_confirm>
<table>
<tr><td><b>Catalog name</b></td><td><input type=text name=name value=_NAME_></td></tr>
<tr><td><b>File path</b></td><td><input type=text name=file></td></tr>
</table>
<input type=submit value='Load it!'>
</form>
"),
       'cexport.html' => template_parse('inline cexport', "$head
<title>Unload a thematic catalog</title>
<form action=_SCRIPT_ method=POST>
<input type=hidden name=context value=cexport_confirm>
<input type=hidden name=name value=_NAME_>
<table>
<tr><td><b>Catalog name</b></td><td>_NAME_</td></tr>
<tr><td><b>File path</b></td><td><input type=text name=file></td></tr>
</table>
<input type=submit value='Unload it!'>
</form>
"),
       );


#
# Class specific initialization (called by new)
#
sub initialize {
    my($self) = @_;

    $self->Catalog::tools::sqledit::initialize();
    $self->Catalog::implementation::initialize();

    my($config) = config_load("catalog.conf");
    %$self = (%$self, %$config) if(defined($config));

    my($encoding) = $self->{'encoding'} || "ISO-8859-1";
    $self->{'encoding'} = $encoding;
    
    push(@{$self->{'params'}}, 'name', 'path');
    my($templates) = $self->{'templates'};
    %$templates = ( %$templates, %default_templates );
}


#
# Called after catalog edited/removed/created
#
sub cinfo_clear {
    my($self) = @_;

    delete($self->{'ccatalog'});
}

#
# HTML catalog setup step 1
#
sub csetup {
    my($self) = @_;

    my($template) = $self->template("csetup");
    return $self->stemplate_build($template);
}

#
# HTML catalog setup step 2
#
sub csetup_confirm {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    $self->csetup_api();

    return $self->ccontrol_panel(Catalog::tools::cgi->new({
	'context' => 'ccontrol_panel',
	'comment' => 'The catalog has been setup'
    }));
}

#
# HTML display control panel
#
sub ccontrol_panel {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();
    
    my($url) = $cgi->script_name();

    if(!defined($self->{'csetup'})) {
	return $self->csetup();
    }

    my($template) = $self->template("ccontrol_panel");

    my($template_catalogs) = $template->{'children'}->{'catalogs'};
    $self->cerror("missing catalogs part") if(!defined($template_catalogs));
    my($template_theme) = $template_catalogs->{'children'}->{'theme'};

    if($ccatalog) {
	my($html) = '';
	my(%navigation2function) = (
				    'alpha' => 'calpha_count',
				    'theme' => 'category_count',
				    'date' => 'cdate_count',
				    );
	my($assoc) = $template_catalogs->{'assoc'};
	my($name, $catalog);
	while(($name, $catalog) = each(%$ccatalog)) {
	    my($root) = $catalog->{'root'};
	    my($navigation) = $catalog->{'navigation'};
	    my($count) =  $navigation2function{$navigation};
	    my($id) = '';
	    if($navigation eq 'theme') {
		$id = "&id=$root";
		my($assoc) = $template_theme->{'assoc'};
		template_set($assoc, '_ID_', $id);
		template_set($assoc, '_COUNT_', $count);
		template_set($assoc, '_NAME_', $name);
		template_set($assoc, '_SCRIPT_', $url);
	    } else {
		$template_theme->{'skip'} = 1;
	    }
	    template_set($assoc, '_ID_', $id);
	    template_set($assoc, '_COUNT_', $count);
	    template_set($assoc, '_NAME_', $name);
	    $html .= $self->stemplate_build($template_catalogs);
	}

	$template_catalogs->{'html'} = $html;
    } else {
	$template_catalogs->{'skip'} = 'yes';
    }
    my($navigation) = $cgi->popup_menu(-name => 'navigation',
				       -values => ['theme', 'alpha', 'date'],
				       -default => 'theme',
				       -labels => {
					   'theme' => 'Thematical',
					   'alpha' => 'Alphabetical',
					   'date' => 'Chronological',
					   });
    template_set($template->{'assoc'}, '_NAVIGATION_', $navigation);
    my($tables) = $cgi->popup_menu(-name => 'table',
				   -values => $self->{'ctables'});
    template_set($template->{'assoc'}, '_TABLES_', $tables);
    template_set($template->{'assoc'}, '_COMMENT_', $cgi->param('comment'));
    return $self->stemplate_build($template);
}

#
# HTML import XML representation step 1
#
sub cimport {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($template) = $self->template("cimport");
    my($assoc) = $template->{'assoc'};

    template_set($assoc, '_NAME_', $cgi->param('name'));
    template_set($assoc, '_PATH_', $cgi->param('path'));
    template_set($assoc, '_COMMENT_', $cgi->param('comment'));
    
    return $self->stemplate_build($template);
}

#
# HTML import XML representation step 2
#
sub cimport_confirm {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($name) = $cgi->param('name');
    my($file) = $cgi->param('file');

    $self->cerror("no file specified") if(!defined($file));
    $self->cerror("$file is not a readable file") if(! -r $file);

    eval {
	$self->cimport_api($name, $file);
    };
    if($@) {
	my($error) = $@;
	print STDERR $error;
	$self->cerror("load failed, check logs");
    }

    return $self->ccontrol_panel(Catalog::tools::cgi->new({
	'context' => 'ccontrol_panel',
	'comment' => "The $name catalog was (re)loaded"
    }));
}

#
# HTML export XML representation step 1
#
sub cexport {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($template) = $self->template("cexport");
    my($assoc) = $template->{'assoc'};

    template_set($assoc, '_NAME_', $cgi->param('name'));
    
    return $self->stemplate_build($template);
}

#
# HTML export XML representation step 2
#
sub cexport_confirm {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($name) = $cgi->param('name');
    my($file) = $cgi->param('file');

    $self->cerror("no file specified") if(!defined($file));
    my($dir) = dirname($file);
    $self->cerror("directory $dir is not writable") if(! -w $dir);

    eval {
	$self->cexport_api($name, $file);
    };
    
    if($@) {
	my($error) = $@;
	print STDERR $error;
	$self->cerror("load failed, check logs");
    }

    return $self->ccontrol_panel(Catalog::tools::cgi->new({
	'context' => 'ccontrol_panel',
	'comment' => "The $name catalog was unloaded"
    }));
}

#
# HTML create demo data table
#
sub cdemo {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    $self->cdemo_api();

    return $self->ccontrol_panel(Catalog::tools::cgi->new({'context' => 'ccontrol_panel'}));
}

#
# HTML Create a symbolic link to a category
# param rowid not set : navigate catalog structure
# param rowid set : create symlink to rowid
#
sub categorysymlink {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();

    #
    # Show a form to create a new category symlink
    #
    my($rowid) = $cgi->param('rowid');
    my($name) = $cgi->param('name');
    my($root) = $ccatalog->{$name}->{'root'};
    if(!defined($rowid)) {
	my($params) = $self->params('context' => 'cedit',
				    'path' => undef,
				    'style' => 'catalog_category_select',
				    'id' => $root);
	eval {
	    $cgi = $cgi->fct_call($params,
				  'name' => 'select',
				  'args' => { },
				  'returned' => { },
				  );
	};
	if($@) {
	    my($error) = $@;
	    print STDERR $error;
	    $self->cerror("recursive cgi call failed, check logs");
	}
	return $self->cedit($cgi);
    } else {
	my($name) = $cgi->param('name');
	$cgi = $cgi->fct_return('context' => 'cedit');
	my($id) = $cgi->param('id');
	$self->categorysymlink_api($name, $id, $rowid);
	return $self->cedit($cgi);
    }
}

#
# HTML destroy a catalog step 1
#
sub cdestroy {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    $self->cinfo();

    my($template) = $self->template('cdestroy');
    my($assoc) = $template->{'assoc'};

    template_set($assoc, '_NAME_', $cgi->param('name'));
    template_set($assoc, '_HIDDEN_', $self->hidden('context' => 'cdestroy_confirm'));

    return $self->stemplate_build($template);
}

#
# HTML destroy a catalog step 2
#
sub cdestroy_confirm {
    my($self, $cgi) = @_;

    my($name) = $cgi->param('name');
    $self->cerror("no catalog name specified") if(!defined($name));

    $self->cdestroy_api($name);

    return $self->ccontrol_panel(Catalog::tools::cgi->new({'context' => 'ccontrol_panel'}));
}

#
# HTML display a category for editing
#
sub cedit {
    my($self, $cgi) = @_;

    my(%info) = ('mode' => 'cedit');

    return $self->cedit_1($cgi, \%info);
}

#
# HTML display a category specified by the pathname param only
# Map the pathname to id with catalog_path table.
#
sub pathcontext {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();
    my($pathname) = $cgi->param('pathname');
    my($params) = $self->{'pathcontext_params'};
    $cgi->reset_params($params);
    $cgi->param('page_length' => 1000000);
    my($name) = $cgi->param('name');
    $self->pathcheck($name);
    if(!defined($cgi->param('name'))) {
	$self->cerror("missing name from pathcontext_params in catalog.conf");
    }
    if(!exists($ccatalog->{$name})) {
	$self->cerror("the default catalog name, $name (from pathcontext_params in catalog.conf) is
not an existing catalog");
    }
    my($catalog) = $ccatalog->{$name};
    if($catalog->{'navigation'} ne 'theme') {
	$self->cerror("pathcontext only valid for theme catalog");
    }

    $cgi->param('context', 'cbrowse');
    $cgi->param('pathname', $pathname);
    my(%info) = ('mode' => 'cbrowse');
    return $self->cedit_1($cgi, \%info);
}

#
# HTML display a catalog (date,alpha,theme)
#
sub cbrowse {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();

    my($catalog) = $ccatalog->{$cgi->param('name')};

    if($catalog->{'navigation'} eq 'alpha') {
	return $self->calpha($cgi);
    } elsif($catalog->{'navigation'} eq 'date') {
	return $self->cdate($cgi);
    } else {
	my(%info) = ('mode' => 'cbrowse');
	return $self->cedit_1($cgi, \%info);
    } 

}

#
# HTML display an alpha catalog
#
sub calpha {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();

    my($name) = $cgi->param('name');
    my($catalog) = $ccatalog->{$name};
    my($letter) = $cgi->param('letter');
    if(!defined($letter)) {
	my($base) = "calpha_root";
	my($template) = $self->template($base);
	my($assoc) = $template->{'assoc'};

	my($day) = 24 * 60 * 60;
	if(($catalog->{'updated'} || 0) < time() - $day) {
	    $self->calpha_count_1_api($name);
	}
	my($rows) = $self->db()->exec_select("select letter,count from catalog_alpha_$name");
	$rows = { map { $_->{'letter'} => $_->{'count'} } @$rows };
	my($url) = $self->ccall();
	my($tag);
	foreach $tag (keys(%$assoc)) {
	    my($what);
	    ($letter, $what) = $tag =~ /_(.)(URL|COUNT|LETTER)_/;
	    ($letter) = $tag =~ /_(.)_/ if(!defined($what));
	    if(defined($letter)) {
		$letter = lc($letter);
		if(exists($rows->{$letter})) {
		    if(defined($what) && $what eq 'URL') {
			$assoc->{$tag} = $self->ccall('letter' => ($rows->{$letter} > 0 ? $letter : 'none'));
		    } elsif(defined($what) && $what eq 'COUNT') {
			$assoc->{$tag} = $rows->{$letter};
		    } elsif(defined($what) && $what eq 'LETTER') {
			$assoc->{$tag} = $rows->{$letter} > 0 ? $letter : "${letter}0";
		    } else {
			my($count) = $rows->{$letter};
			my($html);
			if($count > 0) {
			    $html = "<a href='$url&letter=$letter'>$letter</a> ($count)";
			} else {
			    $html = $letter;
			}
			$assoc->{$tag} = $html;
		    }
		} else {
		    $assoc->{$tag} = '';
		}
	    }
	}
	
	return $self->stemplate_build($template);
    } else {
	$self->cerror("no entries for this letter in $name") if($letter eq 'none');
	return $self->catalog_searcher("calpha", $catalog->{'tablename'}, { 'mode' => 'cbrowse'}, " $catalog->{'fieldname'} like '$letter\%' ", "letter");
	
    }
}

#
# HTML force recalculation of the cached data for alpha catalog
#
sub calpha_count {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($name) = $cgi->param('name');

    #
    # Force recalculation at first browsing action
    #
    $self->calpha_count_api($name);

    return $self->ccontrol_panel(Catalog::tools::cgi->new({'context' => 'ccontrol_panel'}));
}

#
# HTML display a date catalog
#
sub cdate {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();

    my($name) = $cgi->param('name');
    my($catalog) = $ccatalog->{$name};

    my(%intervals) = $self->cdate_cgi2intervals($cgi);

    my($day) = 24 * 60 * 60;
    if((($catalog->{'updated'} || 0) < time() - $day) ||
       ($self->db()->exec_select_one("select count(*) as count from catalog_date_$name")->{'count'} <= 0)) {
	$self->cdate_count_1_api($name);
    }

    #
    # Try to load the most specific template first, then backup to
    # cdate_default if none is found.
    #
    my($prefix) = $cgi->param('template') ? "cdate_" . $cgi->param('template') : "cdate_default";
    my($template) = template_load("$prefix.html", $self->{'templates'}, $cgi->param('style'));
    if(!defined($template)) {
	$template = $self->template("cdate_default");
    }

    #
    # Format the index
    #
    if(exists($template->{'children'}->{'years'})) {
	$self->cdate_index($template->{'children'}->{'years'}, $intervals{'index'},
			   {
			       'complement' => '0101',
			       'length' => 4,
			       'format' => '%Y',
			       'order' => 'tag desc',
			       'tag_ftag' => 'YEARFORMATED',
			       'tag_link' => 'YEARLINK',
			       'tag_date' => 'YEARDATE',
			       'next_period' => 'months',
			       },
			   {
			       'complement' => '01',
			       'length' => 6,
			       'format' => '%M %Y',
			       'order' => 'tag desc',
			       'tag_ftag' => 'MONTHFORMATED',
			       'tag_link' => 'MONTHLINK',
			       'tag_date' => 'MONTHDATE',
			       'next_period' => 'days',
			       },
			   {
			       'complement' => '',
			       'length' => 8,
			       'format' => '%d %M %Y',
			       'tag_ftag' => 'DAYFORMATED',
			       'tag_link' => 'DAYLINK',
			       'tag_date' => 'DAYDATE',
			       'order' => 'tag desc',
			       });
    }

    #
    # Format the record list
    #
    if(exists($template->{'children'}->{'records'})) {
	$self->cdate_records($template->{'children'}->{'records'}, $intervals{'records'});
    }
    
    return $self->stemplate_build($template);
}

#
# HTML display a data catalog calendar
#
sub cdate_index {
    my($self, $template, $interval, $spec, @specs) = @_;

#    warn("from = $interval->{'from'} => to = $interval->{'to'}");
    my($cgi) = $self->{'cgi'};
    my($name) = $cgi->param('name');
    my($catalog) = $self->cinfo()->{$name};
    my($url) = $cgi->script_name();

#    warn("cdate_index " . ostring($interval));

    $self->cdate_normalize($interval);

    my($length) = $spec->{'length'};
    my($format) = exists($template->{'params'}->{'format'}) ? $template->{'params'}->{'format'} : $spec->{'format'};
    my($order) = exists($template->{'params'}->{'order'}) ? $template->{'params'}->{'order'} : $spec->{'order'};
    my($language) = $template->{'params'}->{'language'};
#    warn($language);
    my($from) = substr($interval->{'from'}, 0, $length);
    my($to) = substr($interval->{'to'}, 0, $length);

    #
    # Recurse if template specified by user
    #
    my($next_period) = $spec->{'next_period'};
    if(defined($next_period) &&
       !exists($template->{'children'}->{$next_period})) {
	undef($next_period);
    }
    
    my($sql) = "select tag,date_format(concat(tag, '$spec->{'complement'}'), '$format') as ftag,count from catalog_date_$name where length(tag) = $length and tag $interval->{'from_op'} '$from' and tag $interval->{'to_op'} '$to' order by $order";
    my($rows) = $self->db()->exec_select($sql);
#    warn($sql);

    my($assoc) = $template->{'assoc'};

    my($html) = '';
    my($row);
    foreach $row (@$rows) {
	my($ftag) = $row->{'ftag'};
	if($language) {
	    $ftag =~ s/(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)/$datemap{$language}{'days'}{$1}/g;
	    $ftag =~ s/(January|February|March|April|May|June|July|August|September|October|November|December)/$datemap{$language}{'months'}{$1}/g;
	}
	template_set($assoc, "_$spec->{'tag_ftag'}_", $ftag);
	template_set($assoc, "_$spec->{'tag_date'}_", $row->{'tag'});
	template_set($assoc, "_$spec->{'tag_link'}_", $self->ccall('date' => $row->{'tag'}));
	template_set($assoc, "_COUNT_", $row->{'count'});

	if(defined($next_period)) {
	    my($interval_new) = $self->cdate_intersection($self->cdate_normalize({ 'date' => $row->{'tag'} }), $interval);
	    $self->cdate_index($template->{'children'}->{$next_period},
			       $interval_new,
			       @specs);
	}

	$html .= $self->stemplate_build($template);
    }

    $template->{'html'} = $html;
}

#
# HTML display a date catalog list of records
#
sub cdate_records {
    my($self, $template, $interval) = @_;
    my($cgi) = $self->{'cgi'};
    my($name) = $cgi->param('name');
    my($catalog) = $self->cinfo()->{$name};
    my($url) = $cgi->script_name();

    my($from) = $interval->{'from'};
    $from =~ s/^(\d\d\d\d)(\d\d)(\d\d)$/$1-$2-$3 00:00:00/;
    my($to) = $interval->{'to'};
    $to =~ s/^(\d\d\d\d)(\d\d)(\d\d)$/$1-$2-$3 23:59:59/;

    my($field) = $catalog->{'fieldname'};
    my($table) = $catalog->{'tablename'};
    my($where) = " ( $table.$field $interval->{'from_op'} '$from' and $table.$field $interval->{'to_op'} '$to' ) ";
    
    if(defined($catalog->{'cwhere'}) && $catalog->{'cwhere'} !~ /^\s*$/) {
	$where .= " and ($catalog->{'cwhere'})";
    }

#    warn($where);

    my(%context) = (
		    'context' => 'catalog entries',
		    'params' => [ 'from', 'to', 'date', 'index_from', 'index_to', 'index_date', 'records_from', 'records_to', 'records_date', 'template' ],
		    'url' => $cgi->script_name(),
		    'page' => scalar($cgi->param('page')),
		    'page_length' => scalar($cgi->param('page_length')),
		    'template' => $template,
		    'expand' => 'yes',
		    'table' => $table,
		    'where' => $where,
		    'order' => $catalog->{'corder'},
		    );

    return $self->searcher(\%context);
}

#
# HTML translate date cgi argument to interval structure
#
sub cdate_cgi2intervals {
    my($self, $cgi) = @_;

    my(%params) = (
		   'all' => {
		       'date' => scalar($cgi->param('date')),
		       'from' => scalar($cgi->param('from')),
		       'to' => scalar($cgi->param('to')),
		   },
		   'index' => {
		       'date' => scalar($cgi->param('index_date')),
		       'from' => scalar($cgi->param('index_from')),
		       'to' => scalar($cgi->param('index_to')),
		   },
		   'records' => {
		       'date' => scalar($cgi->param('records_date')),
		       'from' => scalar($cgi->param('records_from')),
		       'to' => scalar($cgi->param('records_to')),
		   },
		   );

    my(@params);
    if($cgi->param('date') ||
       $cgi->param('from') ||
       $cgi->param('to')) {
	push(@params, 'all');
    } else {
	push(@params, 'index', 'records');
    }

    #
    # Normalize arguments
    #
    my($param);
    foreach $param (@params) {
	$self->cdate_normalize($params{$param});
    }
    
    #
    # Expand so that index and records are filled
    #
    if($params[0] eq 'all') {
	$params{'index'} = $params{'records'} = $params{'all'};
    }

    return (
	     'index' => $params{'index'},
	     'records' => $params{'records'} );
}

#
# HTML force recalculation of the cached data for date catalog
#
sub cdate_count {
    return calpha_count(@_);
}

#
# HTML recalculate counts for each category
#
sub category_count {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($name) = $cgi->param('name');
    $self->category_count_api($name);
    return $self->ccontrol_panel(Catalog::tools::cgi->new({'context' => 'ccontrol_panel'}));
}

#
# HTML search categories and records (SQL style)
#
sub csearch {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();

    my($name) = $cgi->param('name');
    my($catalog) = $ccatalog->{$name};
    my($navigation) = $catalog->{'navigation'};

    $self->cerror("%s catalog cannot be searched", $navigation) if($navigation ne 'theme');

    my($what) = $cgi->param('what');
    my($mode) = $cgi->param('mode') || 'cbrowse';

    if($mode eq 'static') {
	$mode = 'pathcontext';
	$ENV{'SCRIPT_NAME'} = $catalog->{'dumplocation'};
    }
    
    my($template) = $self->template('csearch');

    my($select_category);
    $select_category = $self->csearch_param2select('categories') if(!defined($what) || $what eq 'categories' || $what eq '');
#    warn($select_category);
    my($select_records);
    $select_records = $self->csearch_param2select('records') if(!defined($what) || $what eq 'records' || $what eq '');
#    warn($select_records);

    my($results_count) = 0;
    #
    # Search in categories
    #
    my($template_categories) = $template->{'children'}->{'categories'};
    $self->cerror("missing categories part") if(!defined($template_categories));
    my($template_nocategories) = $template->{'children'}->{'nocategories'};
    $self->cerror("missing nocategories part") if(!defined($template_nocategories));
    if(defined($select_category)) {
	my($layout) = sub {
	    my($template, $subname, $result, $context) = @_;

	    my($assoc) = $template->{'assoc'};
	    my($row) = $result->{"catalog_category_$name"};
	    my(@result_key) = keys(%$row);
#	    warn("result_key = @result_key, $row->{'pathname'}");
	    
	    #
	    # Build forged tags
	    #
	    if(exists($assoc->{'_URL_'})) {
		my($url);
		if($mode eq 'pathcontext') {
		    my($pathname) = $row->{'pathname'};
		    $url = $cgi->script_name() . $pathname;
		} else {
		    my($path) = $row->{'path'};
		    $path =~ s/^,(.*),$/$1/o;
		    $url = $self->ccall('context' => $mode,
					'id' => $row->{'rowid'},
					'path' => $path);
		}
		$assoc->{'_URL_'} = $url;
	    }

	    $result->{"catalog_path_$name"} = {
		'pathname' => $row->{'pathname'},
	    };
	    
	    $self->searcher_layout_result($template, $subname, $result, $context);
	};
	my(%context) = (
			'params' => [ 'text', 'what', 'mode', 'querymode' ],
			'url' => $cgi->script_name(),
			'page' => scalar($cgi->param('page')),
			'page_length' => scalar($cgi->param('page_length')),
			'context' => 'catalog search categories',
			'template' => $template_categories,
			'accept_empty' => 'yes',
			'layout' => $layout,
			'table' => "catalog_category_$name",
			'sql' => $select_category,
			);

	eval {
	    $results_count = $self->searcher(\%context);
	};
	if($@) {
	    my($error) = $@;
	    $self->cerror("The query failed, check the syntax");
	}

	if($results_count <= 0) {
	    $template_categories->{'skip'} = 1;
	    #
	    # If searching in records, do not bark because nothing found,
	    # wait for records search to complete.
	    #
	    if(defined($select_records)) {
		$template_nocategories->{'skip'} = 1;
	    }
	} else {
	    $template_nocategories->{'skip'} = 1;
	    my($assoc) = $template_categories->{'assoc'};
	    template_set($assoc, '_COUNT_', $results_count);
	    template_set($assoc, '_TEXT_', $cgi->param('text'));
	    template_set($assoc, '_TEXT-QUOTED_', Catalog::tools::cgi::myescapeHTML($cgi->param('text')));
	}
    } else {
	$template_categories->{'skip'} = 1;
	$template_nocategories->{'skip'} = 1;
    }
    #
    # Search in records, if no category found
    #
    my($template_records) = $template->{'children'}->{'records'};
    $self->cerror("missing records part") if(!defined($template_records));
    my($template_norecords) = $template->{'children'}->{'norecords'};
    $self->cerror("missing norecords part") if(!defined($template_norecords));
    if($results_count <= 0 && defined($select_records)) {
	my($catalog) = $self->cinfo()->{$name};
	my($table) = $catalog->{'tablename'};
	my($current_pathname) = '';
	
	my($layout) = sub {
	    my($template, $subname, $result, $context) = @_;

	    my($assoc) = $template->{'assoc'};
	    my($row) = $result->{$table};
	    my(@result_key) = keys(%$row);
#	    warn("result_key = @result_key, $row->{'pathname'}");
	    
	    my($template_category) = $template->{'children'}->{'category'};
	    $self->cerror("missing records/category part") if(!defined($template_category));
	    if($row->{'pathname'} ne $current_pathname) {
		$current_pathname = $row->{'pathname'};
		my($assoc) = $template_category->{'assoc'};
		#
		# Build forged tags
		#
		if(exists($assoc->{'_URL_'})) {
		    my($url);
		    if($mode eq 'pathcontext') {
			my($pathname) = $row->{'pathname'};
			$url = $cgi->script_name() . $pathname;
		    } else {
			my($path) = $row->{'path'};
			$path =~ s/^,(.*),$/$1/o;
			$url = $self->ccall('context' => $mode,
					    'id' => $row->{'id'},
					    'path' => $path);
		    }
		    $assoc->{'_URL_'} = $url;
		}

		$self->row2assoc("catalog_path_$name", $row, $assoc);
		$template_category->{'skip'} = 0;
	    } else {
		$template_category->{'skip'} = 1;
	    }
	    
	    $self->searcher_layout_result($template, $subname, $result, $context);
	};
	my(%context) = (
			'params' => [ 'text', 'what', 'mode', 'querymode' ],
			'url' => $cgi->script_name(),
			'page' => scalar($cgi->param('page')),
			'page_length' => scalar($cgi->param('page_length')),
			'context' => 'catalog search records',
			'template' => $template_records,
			'accept_empty' => 'yes',
			'layout' => $layout,
			'table' => $table,
			'sql' => $select_records,
			);

	eval {
	    $results_count = $self->searcher(\%context);
	};
	if($@) {
	    my($error) = $@;
	    $self->cerror("The query failed, check the syntax");
	}

	if($results_count <= 0) {
	    $template_records->{'skip'} = 1;
	} else {
	    $template_norecords->{'skip'} = 1;
	    my($assoc) = $template_records->{'assoc'};
	    template_set($assoc, '_COUNT_', $results_count);
	    template_set($assoc, '_TEXT_', $cgi->param('text'));
	    template_set($assoc, '_TEXT-QUOTED_', Catalog::tools::cgi::myescapeHTML($cgi->param('text')));

	}
    } else {
	$template_records->{'skip'} = 1;
	$template_norecords->{'skip'} = 1;
    }

    $self->csearch_fill_form($cgi, $template, $results_count);

    my($assoc) = $template->{'assoc'};
    template_set($assoc, '_COUNT_', $results_count);
    template_set($assoc, '_TEXT_', $cgi->param('text'));
    template_set($assoc, '_TEXT-QUOTED_', Catalog::tools::cgi::myescapeHTML($cgi->param('text')));

    return $self->stemplate_build($template);
}

sub csearch_form {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();

    my($name) = $cgi->param('name');
    my($catalog) = $ccatalog->{$name};
    my($navigation) = $catalog->{'navigation'};

    $self->cerror("%s catalog cannot be searched", $navigation) if($navigation ne 'theme');

    my($template) = $self->template('csearch');

    $template->{'children'}->{'records'}->{'skip'} = 1;
    $template->{'children'}->{'norecords'}->{'skip'} = 1;
    $template->{'children'}->{'categories'}->{'skip'} = 1;
    $template->{'children'}->{'nocategories'}->{'skip'} = 1;

    $self->csearch_fill_form($cgi, $template, 0);

    return $self->stemplate_build($template);
}

sub csearch_fill_form {
    my($self, $cgi, $template, $results_count) = @_;

    my($what) = $cgi->param('what');

    my($what_menu) = $cgi->popup_menu(-name => 'what',
				      -values => ['', 'categories', 'records'],
				      -default => '',
				      -labels => {
					  '' => 'Category Names and Records',
					  'categories' => 'Category Names only',
					  'records' => 'Records only',
				      });
    my($querymode_menu) = $cgi->popup_menu(-name => 'querymode',
					   -values => ['simple', 'advanced'],
					   -default => 'simple',
					   -labels => {
					       'simple' => 'Simple Syntax',
					       'advanced' => 'Advanced Syntax',
					   });
    my($querymode) = $cgi->param('querymode') || 'simple';
    my($notquerymode) = $querymode eq 'simple' ? 'advanced' : 'simple';
    my(%has_template) = (
			 'simple' => exists($template->{'children'}->{'simple'}),
			 'advanced' => exists($template->{'children'}->{'advanced'})
			 );
    my($template_form) = $has_template{$querymode} ? $template->{'children'}->{$querymode} : $template;
    $template->{'children'}->{$notquerymode}->{'skip'} = 1 if($has_template{$notquerymode});

    my($url) = $cgi->script_name();
    
    my($assoc) = $template_form->{'assoc'};
    template_set($assoc, '_HIDDEN_',
		 $self->hidden('mode' => scalar($cgi->param('mode')),
			       'context' => 'csearch'));
    template_set($assoc, '_PARAMS_',
		 $self->params('mode' => scalar($cgi->param('mode')),
			       'context' => undef));
    template_set($assoc, '_SCRIPT_', $url);
    template_set($assoc, '_WHAT_', $what);
    template_set($assoc, '_WHAT-MENU_', $what_menu);
    template_set($assoc, '_QUERYMODE_', $querymode);
    template_set($assoc, '_QUERYMODE-MENU_', $querymode_menu);
    template_set($assoc, '_COUNT_', $results_count);
    template_set($assoc, '_TEXT_', $cgi->param('text'));
    template_set($assoc, '_TEXT-QUOTED_', Catalog::tools::cgi::myescapeHTML($cgi->param('text')));
}

#
# HTML translate cgi parameters to select order for search
#
sub csearch_param2select {
    my($self, $what) = @_;
    my($cgi) = $self->{'cgi'};
    my($name) = $cgi->param('name');
    my($words) = $cgi->param('text');
    my($querymode) = $cgi->param('querymode');
    #
    # No search if nothing specified
    #
    return undef if(!defined($words) && $words =~ /^\s*$/o);

    if($what eq 'categories') {
	return $self->csearch_param2select_categories($name, $words, $querymode);
    } else {
	return $self->csearch_param2select_records($name, $words, $querymode);
    }
}

#
# HTML translate cgi parameters to select order for searching records
#
sub csearch_param2select_records {
    my($self, $name, $words, $querymode) = @_;

    my($catalog) = $self->cinfo()->{$name};
    my($table) = $catalog->{'tablename'};
    my($table_info) = $self->db()->info_table($table);
    my($primary_key) = $table_info->{'_primary_'};
    my($spec) = $self->{'search'}->{$name};

    my($fields);
    if(defined($spec) && exists($spec->{'searched'})) {
	$fields = $spec->{'searched'};
	$self->cerror("no searched fields specified in catalog.conf") if(!$fields);
    } else {
	my(@fields);
	my($field, $info);
	while(($field, $info) = each(%$table_info)) {
	    push(@fields, $field) if(ref($info) eq 'HASH' && $info->{'type'} eq 'char');
	}
	$self->cerror("no char fields in $table") if(!@fields);
	$fields = join(',', @fields);
    }

    my($fields_extracted) = '';
    if(defined($spec) && exists($spec->{'extracted'})) {
	$fields_extracted = $spec->{'extracted'};
    } else {
	$fields_extracted = "$table.*";
    }
    $self->cerror("no extracted fields for $table") if($fields_extracted =~ /^\s*$/);

    my($order) = '';
    if(defined($spec) && exists($spec->{'order'})) {
	$order = ", $spec->{'order'}";
    }

    my($select) = "select $fields_extracted,c.pathname,c.path,c.id from $table, catalog_entry2category_$name as b, catalog_path_$name as c where __WHERE__ and $table.$primary_key = b.row and b.category = c.id order by c.pathname asc $order";

    my($result);
    eval {
	( $result ) = $self->csearch_parse($words, $querymode, $fields, $select);
    };
    if($@) {
	warn("$@");
	$self->cerror("The syntax of the <b>$words</b> query is incorrect");
    }

    return $result;
}

#
# HTML translate cgi parameters to select order for searching categories
#
sub csearch_param2select_categories {
    my($self, $name, $words, $querymode) = @_;

    my($select) = "select a.rowid,a.name,a.info,b.path,b.pathname from catalog_category_$name as a,catalog_path_$name as b where a.rowid = b.id and __WHERE__ ";

    my($result);
    eval {
	( $result ) = $self->csearch_parse($words, $querymode, 'a.name', $select);
    };
    if($@) {
	warn("$@");
	$self->cerror("The syntax of the <b>$words</b> query is incorrect");
    }

    return $result;
}

#
# HTML dump theme catalog in file tree step 1
#
sub cdump {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();

    my($name) = $cgi->param('name');
    my($catalog) = $ccatalog->{$name};
    my($navigation) = $catalog->{'navigation'};
    $self->cerror("%s catalog cannot be dumped", $navigation) if($navigation ne 'theme');

    my($template) = $self->template('cdump');
    my($assoc) = $template->{'assoc'};

    template_set($assoc, '_PATH_', Catalog::tools::cgi::myescapeHTML($catalog->{'dump'}));
    template_set($assoc, '_LOCATION_', Catalog::tools::cgi::myescapeHTML($catalog->{'dumplocation'}));
    template_set($assoc, '_NAME_', $name);
    template_set($assoc, '_HIDDEN_', $self->hidden('context' => 'cdump_confirm'));

    return $self->stemplate_build($template);
}

#
# HTML dump theme catalog in file tree step 2
#
sub cdump_confirm {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($path) = $cgi->param('path');
    $self->cerror("you must specify a path") if(!$path);
    my($location) = $cgi->param('location');
    $self->cerror("you must specify a location") if(!$location);
    my($name) = $cgi->param('name');

    my($script) = $ENV{'SCRIPT_NAME'};
    $ENV{'SCRIPT_NAME'} = $location;

    $self->cdump_api($name, $path, sub { $self->cdump_category_layout(@_) });
    
    if(defined($script)) {
	$ENV{'SCRIPT_NAME'} = $script;
    } else {
	delete($ENV{'SCRIPT_NAME'});
    }

    $self->db()->update("catalog", "name = '$name'",
			'dump' => $path,
			'dumplocation' => $location);

    return $self->ccontrol_panel(Catalog::tools::cgi->new({
	'context' => 'ccontrol_panel',
	'comment' => 'The catalog has been dumped'
    }));
}

#
# HTML dump theme catalog layout of a category
#
sub cdump_category_layout {
    my($self, $pathname) = @_;

    $self->gauge();
    return $self->pathcontext(Catalog::tools::cgi->new({
	'context' => 'pathcontext',
	'pathname' => $pathname,
    }));
}

#
# HTML display category for editing or browsing (subroutine)
#
sub cedit_1 {
    my($self, $cgi, $info) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();

    my($name) = $cgi->param('name');
    my($catalog) = $ccatalog->{$name}
	    || $self->cerror("Can't edit unknown catalog '%s'", $name);
    my($navigation) = $catalog->{'navigation'};
    $self->cerror("A %s catalog cannot be edited", $navigation) if($navigation ne 'theme');
    $self->pathcheck($name);
    my($opath) = $self->cgi2path();
    my($id);
    eval { $id = $opath->id(); };
    $self->cerror("The category path was not found") if(!defined($id));

    my($category) = $self->db()->exec_select_one("select * from catalog_category_$name where rowid = $id");

    #
    # Load template
    #
    my($base) = $info->{'mode'};
    if(defined($category->{'info'}) && $category->{'info'} =~ /\broot\b/ && $base eq 'cbrowse') {
	$base .= "_root";
    }
    my($template) = $self->template($base);
    my($assoc) = $template->{'assoc'};

    #
    # Set top level tags
    #
    #
    # Comment
    #
    template_set($assoc, '_COMMENT_', $cgi->param('comment'));
    #
    # Path substitution
    #
    $opath->ptemplate_set($template);
    #
    # Hidden parameters
    #
    template_set($assoc, '_HIDDEN_', $self->hidden('path' => undef,
						   'context' => undef));
    template_set($assoc, '_PARAMS_', $self->params('path' => undef,
						   'context' => undef));
    #
    # Category name
    #
    template_set($assoc, '_CATEGORY_', $category->{'name'});
    template_set($assoc, '_CATEGORY-CODED_', CGI::escape($category->{'name'}));
    #
    # Catalog name
    #
    template_set($assoc, '_NAME_', $name);
    #
    # Current category id
    #
    template_set($assoc, '_CATEGORYID_', $id);
    #
    # Context
    #
    template_set($assoc, '_CONTEXT_',
	$opath->fashion() eq 'intuitive' ? 'pathcontext' : $cgi->param('context'));

    if($info->{'mode'} eq 'cedit') {
	template_set($assoc, '_CONTROLPANEL_', $self->ccall('context' => 'ccontrol_panel',
							    'id' => undef,
							    'name' => undef,
							    'path' => undef));
	my($context);
	foreach $context ('centryinsert', 'centryselect', 'categoryinsert', 'categorysymlink') {
	    my($call) = $self->ccall('context' => $context, 'id' => $id);
	    template_set($assoc, "_" . uc($context) . "_", $call);
	}
	#
	# Symbolic link selection 
	#
	if(exists($template->{'children'}->{'symlink'})) {
	    my($template) = $template->{'children'}->{'symlink'};
	    if(defined($cgi->fct_name()) && $cgi->fct_name() eq 'select') {
		my($assoc) = $template->{'assoc'};
		my($call) = $self->ccall('context' => 'categorysymlink',
					 'rowid' => $id);
		template_set($assoc, "_HTMLPATH_", $self->{'htmlpath'});
		template_set($assoc, "_CATEGORYSYMLINK_", $call);
	    } else {
		$template->{'skip'} = 1;
	    }
	}
    }

    #
    # Show entries
    #
    if(exists($template->{'children'}->{'entry'}) ||
       exists($template->{'children'}->{'row'})) {
	my($table) = $self->cinfo()->{$name}->{'tablename'};
	my($row_ids) = $self->select_entry_rows($name, $id); # can subclass to filter
	if(@$row_ids) {
	    $self->cedit_searcher($template, $table, $info, join(',', @$row_ids));
	} else {
	    $template->{'children'}->{'entry'}->{'skip'} = 1;
	    $template->{'children'}->{'row'}->{'skip'} = 1;
	    $template->{'children'}->{'pager'}->{'skip'} = 1;
	}
    }

    #
    # Show sub categories
    #
    $self->category_searcher($template->{'children'}->{'categories'}, $id, $info, $category);

    return $self->stemplate_build($template);
}

#
# HTML display sub-categories of a category for editing/display
#
sub category_searcher {
    my($self, $template, $id, $info, $current_category, $recursion) = @_;
    my($cgi) = $self->{'cgi'};

    #
    # Define search domain
    #
    my($name) = $cgi->param('name');
    my($catalog) = $self->cinfo()->{$name};
    my($category) = "catalog_category_$name";
    my($path) = "catalog_path_$name";
    my($category2category) = "catalog_category2category_$name";

    template_set($template->{'assoc'}, '_CATEGORY_', $current_category->{'name'});
    template_set($template->{'assoc'}, '_CATEGORY-CODED_', CGI::escape($current_category->{'name'}));

    my($where) = '';
    if(defined($catalog->{'info'}) &&
       $catalog->{'info'} =~ /hideempty/ && $info->{'mode'} ne 'cedit') {
	$where = " and a.count > 0 ";
    }

    #
    # Only display categories explicitely marked to be displayed
    #
    $where .= " and find_in_set('displaygrandchild',a.info)"
	if($recursion);

    my($sql) = qq{
	select a.rowid, a.name, a.count, b.info, c.pathname
	from $category as a, $category2category as b, $path as c
	where a.rowid = b.down and b.down = c.id and b.up = $id 
		$where
	order by a.name
    };
    my($layout);
    $layout = sub {
	my($template, $name, $result, $context) = @_;

	my($assoc) = $template->{'assoc'};
	my($row) = $result->{$category};
	my($issymlink) = defined($row->{'info'}) && $row->{'info'} =~ /symlink/;

        if(exists $template->{'children'}{'categories'}) {
	    my($subid) = $result->{$category}{'rowid'};
	    my($cur_category) = $self->db()->exec_select_one("select * from $category where rowid = $subid");

	    my $count = $self->category_searcher($template->{'children'}{'categories'}, $subid, $info, $cur_category, 'recursion');
	    if($count) {
		$template->{'children'}{'categories'}{'skip'} = 0
	    }
	}

	#
	# Build forged tags
	#
	if(exists($assoc->{'_URL_'})) {
	    my($url);
	    if($cgi->param('pathname')) {
		my($pathname) = $row->{'pathname'};
		$url = $cgi->script_name() . $pathname;
	    } else {
		my($path) = $cgi->param('path');
		$url = $self->ccall('context' => $info->{'mode'},
				    'id' => $row->{'rowid'},
				    'path' => join(',', ($path || ()), $row->{'rowid'}));
	    }
	    $assoc->{'_URL_'} = $url;
	}
	#
	# Fix field values
	#
	if($cgi->param('pathname') && $issymlink) {
	    $row->{'count'} = '@';
	}

	$self->searcher_layout_result($template, $name, $result, $context);
    };

    my(%context) = (
		    'context' => 'catalog categories',
		    'template' => $template,
		    'layout' => $layout,
		    'table' => $category,
		    'sql' => $sql,
		    );

    return $self->searcher(\%context);
}

#
# HTML display records of a category for editing/display
#
sub cedit_searcher {
    my($self, $template, $table, $info, $primary_values) = @_;

    my($info_table) = $self->db()->info_table($table);
    my($primary_key) = $info_table->{'_primary_'};

    my($where) = "$table.$primary_key in ($primary_values)";

    return $self->catalog_searcher($template, $table, $info, $where, 'id');
}

#
# HTML display records for editing/display
# Common function for date/alpha/theme catalogs
#
sub catalog_searcher {
    my($self, $template, $table, $info, $where, $param) = @_;
    my($cgi) = $self->{'cgi'};

    my($info_table) = $self->db()->info_table($table);
    my($primary_key) = $info_table->{'_primary_'};

    #
    # Define search domain
    #
    my($name) = $cgi->param('name');
    my($catalog) = $self->cinfo()->{$name};
    $where = '' if(!defined($where));
    if(defined($catalog->{'cwhere'}) && $catalog->{'cwhere'} !~ /^\s*$/) {
	$where .= " and ($catalog->{'cwhere'})";
    }

    my(%context) = (
		    'context' => 'catalog entries',
		    'params' => [ $param ],
		    'url' => $cgi->script_name(),
		    'page' => scalar($cgi->param('page')),
		    'page_length' => scalar($cgi->param('page_length')),
		    'template' => $template,
		    'expand' => 'yes',
		    'table' => $table,
		    'where' => $where,
		    'order' => $catalog->{'corder'},
		    );

    return $self->searcher(\%context);
}

#
# HTML callback of sqledit function searcher : add links to actions
# depending on the context of the search.
#
sub searcher_links {
    my($self, $table, $row, $context) = @_;

    my($imagespath) = "$self->{'htmlpath'}/images";
    my($cgi) = $self->{'cgi'};
    my($name) = $cgi->param('name');
    my($url) = $cgi->script_name();
    if($context->{'context'} eq 'catalog categories') {
	my($id) = $cgi->param('id');
	my($issymlink);
	my(@symlink);
	if($row->{'info'} && $row->{'info'} =~ /\bsymlink\b/) {
	    $issymlink = 1;
	    @symlink = (
			'symlink' => 'yes',
			);
	}
	my($html) = '';
	$html .= "<a href=\"" . $self->ccall('context' => 'categoryremove',
					     'id' => $id,
					     'path' => undef,
					     @symlink,
					     'child' => $row->{'rowid'}) . "\"><img src=$imagespath/cut.gif alt='Remove this category' border=0></a> ";
	if(!$issymlink) {
	    $html .= "<a href=\"" . $self->ccall('context' => 'categoryedit',
						 'child' => $row->{'rowid'},
						 'id' => $id) . "\"><img src=$imagespath/edit.gif alt='Edit category properties' border=0></a> ";
	}
	return $html;
    } elsif($context->{'context'} eq 'catalog entries') {
	my($info) = $self->db()->info_table($table);
	my($primary_key) = $info->{'_primary_'};
	my($id) = $cgi->param('id');
	my($html);
	my(%spec) = (
		     'centryremove' => ['Unlink from this category', 'unlink'],
		     'centryremove_all' => ['Unlink from all categories and remove record', 'cut'],
		     );
	my($tag, $label);
	foreach $tag (sort(keys(%spec))) {
	    my($label, $image) = @{$spec{$tag}};
	    $html .= "<a href=\"" . $self->ccall('row' => $row->{$primary_key},
						 'context' => $tag,
						 'id' => $id) . "\"><img src=$imagespath/$image.gif alt='$label' border=0></a> ";
	}
	$html .= "<a href=\"" . $self->ccall('row' => $row->{$primary_key},
					     'context' => 'centryedit',
					     'id' => $id) . "\"><img src=$imagespath/edit.gif alt='Edit the record' border=0></a> ";
	return $html;
    } else {
	return $self->Catalog::tools::sqledit::searcher_links($table, $row, $context);
    }
}

#sub crowid2categories {
#    my($self, $name, $rowid, $url) = @_;
#
#    my($category2entry) = "catalog_entry2category_$name";
#    my($category) = "catalog_category_$name";
#    my($rows) = $self->db()->exec_select("select a.rowid,a.name from $category as a,$category2entry as b where b.row = $rowid and b.category = a.rowid");
#    my(@categories);
#    my($row);
#    foreach $row (@$rows) {
#	push(@categories, "<a href=$url&id=$row->{'rowid'}>$row->{'name'}</a>");
#    }
#    return (@categories ? \@categories : undef);
#}

#
# HTML walk records of a theme catalog, call $func on each record
#
sub walk {
    my($self, $func, @ids) = @_;
    
    my($cgi) = $self->{'cgi'};

    my($name) = $cgi->param('name');

    $self->walk_api($name, $func, @ids);
}

#
# HTML fill template with specified categories
#
sub category_display {
    my($self, $template, $ids) = @_;
    my($cgi) = $self->{'cgi'};

    my($name) = $cgi->param('name');
    my($category) = "catalog_category_$name";

    my($sql);
    if(defined($ids)) {
	my($limit) = join(',', @$ids);
	$sql = "select a.rowid,a.name,a.count from $category as a where a.rowid in ($limit)";
    } else {
	my($catalog) = $self->cinfo()->{$name};
	my($id) = $catalog->{'root'};
	my($category2category) = "catalog_category2category_$name";
	$sql = "select a.rowid,a.name,a.count,b.info from $category as a, $category2category as b where a.rowid = b.down and b.up = $id";
    }

    my(%context) = (
		    'context' => 'catalog categories display',
		    'template' => $template,
		    'table' => $category,
		    'sql' => $sql,
		    );

    return $self->searcher(\%context);
}

#sub category_rows {
#    my($self, $template, $rows, $info) = @_;
#
#    if(@$rows <= 0) {
#	$template->{'skip'} = 1;
#	return;
#    }
#    
#    my($html) = '';
#    my($params) = $template->{'params'};
#    if(!exists($params->{'style'}) || $params->{'style'} eq 'list') {
#	my($template_entry) = $template->{'children'}->{'entry'};
#
#	my($row);
#	foreach $row (@$rows) {
#	    $html .= $self->category_row($template_entry, $row, $info);
#	}
#	$template_entry->{'html'} = $html;
#    } elsif($params->{'style'} eq 'table') {
#	my($template_row) = $template->{'children'}->{'row'};
#	my($template_entry) = $template_row->{'children'}->{'entry'};
#	my($count_max) = $params->{'columns'} || 5;
#	my($count) = 0;
#	my($columns) = '';
#	my($row);
#	foreach $row (@$rows) {
#	    if($count >= $count_max) {
#		$template_entry->{'html'} = $columns;
#		$html .= $self->stemplate_build($template_row);
#		$columns = '';
#		$count = 0;
#	    }
#	    $count++;
#	    $columns .= $self->category_row($template_entry, $row, $info);
#	}
#	if($count > 0) {
#	    $template_entry->{'html'} = $columns;
#	    $html .= $self->stemplate_build($template_row);
#	}
#	$template_row->{'html'} = $html;
#    } else {
#	croak("unknown style $params->{'style'}");
#    }
#}
#
#sub category_row {
#    my($self, $template, $row, $info) = @_;
#    my($cgi) = $self->{'cgi'};
#
#    my($assoc) = $template->{'assoc'};
#    template_set($assoc, '_NAME_', $row->{'name'});
#    template_set($assoc, '_ROWID_', $row->{'rowid'});
#    if(exists($assoc->{'_URL_'})) {
#	my($path) = $cgi->param('path');
#	my($url) = $self->ccall('context' => $info->{'mode'},
#				'id' => $row->{'rowid'},
#				'path' => join(',', ($path || ()), $row->{'rowid'}));
#	$assoc->{'_URL_'} = $url;
#    }
#    if(defined($row->{'info'}) && $row->{'info'} =~ /symlink/) {
#	template_set($assoc, '_COUNT_', '@');
#    } else {
#	template_set($assoc, '_COUNT_', $row->{'count'});
#    }
#
#    return $self->stemplate_build($template);
#}

#
# HTML remove an empty category
#
sub categoryremove {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($name) = $cgi->param('name');
    my($id) = $cgi->param('child');
    my($parent) = $cgi->param('id');
    my($symlink) = $cgi->param('symlink');

    $self->categoryremove_api($name, $parent, $id, $symlink);

    $cgi->param('context', 'cedit');
    return $self->cedit($cgi);
}

#
# HTML Create a subcategory
# param rowid not set : ask for category name
# param rowid set : create the category
#
sub categoryinsert {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    #
    # Show a form to create a new category
    #
    if(!defined($cgi->param('rowid'))) {
	my($name) = $cgi->param('name');
	$self->pathcheck($name);
	my($table) = "catalog_category_$name";
	my($params) = $self->params('context' => 'insert_form',
				    'style' => 'catalog_category',
				    'table' => $table,
				    'name' => undef);
	eval {
	    $cgi = $cgi->fct_call($params,
				  'name' => 'insert',
				  'args' => { },
				  'returned' => {
				      'fields' => 'rowid',
				      'context' => 'categoryinsert',
				  });
	};
	if($@) {
	    my($error) = $@;
	    print STDERR $error;
	    $self->cerror("recursive cgi call failed, check logs");
	}
	return $self->insert_form($cgi);
    } else {
	my($name) = $cgi->param('name');
	#
	# Link the created category to its parent
	#
	my($up_id) = $cgi->param('id');
	my($down_id) = $cgi->param('rowid');
	$self->categoryinsert_api($name, $up_id, $down_id);
	$cgi->param('context', 'cedit');
	return $self->cedit($cgi);
    }
    
}

#
# HTML edit the category record
#
sub categoryedit {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    #
    # Editing form
    #
    my($child) = $cgi->param('child');
    my($table) = "catalog_category_" . $cgi->param('name');
    my($params) = $self->params('context' => 'edit',
				'style' => 'catalog_category',
				'table' => $table,
				'primary' => $child,
				'name' => undef);
    eval {
	$cgi = $cgi->fct_call($params,
			      'name' => 'edit',
			      'args' => { },
			      'returned' => {
				  'context' => 'categoryedit_done',
			      });
    };
    if($@) {
	my($error) = $@;
	print STDERR $error;
	$self->cerror("recursive cgi call failed, check logs");
    }
    return $self->edit($cgi);
}

#
# HTML the category record has been edited, update catalog structure
# accordingly.
#
sub categoryedit_done {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($name) = $cgi->param('name');
    $self->pathcheck($name);
    my($child) = $cgi->param('child');

    $self->categoryedit_api($name, $child);

    $cgi->param('context' => 'cedit');
    $self->cedit($cgi);
}

#
# HTML edit a catalog entry
#
sub centryedit {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();

    #
    # Editing form
    #
    my($name) = $cgi->param('name');
    my($table) = $ccatalog->{$name}->{'tablename'};
    my($params) = $self->params('context' => 'edit',
				'primary' => $cgi->param('row'),
				'table' => $table,
				'name' => undef);
    eval {
	$cgi = $cgi->fct_call($params,
			      'name' => 'edit',
			      'args' => { },
			      'returned' => {
				  'context' => 'cedit',
			      });
    };
    if($@) {
	my($error) = $@;
	print STDERR $error;
	$self->cerror("recursive cgi call failed, check logs");
    }
    return $self->edit($cgi);
}

#
# HTML search the record table for a record to insert in current
# category.
#
sub centryselect {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();
    
    my($name) = $cgi->param('name');
    my($table) = $ccatalog->{$name}->{'tablename'};
    
    my($params) = $self->params('context' => 'search_form',
				'table' => $table,
				'name' => undef);

    eval {
	$cgi = $cgi->fct_call($params,
			      'name' => 'select',
			      'args' => { },
			      'returned' => {
				  'fields' => 'rowid',
				  'context' => 'centryinsert',
			      });
    };
    if($@) {
	my($error) = $@;
	print STDERR $error;
	$self->cerror("recursive cgi call failed, check logs");
    }
    return $self->search_form($cgi);
}

#
# HTML remove catalog entry and all links to categories step 1
#
sub centryremove_all {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($table) = $cgi->param('table');

    my($template) = $self->template("centryremove_all");

    template_set($template->{'assoc'}, '_HIDDEN_',
		 $self->hidden('id' => $cgi->param('id'),
			       'row' => $cgi->param('row'),
			       'context' => 'centryremove_all_confirm'));
    
    return $self->stemplate_build($template);
}

#
# HTML remove catalog entry and all links to categories step 2
#
sub centryremove_all_confirm {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($name) = $cgi->param('name');
    #
    # Remove all the links between the entry and the categories
    #
    my($primary_value) = $cgi->param('row');

    $self->centryremove_all_api($name, $primary_value);

    $cgi->param('context', 'cedit');
    return $self->cedit($cgi);
}

#
# HTML remove link between current category and record
#
sub centryremove {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($name) = $cgi->param('name');
    #
    # Remove the link between the entry and the category
    #
    my($id) = $cgi->param('id');
    my($row) = $cgi->param('row');

    $self->centryremove_api($name, $id, $row);

    $cgi->param('context', 'cedit');
    return $self->cedit($cgi);
}

#
# HTML Create a record and link to current category
# param rowid not set : insert form for the record
# param rowid set : link new record to current category
#
sub centryinsert {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();

    #
    # Show a form to create a new entry
    #
    my($name) = $cgi->param('name');
    my($table) = $ccatalog->{$name}->{'tablename'};
    if(!defined($cgi->param('rowid'))) {
	my($params) = $self->params('context' => 'insert_form',
				    'table' => $table,
				    'name' => undef);
	eval {
	    $cgi = $cgi->fct_call($params,
				  'name' => 'insert',
				  'args' => { },
				  'returned' => {
				      'fields' => 'rowid',
				      'context' => 'centryinsert',
				  });
	};
	if($@) {
	    my($error) = $@;
	    print STDERR $error;
	    $self->cerror("recursive cgi call failed, check logs");
	}
	return $self->insert_form($cgi);
    } else {
	my($name) = $cgi->param('name');
	#
	# Link the created entry to its category
	#
	my($id) = $cgi->param('id');
	my($rowid) = $cgi->param('rowid');

	$self->centryinsert_api($name, $id, $rowid);

	$cgi->param('context', 'cedit');
	return $self->cedit($cgi);
    }
}

#
# HTML Create a new catalog (date/alpha/theme)
# param rowid not set : fill the category description record
# param rowid set : call the appropriate catalog creation function
#
sub cbuild {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;

    my($error);
    my($navigation) = $cgi->param('navigation');
    my($table) = $cgi->param('table');
    #
    # Show a form to create a new catalog
    #
    if(!defined($cgi->param('rowid'))) {
	$error = $self->cbuild_check('', $table, $navigation, 'step1');
	if(!defined($error)) {
	    my($style) = "catalog_$navigation";
	    my($params) = $self->params('context' => 'insert_form',
					'table' => 'catalog',
					'navigation' => $navigation,
					'style' => $style,
					'tablename' => $table,
					'name' => undef);
	    eval {
		$cgi = $cgi->fct_call($params,
				      'name' => 'insert',
				      'args' => { },
				      'returned' => {
					  'fields' => 'rowid,name,tablename,fieldname,navigation',
					  'context' => 'cbuild',
				      });
	    };
	    if($@) {
		my($error) = $@;
		print STDERR $error;
		$self->cerror("recursive cgi call failed, check logs");
	    }
	    return $self->insert_form($cgi);
	}
    } else {
	my($rowid) = $cgi->param('rowid');
	my($name) = $cgi->param('name');
	my($field)= $cgi->param('fieldname');
	$error = $self->cbuild_check($name, $table, $navigation, 'step2', $field);

	if(!defined($error)) {
	    $self->cbuild_real($rowid, $name, $table, $navigation, $field);
	} else {
	    $self->db()->exec("delete from catalog where rowid = $rowid");
	}
    }
    return $self->ccontrol_panel(Catalog::tools::cgi->new({
	'context' => 'ccontrol_panel',
	'comment' => $error,
	'table' => $table,
	'navigation' => $navigation,
    }));
}

#
# HTML edit catalog description record
#
sub ccatalog_edit {
    my($self, $cgi) = @_;
    $self->{'cgi'} = $cgi;
    my($ccatalog) = $self->cinfo();

    #
    # Edit the informations about the catalog
    #
    my($name) = $cgi->param('name');
    my($rowid) = $ccatalog->{$name}->{'rowid'};
    my($navigation) = $ccatalog->{$name}->{'navigation'};
    my($params) = $self->params('context' => 'edit',
				'table' => 'catalog',
				'style' => "catalog_$navigation",
				'primary' => $rowid);
    eval {
	$cgi = $cgi->fct_call($params,
			      'name' => 'edit',
			      'args' => { },
			      'returned' => {
				  'context' => 'ccatalog_edit_done',
			      });
    };
    if($@) {
	my($error) = $@;
	print STDERR $error;
	$self->cerror("recursive cgi call failed, check logs");
    }
    return $self->edit($cgi);
}

#
# HTML update catalog structure after modification of catalog record
#
sub ccatalog_edit_done {
    my($self, $cgi) = @_;

    $self->cinfo_clear();

    return $self->ccontrol_panel(Catalog::tools::cgi->new({
	'context' => 'ccontrol_panel',
    }));
}

#
# HTML Return a Catalog::path object built with the cgi params
#
sub cgi2path {
    my($self) = @_;
    my($cgi) = $self->{'cgi'};

    my($path) = $cgi->param('path');
    my($id) = $cgi->param('id');
    my($pathname) = $cgi->param('pathname');
    my($url);
    my($fashion);
    if(defined($pathname)) {
	$url = $cgi->script_name();
	$fashion = 'intuitive';
    } else {
	$url = $self->ccall(path => undef, id => undef);
	$fashion = 'cgi';
    }
    my($name) = $cgi->param('name');
    my($catalog) = $self->cinfo()->{$name};
    my($root) = $catalog->{'root'};

    my($path_obj);

    eval {
	$path_obj = Catalog::path->new(
				       'db' => $self->db(),
				       'root' => $root,
				       'name' => $name,
				       'url' => $url,
				       'id' => $id,
				       'path' => $path,
				       'pathname' => $pathname,
				       'fashion' => $fashion,
				       'path_root_label' => $self->{'path_root_label'},
				       'path_separator' => $self->{'path_separator'},
				       'path_constant' => $self->{'path_constant'},
				       'path_last_link' => $self->{'path_last_link'},
				       );
    };
    $self->cerror("The category path was not found") if(!defined($path_obj));

    return $path_obj;
}

#
# When generating sqledit recursive calls, strip catalog name and path
#
sub call {
    my($self, $table, $info, $row, %pairs) = @_;

    my($tag);
    foreach $tag ('name', 'path') {
	$pairs{$tag} = undef if(!defined($pairs{$tag}));
    }

    return $self->Catalog::tools::sqledit::call($table, $info, $row, %pairs);
}

#
# Generate cgi call that preserves persistent parameters
#
sub ccall {
    my($self, %pairs) = @_;

    my($params) = $self->params(%pairs);
    my($script) = $self->{'cgi'}->script_name();
    return "$script?$params";
}

#
# Change error handling method for Catalog::implementation base class
#
sub cerror {
    my($self) = shift;
    $self->serror(@_);
}

1;
# Local Variables: ***
# mode: perl ***
# End: ***