./Zanas.pm

no warnings;

use Zanas::Presentation;
use Zanas::Content;
use Zanas::Apache;
use Zanas::SQL;
use Zanas::Request;

################################################################################

sub require_fresh {

	my ($module_name) = @_;

	if ($_USER and $$_USER{role} and $module_name =~ /Content|Presentation/) {

		my $specific_module_name = $module_name;

		$specific_module_name =~ s/(Content|Presentation)/$$_USER{role}::$1/;

		my $error;

		eval {$error = require_fresh_internal ($specific_module_name)};

		return unless $error;

	}

	require_fresh_internal ($module_name, 1);

}

################################################################################

sub fix_module_for_role {

	my ($file_name) = @_;

	my $tmp_file_name = $file_name . '~';

	open (IN, $file_name) or die "Cannot open $file_name: $!\n";
	open (OUT, ">$tmp_file_name") or die "Cannot write to $tmp_file_name: $!\n";

	my $suffix = ($_USER and $$_USER{role}) ? '_for_' . $_USER -> {role} : '';

	while (my $s = ) {

		$s =~ s/sub\s+get_menu\w*/sub get_menu$suffix/;

		print OUT $s;

	}

	close (OUT);
	close (IN);

}

################################################################################

sub require_fresh_internal {

	my ($module_name, $fatal) = @_;

	if ($conf -> {core_spy_modules} || $preconf -> {core_spy_modules}) {

		my $file_name = $module_name;

		$file_name =~ s{::}{\/}g;

		my $inc_key = $file_name . '.pm';

		$file_name =~ s{^(.+?)\/}{\/};
		$file_name = $PACKAGE_ROOT . $file_name . '.pm';

		-f $file_name or return "File not found: $file_name\n";

		fix_module_for_role ($file_name) if $conf -> {core_fix_modules} and $module_name =~ /Content|Presentation/;

		my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $last_modified, $ctime, $blksize, $blocks) = stat ($file_name);

		my $last_load = $INC_FRESH {$module_name} + 0;

		my $need_refresh = $last_load < $last_modified;

		$need_refresh or return;

#		eval { do $file_name };

		delete $INC {$inc_key};

		eval "require $module_name";

	}

	else {

		eval "require $module_name";

	}

	$INC_FRESH {$module_name} = time;

        if ($@ and $fatal) {
		$_REQUEST {error} = $@;
		print STDERR "require_fresh: error load module $module_name: $@\n";
        }

        return $@;

}

################################################################################

BEGIN {

#	print STDERR Dumper ($preconf);

	if ($ENV {GATEWAY_INTERFACE} =~ m{^CGI/} || $conf -> {use_cgi} || $preconf -> {use_cgi}) {
 		eval 'require CGI';
	} else {
		eval 'require Apache::Request';
		if ($@) {
			eval 'require CGI';
			eval 'require Zanas::Request';
		};
	}

	$INC {'Apache/Request.pm'} or eval 'require Zanas::Request';

	our $STATIC_ROOT = __FILE__;
	$STATIC_ROOT =~ s{\.pm}{/static/};

	eval 'require Compress::Zlib';

	if ($@) {
		delete $conf -> {core_gzip};
		delete $preconf -> {core_gzip};
	};

	our %INC_FRESH = ();

	while (my ($name, $path) = each %INC) {

		delete $INC {$name} if $name =~ m{Zanas[\./]};

	}

	our $PACKAGE_ROOT = $INC {__PACKAGE__ . '/Config.pm'};

	$PACKAGE_ROOT ||= '';

	$PACKAGE_ROOT =~ s{\/Config\.pm}{};

	$conf = {%$conf, %$preconf};

	if ($conf -> {core_load_modules}) {

		opendir (DIR, "$PACKAGE_ROOT/Content") || die "can't opendir $PACKAGE_ROOT/Content: $!";
		my @files = grep {/\.pm$/} map { "Content/$_" } readdir(DIR);
		closedir DIR;

		opendir (DIR, "$PACKAGE_ROOT/Presentation") || die "can't opendir $PACKAGE_ROOT/Presentation: $!";
		push @files, grep {/\.pm$/} map { "Presentation/$_" } readdir(DIR);
		closedir DIR;

		foreach my $file (@files) {

			$file =~ s{\.pm$}{};
			$file =~ s{\/}{\:\:};

			require_fresh (__PACKAGE__ . "::$file");

		}

	}

	if ($conf -> {db_dsn}) {

		sql_reconnect ();

		$model_update -> assert (

			tables => {

				sessions => {

					columns => {

						id => {
							TYPE_NAME  => 'bigint',
							_PK    => 1,
						},

						id_user => {
							TYPE_NAME  => 'int',
						},

						ts => {
							TYPE_NAME  => 'timestamp',
						},

					}

				},

			},

		);

		$model_update -> assert (

			default_columns => {
				id   => {TYPE_NAME  => 'int', _EXTRA => 'auto_increment', _PK => 1},
				fake => {TYPE_NAME  => 'bigint', COLUMN_DEF => 0, NULLABLE => 0},
			},

			tables => {

				roles => {

					columns => {
						name  => {TYPE_NAME    => 'varchar', COLUMN_SIZE  => 255},
						label => {TYPE_NAME    => 'varchar', COLUMN_SIZE  => 255},
					},

				},

				users => {

					columns => {
						name =>     {TYPE_NAME => 'varchar', COLUMN_SIZE => 255},
						login =>    {TYPE_NAME => 'varchar', COLUMN_SIZE => 255},
						label =>    {TYPE_NAME => 'varchar', COLUMN_SIZE => 255},
						password => {TYPE_NAME => 'varchar', COLUMN_SIZE => 255},
						id_role =>  {TYPE_NAME => 'int'},
					}

				},

				log => {

					columns => {
						id_user =>   {TYPE_NAME => 'int'},
						id_object => {TYPE_NAME => 'int'},
						ip =>        {TYPE_NAME => 'varchar', COLUMN_SIZE => 15},
						ip_fw =>     {TYPE_NAME => 'varchar', COLUMN_SIZE => 15},
						type =>      {TYPE_NAME => 'varchar', COLUMN_SIZE => 255},
						action =>    {TYPE_NAME => 'varchar', COLUMN_SIZE => 255},
						error =>     {TYPE_NAME => 'varchar', COLUMN_SIZE => 255},
						params =>    {TYPE_NAME => 'text'},
						dt =>        {TYPE_NAME => 'timestamp'},
					}

				},

			},

		)

	}

}

################################################################################

package Zanas;

$VERSION = '0.9908';

=head1 NAME

Zanas.pm - a RAD platform for WEB GUIs with rich DHTML widget set.

=head1 DESCRIPTION

Zanas.pm is a set of naming conventions, utility functions, and a basic Apache request handler that help to quickly build robust, efficient and good-looking Web interfaces with standard design. The last doesn't mean that you can't alter hardcoded HTML fragments at all. But building public Web sites with original graphics and layout is not the primary goal of Zanas development. Zanas is good for developing client database editing GUIs ('thin clients') and is conditionnally comparable to Windows API and java Swing.

Zanas' basic features are:

=over

=item GUI base

usable set of DHTML widgets (forms, toolbars, etc);

=item transparent DB scheme maintenace

when some table or field lacks, the application creates it silently;

=item ALC support

standard routines to backup, mirror and synchronize multiple installations of the same application (Zanas::Install);

=item sessions

session management subsystem with transparent query rewriting;

=item js alerting

server side error handling and data validation with client javaScript notifications without page reloading (yes, it really is);

=item logging

action logging is a part of core process, additionl API calls aren't needed;

=item fake records/garbage collection

handling of temporary records that are only visible on creation forms and wizards;

=back

=head1 DESIGN PRINCIPLES

There is a whole a lot of univesal web application platforms. So, why develop another one instead of using some mature product? 'Cause we've already tried many of this and have'nt found a good one.

When developing Zanas, we use the following principles:

=over

=item no OO

HTTP is nothing else than evaluting string functions with sets of named parameters. Request handler must do nothing else than decompose the top function to some more primitive functions. So, Zanas is purely procedure-oriented framework.

=item content/presentation separation

Request handler reduce the top function f (x) to a superposition of a content and a presentation function: c (x) and p (c, x), where c (x) can't produce any HTML fragment in its result and p (c, x) can't use any info stored in the database.

	f (x) = p (c (x), x).

=item URL discipline and strict callback naming

Content and presentation functions can be reduced to swithes between some elementary callback functions, where the switch is directly governed by known CGI parameters. Say, for C C and C

. =item no ASP or like Perl is ideal for implementing templating languages. That's why people love to implement new templating languages in Perl. But most of them ignore the fact that Perl I a templating language. Heredoc syntax is much more usable than any ASP-like. And it doesn't require any additional processing: everything is done by the Perl interpreter. =item no XML Nested Perl datastructures like list-of-hashes and more complex offer the same functionnality as the XML DOM model. And it doesn't require any external libraries: everything is done by the Perl interpreter. =item no XSLT It would be very strange to use XSLT without XML, but we must underline here that there was one more reason to not use XSLT. Its syntax is even much crappier and less flexible than ASP-like. =back =head1 MAGIC CGI PARAMETERS The next CGI parameters have special meaning in Zanas URLs and can be used only as described. =over =item sid Session ID. If not set, the client is automatically redirected to the logon screen. =item type Type of the current screen. Can have values like C<'users'> or, for example, C<'users_creation_wizard_step_2'>. Influences the callback functions selection and the main menu rendering. =item id Current object ID. Influences the callback functions selection. When set, the screen presents detailed info of one object, otherwise, it contains some search results. =item action Name of the action to execute. If set, the request handler executes some editing callback, then evalutes the new URL where C is unset and redirects the client there. =item salt Fake random parameter for preventing the client HTML cacheing. =back =head1 GLOBAL VARIABLES The next variables are accessible in all callback subs. =over =item %_REQUEST The hash of CGI parameters and its values =item $_USER The hashref containing the current user information: { id => ... name => ... role => ... } =back =head1 CALLBACK SUBS Under differnent circumstances, Zanas Apache request handler executes appropriate callback subs. The name of the callback to execute depends on current program context, C value and the role of the current user. Suppose that the context imply the callback name C<$my_callback>, C<$_REQUEST{type}> is C<$type> and C<$$_USER {role}> is C<$role>. In this case, if the sub named "${my_callback}_${type}_for_${role}" is defined, it will be called. Otherwise, if the sub named "${my_callback}_${type}" is defined, it will be called. Otherwise, undef value will be used instead of missing sub result. In the next sections, "${my_callback}_${type}_for_${role}" always means one of 3 cases described above. =over =item validate_{$action}_${type}_for_${role} This sub must analyze the values of parameters in C<%_REQUEST> hash for consistency. In most cases, the object id is stored in C<$_REQUEST {id}> and the names of all other fields are underscore prefixed (C<$_REQUEST {_name}>, C<$_REQUEST {_login}>, C<$_REQUEST {_password}> etc). If everythig's OK, the validator must return C. Otherwise, the return value is an error code. We'll call it C<$error>. So, if C<$error> is defined, an error message template C<$$error_messages {"{$action}_${type}_${error}"}> is interpolated as a qq-string and then sent to the user as the error message. For example, if the sub C returns C<'duplicate_login'>, C<$_REQUEST {_login} eq 'scott'> and C<$$error_messages {"update_users_duplicate_login"} eq 'Duplicate login: \'$_REQUEST{_login}\''>, then the error message will be C<"Duplicate login: 'scott'">. =item do_{$action}_${type}_for_${role} This sub must execute the C<$action>. Note that you can choose the next screen shown to the user by manipulating the C<%_REQUEST> hash. For example, it's usual to set the C parameter after creating new object: sub do_create_users_for_admin { sql_do ("INSERT INTO ... "); $_REQUEST {id} = sql_last_insert_id (); } The client window will be rediredted to "/?type=users&id=1&sid=...". =item get_item_of_${type}_for_${role} This sub must fetch the info for the screen of type C<$type> having the obgect id C<$_REQUEST {id}> and the role C<${role}>. Usually it's a reference to a hash, may be nested. =item select_${type}_for_${role} This sub must fetch the info for the screen of type C<$type> and the role C<${role}>. Usually it's a reference to a list of references to hashes, may be nested. =item draw_item_of_${type}_for_${role} This sub must render the screen of type C<$type> having the obgect id C<$_REQUEST {id}> and the role C<${role}> as HTML. The info fetched with C is passed as its 1st parameter. =item draw_${type}_for_${role} This sub must render the screen of type C<$type> sand the role C<${role}> as HTML. The info fetched with C is passed as its 1st parameter. =back =head1 HTTP REQUEST HANDLING =head2 SESSION CHECKING First of all, the handler checks for the C param and, if the session is alive, it sets the C<$USER> variable, otherwise, redirects the client to the logon screen. =head2 EDITING REQUEST If the C CGI parameter is set, then the sub named C is invoked. If if returns a non-empty error message, it's logged and presented with a js popup window. Otherwise the sub named C is invoked, then the client is redirected to the new URL composed from all C<%_REQUEST> key-value pairs except C and those which names start with an C<'_'>. In any case, the HTTP response has status 200 (OK) and contains a tiny HTML document consisting of a singular C tag with a non-empty C event handler. When an error occurs, this handler displays the message in a js popup window. Otherwise the C handler opens the new URL in the top browser window. Every conventional HTML page generated by Zanas Apache handler has a zero sized internal frame called C. In order to improve the GUI usability, every anchor with non-empty C parameter value in its href and every form with a non-empty value for C input must use C as the target: [New Folder]

...
Standard Zanas HTML rendering API does this automatically. =head2 OBJECT BROWSING REQUEST If the C CGI parameter is unset and C CGI parameter is set, then the HTML resuls from the superposition of C and C callbacks. =head2 SELECTION BROWSING REQUEST If both C and C CGI parameters are unset, then the HTML resuls from the superposition of C and C callbacks. =head1 MODULES STRUCTURE Zanas modules don't have a C directive. All the stuff is loaded in one package. Callback subs must be placed in strictly named .pm files. Suppose that you've chosen C<$applib> as your application library root and have placed it in your C<@INC> array. Then, create C<$applib/Content> and C<$applib/Presentation> directories. Now, all content callbacks (C, C, C and C) must be defined in C<$applib/Content/${type}.pm> and presentation callbacks (C and C) in C<$applib/Presentation/${type}.pm>. $applib Content roles.pm users.pm Presentation roles.pm users.pm =head1 MORE API DOCS Generate it: perl -MZanas::Docs -e generate =head1 SEE ALSO DBIx::ModelUpdate Zanas::Install =head1 AUTHORS Dmitry Ovsyanko Pavel Kudryavtzev Yaroslav Ivanov <... hekima ...> 1;

./Makefile.PL

use ExtUtils::MakeMaker;
WriteMakefile(
    'NAME'		=> 'Zanas',
    'VERSION_FROM'	=> 'Zanas.pm',
    'PREREQ_PM'		=> {
	'DBI'			=> '1.21',
	'DBD::mysql'		=> '2.9002',
	'Apache::Request'	=> '0.33',
	'Number::Format'	=> '1.45',
	'DBIx::ModelUpdate'	=> '0.2',
    },
    ($] >= 5.005 ?
	(ABSTRACT_FROM => 'Zanas.pm',
	AUTHOR     => 'D. Ovsyanko ') : ())
);

./Changes

0.9908:

	- check_title sub is added, draw_table_header, draw_row_button and draw_text_cell are fixed to use it;

0.9907:

	- magic %_REQUEST params documented;
	- trees in 'checkboxes' are supported;

0.9906:

	- new $conf options: kb_options_menu, kb_options_buttons, kb_options_pager and kb_options_focus;
	- hotkey and hotkeys subs added;
	- draw_toolbar_input_select sub added;
	- draw_form_field_checkboxes redone dramatically;

0.9905:

	- Zanas::Docs added;

0.9904:

	- added 'read_only' option in draw_menu sub;

0.9903:

	- new magic parameter: __help_url;
	- draw_text_cell fix: no hrefs rendered when lpt=1;
	- added 'position' option in add_totals sub;
	- added 'read_only' option in draw_input_cell sub;
	- another XLS fix: $conf -> {site_root} removed, $$ added in temporary filename;

0.9902:

	- serving XLS responses refactored: $conf -> {site_root} is obsoleted & no more filesystem garbage;
	-  fixed to $$conf{page_title} in lpt mode;
	- new magic parameter: $_REQUEST{_xls_checksum}. Added in the 1st <td> in an invisible extra row of the main table (LPT mode only);

0.9901:

	- added magic parameter _xml (included in <head>);
	- added namespace for excel;
	- added title attribute for table headers an cells;
	- added 'a_class' option to draw_text_cells sub;
#	- storing sid in session-only cookie;

0.99: 15.03.2004 10:30

	- pulldown menu rendering fixed (no glitches now);
	- js function open_popup_menu is moved to navigation.js;
	- HTTP_X_FORWARDED_FOR logging;

0.98: 12.03.2004 11:00

	- new feature: 2-level main menu;

		sub get_menu_for_my_role {

			return [
				{
					name  => 'type1',
					label => 'Screen Type 1',
					items => [
						{
							name  => 'type11',
							label => 'Screen Type 11',
						},
						{
							name  => 'type12',
							label => 'Screen Type 12',
						},
					]
				},
				{
					name  => 'type2',
					label => 'Screen Type 2',
				},
			]
		}

0.97: 10.03.2004 16:40

	- sql_reconnect and Config reloading order fixed;
	- target option added in draw_toolbar_button sub;

0.96: 09.03.2004 17:00

	- target option added in draw_toolbar sub;
	- Zanas::Install is forked out;
	- order sub fixed for correct DESC handling;
	- draw_table fixed for not scrolling through totals;
	- added is_total option to draw_text_cell sub;
	- added real_path option to upload_file output;
	- added add_columns option in sql_upload_file sub (by pashka);
	- Zanas::Request fixed for the case where directory listing is denied;
	- Apache::Constants::OK fixed for prototype matching.

0.95: 03.03.2004 12:30

	- add_totals sub added;
	- $_REQUEST {__response_sent} is now used when doing actions (by pashka);
	- redirection target is changed from _top to _parent (by pashka);

0.94: 02.03.2004 16:00

	- (by pashka) no more strong dependency on Apache::Request or even mod_perl.
	  One can use Zanas.pm based apps on any raw CGI hosting. The script is

----------- cut here ------------------------

#!/usr/bin/perl -w

use lib '/path/to/webapp/library';

use MYAPP;

$MYAPP::preconf = {

	db_dsn => "DBI:mysql:database=mybase",
	db_user => 'myuser',
	db_password => 'mypassword',

	core_load_modules => 0,
	core_spy_modules => 0,
	core_fix_modules => 0,
	core_gzip => 1,

};

MYAPP::handler;

----------- cut here ------------------------

	  It's 20 times more slow, but it works;
	- fixed a security hole with 'type=users_for_admin' (by pashka);
	- multirole fixes (by pashka);
	- added $_REQUEST {redirect_params} handling (idea by pashka);
	- new log fields: id_object and ip;


0.93: 26.02.04 12:50

	- targets are now in use with activate_link js function;
	- target option is now passed in draw_text_cells;
	- don't show labels for hrgoups items that are off;
	- <tr> are now marked as id="tr_$$field{name}" in draw_form;
	- added setVisible js function;
	- added onChange option to draw_form_field_select sub;
	- added onClose option to draw_form_field_datetime sub;
	- fixed a bug with calendar format;

0.92: 19.02.04 10:40

	- added href option in draw_text_cells sub;
	- draw_text_cell now receives 2 args: data and options, data can be scalar (label only);
	- '..' option in draw_table;
	- don't load Mozilla3;

0.91: 13.02.04 10:30

	- added a 'no_nobr' option in draw_text_cell;
	- added a 'title' option in draw_text_cell;

0.90: 12.02.04 10:30

	- added a configuration option: $conf -> {exit_url};

0.89:
	- fixed a bug in draw_toolbar_input_text (useless sid hidden input);
	- fixed a bug in check_href (useless sid appending);
	- href option in check_href sub ( => almost *EVRYWHERE*) can now be a HASHref (fed to create_url);
	- added 'additional_buttons' option in draw_form.
	- added 'status_switch'	utility function. Synopsis (in YOUR_APP/Config.pm):

our ($SQL_STATUS, $status) = status_switch (<<EOS);
	CASE
		WHEN now() > my_table.expire_dt THEN 2 # Expired
		WHEN my_table.is_ok             THEN 1 # OK
		ELSE                                 0 # New
	END
EOS
	To use it, add 'use Zanas::Util;' BEFORE 'use YOUR_APP::Config;'.

	- added 'read_only' option for all form inputs;
	- now working unless Apache::Request is installed: in this case, params are fetched with CGI.pm;
	- 'values' option in draw_form_field_static sub can be a hashref;

0.88:
	added 'headers' sub:

		draw_table (

			headers (qw(
				№		no
				Label		label
				_
			)),

			sub {
				...
			}

		)


	- added 'hrefs' sub (for column ordering):

		href      => create_url (order => $order, desc => 0),
		href_asc  => create_url (order => $order, desc => 0),
		href_desc => create_url (order => $order, desc => 1),

	- added 'href_asc' and 'href_desc' options to draw_text_cell.
	- new magic parameter: __pack. Resizes the new opened window to fit its contents.
	- added js function: nop. Does nothing.
	- new magic parameter: __read_only. All form inputs are static.
	- added 'picture' option to draw_form_field_string and draw_form_field_static subs

0.87: 30.01.04 10:30

	- added 'value' option to draw_checkbox_cell sub

0.86: 29.01.04 16:30

	- added 'confirm' option to draw_centered_toolbar_button sub
	- added 'target' option to draw_toolbar_button sub

0.85: 22.01.04 11:52

	- 0.gif is relocated to the root
	- no more 'keepalive' iframe on logon screen

0.84: 21.01.04 13:45

	- Accept-Encoding header is now considered

0.83: 21.01.04 12:15

	- menu.js is killed

0.82: 21.01.04 11:00

	- 0.html is relocated to the root

0.81: 20.01.04 14:00

	- Zanas::Install module added;
	- gzip encoding for basic js and css;
	- sub sql_select_subtree added;

0.80: 26.12.03 14:30

	- basic js and css is now served from Zanas.pm itself;
	- fixed a memory leak related to __include_js and __include_css handling;
	- distro is cleaned up;

0.79: 25.12.03 12:30

	- added html sweeping option ($conf -> {core_sweep_spaces});
	- _W_A_R_N_I_N_G_ !!! Basic navigation javaScript is now served as static content. Copy or symlink static/navigation.js to your app /docroot/i/ dir;
	- added gzipping option ($conf -> {core_gzip}). Requires Compress::Zlib.
	- added Content-Range header in download_file sub;

0.78: 24.12.03 12:30

	- added Content-Length header in download_file sub;
	- added support for $preconf configuration hash (setting in <perl> section);
	- added support for __focused_input magic parameter;
	- sub add_vocabularies added;
	- sub sql_select_vocabulary added;
	- $conf -> {top_banner} is now rendered;

0.77: 17.12.03 10:30

	- added db schema autocheck with DBIx::ModelUpdate;
	- added support for multiroled users (by pashka);
	- __no_navigation is now passed through URL rewriting (by pashka);
	- fixed check_href for /i/ (by pashka);

0.76: 16.12.03 10:30

	- various js fixes in table scrolling/focus handling;

0.75: 15.12.03 14:00

	- new field type: 'calendar' (control taken from http://dynarch.com/mishoo/calendar.epl);

	- added support for:

		$conf -> {include_js}  ||= ['js'];
		$_REQUEST {__include_js} = $conf -> {include_js};

		$conf -> {include_css} ||= ['new'];
		$_REQUEST {__include_css} = $conf -> {include_css};

		Fckeditor js include moved there.

	- added off option to draw_toolbar_input_htmleditor sub;

0.74: 04.12.03 14:00

	- added confirm option to draw_toolbar_button sub;
	- added target option to draw_form sub;

0.73: 02.12.03 15:00

	- added off option to draw_toolbar_input_text sub;
	- added top_banner configuration option;

0.72: 28.11.03 16:00

	- added type-ahead facility to all <select> controls (recipe taken from http://www.oreillynet.com/javascript/2003/09/03/examples/jsdhtmlcb_bonus2_example.html);
	- new magic parameter: __no_navigation. Hides top navigation toolbar and replaces ok_esc_toolbar by close toolbar (for popup windows);
	- added draw_close_toolbar sub;
	- added attributes option to draw_form_field_string sub (for attributes like readonly, disabled etc.);

0.71: 27.11.03 16:20
	- fixed sql_select_all_cnt sub (case: GROUP BY);
	- added value option draw_form_field_select sub;

0.70: 26.11.03 12:00
	- added sql_select_scalar sub;
	- added draw_input_cell sub (by hekima);
	- fixed MSIE_5_js_ok_escape sub (case: no_ok option is set);
	- fixed a bug in draw_form_field_static (case: values option defined);
	- added label_width and cell_width options to all draw_form_field_... subs;
	- added href, target and a_class options in draw_form_field_static sub;
	- fixed draw_form sub (empty <tr>s when $field -> {off});
	- fixed upload_file sub (case: no file upload field at all);

0.69: 25.11.03 15:50
	- added picture option in draw_text_cell sub (Number::Format is now required for install);
	- added draw_text_cells sub;
	- added tooltips in draw_text_cell sub;

0.68: 24.11.03 10:10
	- js fix (char 13 from keyboard & javascript href);
	- added checked option in draw_form_field_checkbox sub (by hekima);
	- added support for $_REQUEST {__meta_refresh} parameter;
	- truncating long strings in draw_form_field_static sub;

0.67: 20.11.03 14:00

	- added values option in draw_form_field_static
	- added support for multicolumn form rows via nested arrays in draw_form sub
	- steps in draw_path in multiline mode (Mayakovsky style)
	- added support for multirow table headers via nested arrays in draw_table sub
	- draw_toolbar_pager is fixed for the case total == 0

0.66: 18.11.03 13:40
	- added portion option in draw_toolbar_pager
	- added support for $_REQUEST {__blur_all} parameter
	- added support for $_REQUEST {__scrollable_table_row} parameter
	- more js fixes
	- added file (up|down)load API: delete_file, download_file, upload_file, sql_download_file, sql_upload_file subs and $_REQUEST {__response_sent} parameter
	- added hidden inputs to draw_form_field_static (by hekima)
	- multiline path fixes (by pashka)
	- separate tds for each button in draw_row_buttons sub

0.65: 17.11.03 12:44
	- "Generator" meta tag with version number added in draw_page sub
	- empty action values are now allowed in draw_form

0.64: 17.11.03 11:51
	- added multiline option in draw_path
	- added width & height options in draw_form_field_htmleditor (by pashka)
	- added check_href facility to draw_ok_esc_toolbar and draw_esc_toolbar (by pashka)

0.63: 14.11.03 12:35
	- added value option in draw_form_field_static sub (by hekima)
	- don't show ':' after empty labels in draw_form_field_hgroup (by hekima)
	- js fix: added support for scancodes 48..57

0.62: 13.11.03 09:40
	- added href attribute for table headers in draw_table sub (by hekima)
	- added value option in draw_form_field_text sub (by hekima)
	- fixed a bug with '$name.submit'
	- added 'off' parameter in draw_toolbar_button sub

0.61: ... Can't remember at all :-((

0.60: 06.11.03 10:40
	- added draw_form_field_button sub (by hekima)
	- added support for arbitary value in draw_toolbar_input_text sub (by hekima)
	- optional rows and cols numbers in draw_form_field_text sub (by hekima)
	- optional form name in draw_ok_esc_toolbar and draw_form subs (by hekima)
	- js fix for scrollable table navigation
	- draw_toolbar_input_text refactored for parameter stickness
	- added cgi_tail option in sql_select_path and sql_draw_path subs

0.59: 03.11.03 12:28
	- added support for keepalive
	- size and max_length attributes separated in draw_form_field_string
	- default 'string' type in draw_form_field_hgroup
	- don't show ':' after empty labels in forms

0.58: 30.10.03 14:21
	- workaround for missing error message templates (by Hekima)
	- added 'mandatory' option in draw_form sub
	- added 'a_class' parameter in draw_text_cell sub

0.57: 23.10.03 14:52
	- added 'off' parameter in draw_window_title and draw_table subs
	- added 'draw_text_cell' parameter in draw_text_cell sub
	- added 'attributes' parameter in draw_text_cell sub
	- added support for multiple per-row callbacks in draw_table sub
	- js fixes for fast find/table scrolling
	- js fixes for multitable scrolling

0.56: 21.10.03 15:56
	- fixed check_href: period parameter

0.55: 13.10.03 10:23
	- added sql_select_path sub;

0.54: 30.09.03 10:28
	- added check_href sub;
	- new field types: 'image' & 'htmleditor' (htmleditor taken from http://www.fredck.com/FCKeditor/);

0.53: 26.09.03 13:18
	- added error reporting when reloading modules;
	- fixed hidden inputs rendering;
	- added $options -> {value} for hidden inputs.</pre>
<h1>./f.html</h1>
<pre><h1>./MANIFEST</h1>
<pre>Changes
MANIFEST
Makefile.PL
Zanas.pm
Zanas/Apache.pm
Zanas/Content.pm
Zanas/Presentation.pm
Zanas/Presentation/MSIE_5.pm
Zanas/Presentation/Mozilla_3.pm
Zanas/Presentation/Unsupported.pm
Zanas/Request.pm
Zanas/Request/Upload.pm
Zanas/SQL.pm
Zanas/static/0.gif.pm
Zanas/static/0.html.pm
Zanas/static/navigation.js.gz.pm
Zanas/static/navigation.js.pm
Zanas/static/zanas.css.gz.pm
Zanas/static/zanas.css.pm
t/use.t
</pre>
<h1>./t</h1>
<pre><h1>./t/use.t</h1>
<pre>#!/usr/bin/env perl -w
use strict;
use Test;
BEGIN { plan tests => 1 }

use Zanas; ok(1);
exit;
__END__
</pre>
<h1>./Zanas</h1>
<pre><h1>./Zanas/Apache.pm</h1>
<pre>no warnings;

use Number::Format;

################################################################################

sub handler {

	our $_PACKAGE = __PACKAGE__ . '::';

	my  $use_cgi = $ENV {SCRIPT_NAME} =~ m{index\.pl} || $ENV {GATEWAY_INTERFACE} =~ m{^CGI/} || $conf -> {use_cgi} || $preconf -> {use_cgi} || !$INC{'Apache/Request.pm'};

	our $r   = $use_cgi ? new Zanas::Request () : $_[0];

	our $apr = $use_cgi ? $r : Apache::Request -> new ($r);

	my $parms = $apr -> parms;
	our %_REQUEST = %{$parms};

	$_REQUEST {type} =~ s/_for_.*//;

	$number_format or our $number_format = Number::Format -> new (%{$conf -> {number_format}});

   	sql_reconnect ();

	require_fresh ($_PACKAGE . '::Config');

   	$conf -> {dbf_dsn} and our $dbf = DBI -> connect ($conf -> {dbf_dsn}, {RaiseError => 1});

	$_REQUEST {type} = '_static_files' if $r -> filename =~ /\w\.\w/;

	$conf -> {include_js}  ||= ['js'];

   	$_REQUEST {__include_js} = [];
   	push @{$_REQUEST {__include_js}}, @{$conf -> {include_js}};

   	$_REQUEST {__include_css} = [];
   	push @{$_REQUEST {__include_css}}, @{$conf -> {include_css}};

	if ($_REQUEST {keepalive}) {
		my $timeout = 60 * $conf -> {session_timeout} - 1;
		keep_alive ($_REQUEST {keepalive});
		$r -> content_type ('text/html');
		$r -> send_http_header;
		print <<EOH;
			<html><head>
				<META HTTP-EQUIV=Refresh CONTENT="$timeout; URL=/?keepalive=$_REQUEST{keepalive}">
			</head></html>
EOH
		return;
	}

	my $action = $_REQUEST {action};

	our $_USER = get_user ();

	require_fresh ($_PACKAGE . '::Calendar');

	eval "our \$_CALENDAR = new ${_PACKAGE}Calendar (\\\%_REQUEST)";

	if (!$_USER and $_REQUEST {type} ne 'logon' and $_REQUEST {type} ne '_static_files') {

		delete $_REQUEST {sid};
		delete $_REQUEST {salt};
		delete $_REQUEST {_salt};
		delete $_REQUEST {__include_js};
		delete $_REQUEST {__include_css};

		redirect ('/?type=logon&redirect_params=' . uri_escape (Dumper (\%_REQUEST)));

	}

	elsif ($_REQUEST {keepalive}) {

		redirect ("/\?type=logon&_frame=$_REQUEST{_frame}");

	}
	else {

		my $user_agent = $r->header_in ('User-Agent');

		$_USER -> {drawer_name} =
			$user_agent =~ /MSIE [56]/  ? 'MSIE_5':
			$user_agent =~ /Mozilla\/3/ ? 'Mozilla_3':
			$user_agent =~ /Mozilla\/5/ ? 'MSIE_5':
			'Unsupported';

		require_fresh ("${_PACKAGE}Content::menu");
		require_fresh ("${_PACKAGE}Content::page");

		$page = get_page ();

		unless ($page -> {type} =~ /^_/) {
			require_fresh ("${_PACKAGE}Content::$$page{type}");
			require_fresh ("${_PACKAGE}Presentation::$$page{type}");
		};

		if ($action) {

			my $sub_name = "validate_${action}_$$page{type}";

			my $error_code = call_for_role ($sub_name);

			if ($error_code) {
				my $error_message_template = $error_messages -> {"${action}_$$page{type}_${error_code}"} || $error_code;
				$_REQUEST {error} = interpolate ($error_message_template);
			}

			if ($_REQUEST {error}) {
				out_html ({}, draw_page ($page));
			}
			else {

				delete $_REQUEST {__response_sent};

				eval {
					delete_fakes () if $action eq 'create';
					call_for_role ("do_${action}_$$page{type}");

					if (($action eq 'execute') and ($$page{type} eq 'logon') and $_REQUEST {redirect_params}) {

						my $VAR1;

						eval $_REQUEST {redirect_params};

						while (my ($key, $value) = each %$VAR1) {
							$_REQUEST {$key} = $value;
						}

					}

				};

				if ($@) {
					$_REQUEST {error} = $@;
					out_html ({}, draw_page ($page));
				}
				else {

					unless ($_REQUEST {__response_sent}) {

						my $url = create_url (action => '', redirect_params => '');
						out_html ({}, qq {<body onLoad="window.open ('$url&salt=' + Math.random (), '_parent', 'location=0,menubar=0,status=0,toolbar=0')"></body>});

					}

				}

			}

			log_action ($_USER -> {id}, $$page{type}, $action, $_REQUEST {error}, $_REQUEST {id});

		}
		else {

			out_html ({}, draw_page ($page));

		}

	}

   	$db -> disconnect;

	return OK;

}

################################################################################

sub out_html {

	my ($options, $html) = @_;

	$html or return;

	if ($_REQUEST {dbf}) {
		redirect ("/$html");
	}

	if ($_REQUEST {xls}) {

		my $fn_local = '/i/xls/' . time . "$$.xls";
		my $fn = $r -> document_root . $fn_local;
		open (O, ">$fn") or die "Can't write to $fn: $!";
		print O $html;
		close (O);

		download_file ({
			path => $fn_local,
			file_name => "file.xls",
		});

		unlink $fn;

	}
	else {

		if ($conf -> {core_sweep_spaces}) {
			$html =~ s{^\s+}{}gsm;
			$html =~ s{[ \t]+}{ }g;
		}

		$_REQUEST {__content_type} ||= 'text/html; charset=windows-1251';
		$r -> content_type ($_REQUEST {__content_type});

		if (($conf -> {core_gzip} or $preconf -> {core_gzip}) and $r -> header_in ('Accept-Encoding') =~ /gzip/) {
			$r -> header_out ('Content-Encoding' => 'gzip');
			$html = Compress::Zlib::memGzip ($html);
		}

		$r -> header_out ('Content-Length' => length $html);
		$r -> header_out ('Set-Cookie' => "sid=$_REQUEST{sid};path=/;") if $_REQUEST{sid};

		$r -> send_http_header;
		print $html;
	}

}

1;</pre>
<h1>./Zanas/Content.pm</h1>
<pre>no warnings;

use URI::Escape;

use Apache::Constants qw(:common);
#use Apache::Request;

use Zanas::SQL;

################################################################################

sub add_totals {

	my ($ar, $options) = @_;
	my $totals = {};

	foreach my $r (@$ar) {

		while (my ($key, $value) = each %$r) {

			next if $key =~ /^id|label$/;

			$totals -> {$key} += $r -> {$key};

		}

	}

	$totals -> {label} = 'Итого';

	$options -> {position} = 0 + @$ar unless defined $options -> {position};

	splice (@$ar, $options -> {position}, 0, $totals);

}

################################################################################

sub keep_alive {
	my $sid = shift;
	sql_do ("UPDATE sessions SET ts = NULL WHERE id = ? ", $sid);
}

################################################################################

sub call_for_role {
	my $sub_name = shift;
	my $role = $_USER ? $_USER -> {role} : '';
	my $full_sub_name = $sub_name . '_for_' . $role;
	my $name_to_call =
		exists $$_PACKAGE {$full_sub_name} ? $full_sub_name :
		exists $$_PACKAGE {$sub_name} ? $sub_name :
		undef;

	if ($name_to_call) {
		return &$name_to_call (@_);
	}
	else {
		print STDERR "call_for_role: callback procedure not found: \$sub_name = $sub_name, \$role = $role \n";
	}

	return $name_to_call ? &$name_to_call (@_) : undef;

}

################################################################################

sub get_user {

	if ($ENV{HTTP_COOKIE} =~ /sid=(\d+)/ && !defined $_REQUEST {sid}) {
#		$_REQUEST {sid} ||= $1;
	}

	sql_do ("DELETE FROM sessions WHERE ts < now() - INTERVAL ? MINUTE", $conf -> {session_timeout});
	sql_do ("UPDATE sessions SET ts = NULL WHERE id = ? ", $_REQUEST {sid});

	my $user = sql_select_hash (<<EOS, $_REQUEST {sid});
		SELECT
			users.*
			, roles.name AS role
		FROM
			sessions
			INNER JOIN users ON sessions.id_user = users.id
			INNER JOIN roles ON users.id_role = roles.id
		WHERE
			sessions.id = ?
EOS

	if ($user && $_REQUEST {role} && ($conf -> {core_multiple_roles} || $preconf -> {core_multiple_roles})) {

		my $id_role = sql_select_scalar (<<EOS, $user -> {id}, $_REQUEST {role});
			SELECT
				roles.id
			FROM
				roles,
				map_roles_to_users
			WHERE
				map_roles_to_users.id_user = ?
				AND roles.id=map_roles_to_users.id_role
				AND roles.name = ?
EOS

		$user -> {role} = $_REQUEST {role} if ($id_role);

		if ($id_role && $id_role != $user -> {id_role}) {
			sql_do ("UPDATE users SET id_role = ? WHERE id = ? ", $id_role, $user -> {id});
			$user -> {id_role} = $id_role;
		}
	}

	$user -> {label} ||= $user -> {name} if $user;

	return $user;

}

################################################################################

sub delete_fakes {
	my ($table_name) = @_;
	$table_name ||= $_REQUEST {type};
	my @sids = (0, sql_select_col ("SELECT id FROM sessions WHERE id <> ?", $_REQUEST {sid}));
	sql_do ("DELETE FROM $table_name WHERE fake NOT IN (" . (join ', ', @sids) . ')');
}

################################################################################

sub interpolate {
	my $template = $_[0];
	my $result = '';
	my $code = "\$result = <<EOINTERPOLATION\n$template\nEOINTERPOLATION";
	eval $code;
	$result .= $@;
	return $result;
}

################################################################################

sub get_filehandle {
#	return $q -> upload ($_[0]);
	return $apr -> upload ($_[0]) -> fh;
}

################################################################################

sub redirect {
#	print $q -> redirect ($_[0]);
	$r -> internal_redirect ($_[0]);
}

################################################################################

sub log_action {

	my ($id_user, $type, $action, $error, $id) = @_;

	my $ip = $ENV {REMOTE_ADDR};
	my $ip_fw = $ENV {HTTP_X_FORWARDED_FOR};

	sql_do ("INSERT INTO log (id_user, type, action, error, params, ip, id_object, ip_fw) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", $id_user, $type, $action, $error, Data::Dumper -> Dump ([\%_REQUEST], ['_REQUEST']), $ip, $id, $ip_fw);

}

################################################################################

sub delete_file {

	unlink $r -> document_root . $_[0];

}

################################################################################

sub select__static_files {

	$r -> filename =~ /\w+\.\w+/;

	my $filename = $&;

	my $content_type =
		$r -> filename =~ /\.js/ ? 'application/x-javascript' :
		$r -> filename =~ /\.css/ ? 'text/css' :
		$r -> filename =~ /\.htm/ ? 'text/html' :
		'application/octet-stream';

	my $path = $STATIC_ROOT . $filename . '.gz.pm';
	(-f $path and $r -> header_in ('Accept-Encoding') =~ /gzip/) or $path = $STATIC_ROOT . $filename . '.pm';

	$r -> header_out ('Content-Type' => $content_type);
	$r -> header_out ('Content-Encoding' => 'gzip') if $path =~ /\.gz/;
	$r -> header_out ('Content-Length' => -s $path);
	$r -> header_out ('Cache-Control' => 'max-age=' . 24 * 60 * 60);

	$r -> send_http_header ();

	open (F, $path) or die ("Can't open $path: $!\n");
	$r -> send_fd (F);
	close (F);

	$_REQUEST {__response_sent} = 1;

}

################################################################################

sub download_file {

	my ($options) = @_;

	$options -> {type} ||= 'application/octet-stream';

	$r -> status (200);

	$options -> {file_name} =~ s{.*\\}{};

	$options -> {type} .= '; charset=' . $options -> {charset} if $options -> {charset};

	my $path = $r -> document_root . $options -> {path};

	my $start = 0;
	my $content_length = -s $path;
	my $range_header = $r -> header_in ("Range");
	if ($range_header =~ /bytes=(\d+)/) {
		$start = $1;
		my $finish = $content_length - 1;
		$r -> header_out ('Content-Range', "bytes $start-$finish/$content_length");
		$content_length -= $start;
	}

	$r -> content_type ($options -> {type});
	$options -> {no_force_download} or $r -> header_out ('Content-Disposition' => "attachment;filename=" . $options -> {file_name});
	$r -> header_out ('Content-Length' => $content_length);
	$r -> header_out ('Accept-Ranges' => 'bytes');

	$r -> send_http_header ();

	open (F, $path) or die ("Can't open file $path: $!");
	seek (F, $start, 0);
	$r -> send_fd (F);
	close F;

	$_REQUEST {__response_sent} = 1;

}

################################################################################

sub upload_file {

	my ($options) = @_;

	my $upload = $apr -> upload ('_' . $options -> {name});

	return undef unless ($upload and $upload -> size);

	my $fh = $upload -> fh;

	my $path = "/i/$$options{dir}/" . time . '-' . $$;

	my $real_path = $r -> document_root . $path;

	open (OUT, ">$real_path") or die "Can't write to $real_path: $!";
	binmode OUT;

	my $buffer = '';
	my $file_length = 0;
	while (my $bytesread = read ($fh, $buffer, 1024)) {
		$file_length += $bytesread;
		print OUT $buffer;
	}
	close (OUT);

	my $filename = $upload -> filename;
	$filename =~ s{.*\\}{};

	return {
		file_name => $filename,
		size      => $upload -> size,
		type      => $upload -> type,
		path      => $path,
		real_path => $real_path,
	}

}

################################################################################

sub add_vocabularies {

	my ($item, @names) = @_;

	map {$item -> {$_} = sql_select_vocabulary ($_)} @names;

}

1;</pre>
<h1>./Zanas/Presentation.pm</h1>
<pre>no warnings;

use Data::Dumper;
use URI::Escape;

use Zanas::Presentation::MSIE_5;
#use Zanas::Presentation::Mozilla_3;
use Zanas::Presentation::Unsupported;

################################################################################

sub dump_attributes {
	my $attributes = $_[0];
	return join ' ', map {"$_='" . $attributes -> {$_} . "'"} keys %$attributes;
}

################################################################################

sub trunc_string {
	my ($s, $len) = @_;
	return length $s <= $len - 3 ? $s : substr ($s, 0, $len - 3) . '...';
}

################################################################################

sub create_url {

	my %over = @_;
	my %param = %_REQUEST;
	$over {password} = '';
	while (my ($key, $value) = each %over) {
		$param {$key} = $value;
	}

	return '/?' . join ('&', map {($_ !~ /^_/ || $_ eq '__no_navigation') && $param {$_} ? ($_ . '=' . uri_escape ($param {$_})) : ()} keys %param);

}

################################################################################

sub hrefs {
	my ($order) = @_;

	return $order ? (
		href      => create_url (order => $order, desc => 0),
		href_asc  => create_url (order => $order, desc => 0),
		href_desc => create_url (order => $order, desc => 1),
	) : ();
}

################################################################################

sub headers {

	my @result = ();

	while (@_) {

		my $label = shift;
		$label =~ s/_/ /g;

		my $order;
		$order = shift if $label ne ' ';

		push @result, {label => $label, hrefs ($order)};

	}

	return \@result;

}

################################################################################

sub order {

	my $default = shift;
	my $result;

	while (@_) {
		my $name  = shift;
		my $sql   = shift;
		$name eq $_REQUEST {order} or next;
		$result   = $sql;
		last;
	}

	$result ||= $default;

	if ($_REQUEST {desc}) {

		$result .= ',';
		$result =~ s/\s+/ /g;
		$result =~ s/ \,/\,/g;
		$result =~ s/([^(ASC|DESC)])\,/$1 ASC\,/g;
		$result =~ s/ DESC\,/ BCSC\,/g;
		$result =~ s/ ASC\,/ DESC\,/g;
		$result =~ s/ BCSC\,/ ASC\,/g;

		chop $result;

	}

	return $result;

}

################################################################################

sub check_title {

	my ($options) = @_;

	$options -> {title} ||= $options -> {label};
	$options -> {title} =~ s{\"}{\"\;}g;
	$options -> {title} = qq{title="$$options{title}"} if length $options -> {title};

}

################################################################################

sub check_href {

	my ($options) = @_;

	if (ref $options -> {href} eq HASH) {
		$options -> {href} = create_url (%{$options -> {href}});
	}

	if ($options -> {href} !~ /^(\#|java|\/i\/)/ and $options -> {href} !~ /[\&\?]sid=/) {
		$options -> {href} .= "\&sid=$_REQUEST{sid}";
		$options -> {href} .= '&_salt=' . rand;
	}

	if ($_REQUEST{period} and $options -> {href} !~ /^(\#|java)/ and $options -> {href} !~ /\&period=/) {
		$options -> {href} .= "\&period=$_REQUEST{period}";
	}

}

1;
</pre>
<h1>./Zanas/SQL.pm</h1>
<pre>no warnings;

use DBI;

use Data::Dumper;
use DBIx::ModelUpdate;

################################################################################

sub sql_do {
	my ($sql, @params) = @_;
	my $st = $db -> prepare ($sql);
	$st -> execute (@params);
	$st -> finish;
}

################################################################################

sub sql_select_all_cnt {

	my ($sql, @params) = @_;

#	$sql =~ s{SELECT}{SELECT SQL_CALC_FOUND_ROWS}i;

#print STDERR $sql;

	my $st = $db -> prepare ($sql);
	$st -> execute (@params);
	my $result = $st -> fetchall_arrayref ({});
	$st -> finish;

#	my $cnt = $db -> selectrow_array ("select found_rows()");

	$sql =~ s{SELECT.*?FROM}{SELECT COUNT(*) FROM}ism;
	if ($sql =~ s{LIMIT.*}{}ism) {
#		pop @params;
	}
	$st = $db -> prepare ($sql);
	$st -> execute (@params);

	my $cnt = 0;
	if ($sql =~ /GROUP\s+BY/i) {
		$cnt++ while $st -> fetch ();
	}
	else {
		$cnt = $st -> fetchrow_array ();
	}

	return ($result, $cnt);

}

################################################################################

sub sql_select_all {

	my ($sql, @params) = @_;
	my $st = $db -> prepare ($sql);
	$st -> execute (@params);
	my $result = $st -> fetchall_arrayref ({});
	$st -> finish;

	return $result;

}

################################################################################

sub sql_select_col {

	my ($sql, @params) = @_;

	my @result = ();
	my $st = $db -> prepare ($sql);
	$st -> execute (@params);
	while (my @r = $st -> fetchrow_array ()) {
		push @result, @r;
	}
	$st -> finish;

	return @result;

}

################################################################################

sub sql_select_vocabulary {

	my ($table_name) = @_;
	return sql_select_all ("SELECT id, label FROM $table_name WHERE fake = 0 ORDER BY label");

}

################################################################################

sub sql_select_hash {

	my ($sql_or_table_name, @params) = @_;

	if (@params == 0 and $sql_or_table_name !~ /^\s*SELECT/i) {

		return sql_select_hash ("SELECT * FROM $sql_or_table_name WHERE id = ?", $_REQUEST {id});

	}

	my $st = $db -> prepare ($sql_or_table_name);
	$st -> execute (@params);
	my $result = $st -> fetchrow_hashref ();
	$st -> finish;

	return $result;

}

################################################################################

sub sql_select_array {

	my ($sql, @params) = @_;
	my $st = $db -> prepare ($sql);
	$st -> execute (@params);
	my @result = $st -> fetchrow_array ();
	$st -> finish;

	return wantarray ? @result : $result [0];

}

################################################################################

sub sql_select_scalar {

	my ($sql, @params) = @_;
	my $st = $db -> prepare ($sql);
	$st -> execute (@params);
	my @result = $st -> fetchrow_array ();
	$st -> finish;

	return $result [0];

}

################################################################################

sub sql_select_path {

	my ($table_name, $id, $options) = @_;

	$options -> {name} ||= 'name';
	$options -> {type} ||= $table_name;
	$options -> {id_param} ||= 'id';

	my ($parent) = $id;

	my @path = ();

	while ($parent) {
		my $r = sql_select_hash ("SELECT id, parent, $$options{name} as name, '$$options{type}' as type, '$$options{id_param}' as id_param FROM $table_name WHERE id = ?", $parent);
		$r -> {cgi_tail} = $options -> {cgi_tail},
		unshift @path, $r;
		$parent = $r -> {parent};
	}

	if ($options -> {root}) {
		unshift @path, {
			id => 0,
			parent => 0,
			name => $options -> {root},
			type => $options -> {type},
			id_param => $options -> {id_param},
			cgi_tail => $options -> {cgi_tail},
		};
	}

	return \@path;

}

################################################################################

sub sql_select_subtree {

	my ($table_name, $id, $options) = @_;

	my @ids = ($id);

	while (TRUE) {

		my $ids = join ',', @ids;

		my @new_ids = sql_select_col ("SELECT id FROM $table_name WHERE parent IN ($ids) AND id NOT IN ($ids)");

		last unless @new_ids;

		push @ids, @new_ids;

	}

	return @ids;

}

################################################################################

sub sql_last_insert_id {
	return 0 + sql_select_array ("SELECT LAST_INSERT_ID()");
}

################################################################################

sub sql_do_update {

	my ($table_name, $field_list, $stay_fake) = @_;
	my $sql = join ', ', map {"$_ = ?"} @$field_list;
	$stay_fake or $sql .= ', fake = 0';
	$sql = "UPDATE $table_name SET $sql WHERE id = ?";
	my @params = @_REQUEST {(map {"_$_"} @$field_list), 'id'};
	sql_do ($sql, @params);

}

################################################################################

sub sql_do_insert {

	my ($table_name, $pairs) = @_;

	my $fields = '';
	my $args   = '';
	my @params = ();

	$pairs -> {fake} = $_REQUEST {sid} unless exists $pairs -> {fake};

	while (my ($field, $value) = each %$pairs) {
		my $comma = @params ? ', ' : '';
		$fields .= "$comma $field";
		$args   .= "$comma ?";
		push @params, $value;
	}

	sql_do ("INSERT INTO $table_name ($fields) VALUES ($args)", @params);

	return sql_last_insert_id ();

}

################################################################################

sub sql_do_delete {

	my ($table_name, $options) = @_;

	if (ref $options -> {file_path_columns} eq ARRAY) {

		map {sql_delete_file ({table => $table_name, path_column => $_})} @{$options -> {file_path_columns}}

	}

	sql_do ("DELETE FROM $table_name WHERE id = ?", $_REQUEST{id});

}

################################################################################

sub sql_delete_file {

	my ($options) = @_;

	my $path = sql_select_array ("SELECT $$options{path_column} FROM $$options{table} WHERE id = ?", $_REQUEST {id});

#print STDERR "sql_delete_file: unlinking '$path'\n";

	unlink $path;

#print STDERR "sql_delete_file: '$path' unlinked\n";

}

################################################################################

sub sql_download_file {

	my ($options) = @_;

	my $r = sql_select_hash ("SELECT * FROM $$options{table} WHERE id = ?", $_REQUEST {id});
	$options -> {path} = $r -> {$options -> {path_column}};
	$options -> {type} = $r -> {$options -> {type_column}};
	$options -> {file_name} = $r -> {$options -> {file_name_column}};

	download_file ($options);

}

################################################################################

sub sql_upload_file {

	my ($options) = @_;

print STDERR "sql_upload_file: purge started\n";

	sql_delete_file ($options);

print STDERR "sql_upload_file: purge finished\n";

	my $uploaded = upload_file ($options) or return;

	my (@fields, @params) = ();

	foreach my $field (qw(file_name size type path)) {
		my $column_name = $options -> {$field . '_column'} or next;
		push @fields, "$column_name = ?";
		push @params, $uploaded -> {$field};
	}

	foreach my $field (keys (%{$options -> {add_columns}})) {
		push @fields, "$field = ?";
		push @params, $options -> {add_columns} -> {$field};
	}

	@fields or return;

	my $tail = join ', ', @fields;

	sql_do ("UPDATE $$options{table} SET $tail WHERE id = ?", @params, $_REQUEST {id});

	return $uploaded;

}

################################################################################

sub sql_reconnect {

	return if $db and $db -> ping;

	$conf = {%$conf, %$preconf};

	our $db  = DBI -> connect ($conf -> {'db_dsn'}, $conf -> {'db_user'}, $conf -> {'db_password'}, {RaiseError => 1});

	our $model_update = DBIx::ModelUpdate -> new ($db, dump_to_stderr => 1);

}

1;</pre>
<h1>./Zanas/Util.pm</h1>
<pre>################################################################################

sub status_switch {

	my ($sql) = @_;

	my $status = {};

	foreach my $line (split /\n/, $sql) {
		$line =~ /(\d+)\s*\#\s*(.*)/;
		$status -> {$1} = $2;
	}

	return ($sql, $status);

}

1;</pre>
<h1>./Zanas/Docs.pm</h1>
<pre>package Zanas::Docs;

package main;

@langs = qw(en ru);

################################################################################

@options = (

	{
		name     => 'code',
		label_en => "Keyboard scan code. Can be set as /F(\d+)/ for function keys.",
		label_ru => "Клавиатурный scan code. Для функциональных клавиш может быть задан как /F(\d+)/.",
	},

	{
		name     => 'data',
		label_en => "ID attibute of an A tag to activate with the hotkey.",
		label_ru => "Атрибут ID тега A, который требуется активизировать при нажатии на горячую клавишу.",
	},

	{
		name     => 'ctrl',
		label_en => "If true, Ctrl key must be pressed.",
		label_ru => "Если истина, требуется нажатие на Ctrl.",
	},

	{
		name     => 'alt',
		label_en => "If true, Alt key must be pressed.",
		label_ru => "Если истина, требуется нажатие на Alt.",
	},

	{
		name     => 'no_force_download',
		label_en => "Unless true, the 'File download' ialog is forced on the client.",
		label_ru => "Если не true, то на клиенте должен появиться диалог открытия файла.",
	},

	{
		name     => 'file_name',
		label_en => "File name as shown to the client.",
		label_ru => "Имя файла для клиента.",
	},

	{
		name     => 'file_path_columns',
		label_en => "Listref of names of column that contain attached file paths.",
		label_ru => "Список имён полей, содержащих пути прикреплённых файлов.",
	},

	{
		name     => 'table',
		label_en => 'Table name.',
		label_ru => 'Имя таблицы.',
	},

	{
		name     => 'dir',
		label_en => 'Directory name to store the file, relative to DocumentRoot.',
		label_ru => 'Имя директории для записи файла, относительно DocumentRoot.',
	},

	{
		name     => 'path',
		label_en => 'File path, relative to DocumentRoot.',
		label_ru => 'Путь к файлу, относительно DocumentRoot.',
	},

	{
		name     => 'size_column',
		label_en => 'Name of the column that contain file size.',
		label_ru => 'Имя поля, содержащего обём прикреплённого файла.',
	},

	{
		name     => 'path_column',
		label_en => 'Name of the column that contain file path.',
		label_ru => 'Имя поля, содержащего путь прикреплённого файла.',
	},

	{
		name     => 'type_column',
		label_en => 'Name of the column that contain file MIME type.',
		label_ru => 'Имя поля, содержащего MIME-тип прикреплённого файла.',
	},

	{
		name     => 'file_name_column',
		label_en => 'Name of the column that contain file name.',
		label_ru => 'Имя поля, содержащего имя прикреплённого файла.',
	},

	{
		name     => 'label',
		label_en => "Visible text displayed in the element's area.",
		label_ru => "Видимый текст, отображаемый элементом.",
	},

	{
		name     => 'off',
		label_en => "If true, the element is not drawn at all",
		label_ru => "Если true, то HTML-код елемента не генерируется",
	},

	{
		name     => '..',
		label_en => "If true and the path is present on the page, the first table row is the reference to the previous level of the path (like '..' in file system).",
		label_ru => "Если true и на странице есть path, то первая строка таблицы ссылается на предпоследний элемент path (как '..' в файловой системе)",
	},

	{
		name     => 'height',
		label_en => "Height, in pixels",
		label_ru => "Высота, в пикселях",
	},

	{
		name     => 'class',
		label_en => "CSS class name",
		label_ru => "Имя CSS-класса",
	},

	{
		name     => 'read_only',
		label_en => "If true, text inputs are replaced by static text + hidden inputs",
		label_ru => "Если true, то текстовые поля ввода превращаются в статический текст + скрытые (hidden) поля",
	},

	{
		name     => 'max_len',
		label_en => "Maximum length for the displayed text. If oversized, the text is truncated and '...' is appended",
		label_ru => "Максимальная длина выводимого текста. При превышении текст обрезается, и к нему приписывается '...'",
	},

	{
		name     => 'size',
		label_en => "Size of the input field",
		label_ru => "Длина поля текстового ввода",
	},

	{
		name     => 'attributes',
		label_en => "additional HTML attributes for the corresponding TD tag",
		label_ru => "дополнительные HTML-атрибуты, дописываемые в тег TD",
	},

	{
		name     => 'a_class',
		label_en => "CSS class name for A tag",
		label_ru => "Имя CSS-класса для тега A",
	},

	{
		name     => 'name',
		label_en => "Input or form name",
		label_ru => "Имя поля ввода или всей формы",
	},

	{
		name     => 'type',
		label_en => "The value for the hidden input named 'type'",
		label_ru => "Значение, передаваемое невидимым полем ввода 'type'",
	},

	{
		name     => 'id',
		label_en => "The value for the hidden input named 'id'",
		label_ru => "Значение, передаваемое невидимым полем ввода 'id'",
	},

	{
		name     => 'action',
		label_en => "The value for the hidden input named 'action'",
		label_ru => "Значение, передаваемое невидимым полем ввода 'action'",
	},

	{
		name     => 'toolbar',
		label_en => "The toolbar on bottom of the table (inside its FORM tag)",
		label_ru => "Панель с кнопками внизу таблицы (внутри соответствующего тега FORM)",
	},

	{
		name     => 'js_ok_escape',
		label_en => "If true, the Ctrl+Enter and Esc keys will submit/escape the current form",
		label_ru => "Если true, то Ctrl+Enter и Esc обрабатываются для этой формы",
	},

	{
		name     => 'checked',
		label_en => "If true, the checkbox is on",
		label_ru => "Если true, checkbox отмечен",
	},

	{
		name     => 'value',
		label_en => "The input's value",
		label_ru => "Значение элемента управления",
	},

	{
		name     => 'hidden_value',
		label_en => "The hidden input's value",
		label_ru => "Значение элемента управления типа hidden",
	},

	{
		name     => 'id_image',
		label_en => "The hidden input's value",
		label_ru => "Значение элемента управления типа hidden",
	},

	{
		name     => 'hidden_name',
		label_en => "The hidden input's name",
		label_ru => "Имя элемента управления типа hidden",
	},

	{
		name     => 'picture',
		label_en => "The picture for numeric data (see Number::Format)",
		label_ru => "Формат числовых значений (см. Number::Format)",
	},

	{
		name     => 'href',
		label_en => "URL pointed by the element (HREF attribute of the A tag). Magic parameters 'sid' and 'salt' are appended automatically. See <a href='check_href.html'>check_href</a>, <a href='create_url.html'>create_url</a>",
		label_ru => "URL, на который ссылается данный элемент (атрибут HREF тега A). Магические параметры 'sid' и 'salt' приписываются автоматически. См. также <a href='check_href.html'>check_href</a>, <a href='create_url.html'>create_url</a>",
	},

	{
		name     => 'target',
		label_en => "The target window/frame (TARGET attribute of the A tag)",
		label_ru => "Целевое окно/фрейм ссылки (атрибут TARGET тега A)",
	},

	{
		name     => 'icon',
		label_en => "Reserved",
		label_ru => "Зарезервировано",
	},

	{
		name     => 'confirm',
		label_en => "The confirmation text",
		label_ru => "Текст запроса на подтверждение действия",
	},

	{
		name     => 'multiline',
		label_en => "If true, multiline mode is on",
		label_ru => "Если true, то отрисовка многострочная",
	},

	{
		name     => 'id_param',
		label_en => "Name of the param which value must be set to the current object ID",
		label_ru => "Имя параметра, в качестве значения которого должен быть передан ID текущего объекта",
	},

	{
		name     => 'keep_params',
		label_en => "REQUEST params to be inherited.",
		label_ru => "Список параметров запроса, которые требуется унаследовать.",
	},

	{
		name     => 'cnt',
		label_en => "Number of table rows on the current page (with START/LIMIT clause).",
		label_ru => "Число строк таблицы на текущей странице (с учётом START/LIMIT).",
	},

	{
		name     => 'total',
		label_en => "Total number of table rows (without START/LIMIT clause).",
		label_ru => "Число строк таблицы на текущей странице (без учёта START/LIMIT).",
	},

	{
		name     => 'portion',
		label_en => "Maximum number of table rows on one page (LIMIT value)",
		label_ru => "Максимальное число строк таблицы на одной странице (LIMIT)",
	},

	{
		name     => 'bottom_toolbar',
		label_en => "Toolbar on bottom of the form",
		label_ru => "Панель с копками внизу формы ввода",
	},

	{
		name     => 'format',
		label_en => "Date/time format, for example '%d.%m.%Y %k:%M'",
		label_ru => "Формат даты/времени, например, '%d.%m.%Y %k:%M",
	},

	{
		name     => 'no_time',
		label_en => "If true, no time is selected, only date",
		label_ru => "Если true, редактируется только дата, но не время",
	},

	{
		name     => 'onClose',
		label_en => "JavaScript code handling for the 'onClose' event",
		label_ru => "JavaScript-код обработчика события 'onClose'",
	},

	{
		name     => 'onChange',
		label_en => "JavaScript code handling for the 'onChange' event",
		label_ru => "JavaScript-код обработчика события 'onChange'",
	},

	{
		name     => 'onclick',
		label_en => "JavaScript code handling for the 'onclick' event",
		label_ru => "JavaScript-код обработчика события 'onclick'",
	},

	{
		name     => 'items',
		label_en => "Listref containing subelement definitions",
		label_ru => "Ссылка на список, содержащий описания подэлементов",
	},

	{
		name     => 'src',
		label_en => "Value of SRC attribute of IMG tag (image URL)",
		label_ru => "Значение атрибута SRC тега IMG (адрес изображения)",
	},

	{
		name     => 'new_image_url',
		label_en => "Path to image selection dialog box",
		label_ru => "Адрес страницы выбора изображения",
	},

	{
		name     => 'rows',
		label_en => "Value of ROWS attribute of TEXTAREA tag (textarea height)",
		label_ru => "Значение атрибута ROWS тега TEXTAREA (высота редактора)",
	},

	{
		name     => 'width',
		label_en => "Value of WIDTH attribute.",
		label_ru => "Значение атрибута WIDTH (ширина)",
	},

	{
		name     => 'height',
		label_en => "Value of HEIGHT attribute",
		label_ru => "Значение атрибута HEIGHT (высота)",
	},

	{
		name     => 'cols',
		label_en => "Value of COLS attribute of TEXTAREA tag (textarea width)",
		label_ru => "Значение атрибута COLS тега TEXTAREA (ширина редактора)",
	},

	{
		name     => 'values',
		label_en => "Data dictionnary for a field: arrayref of hashrefs with fields 'id' (possible field value) and 'label' (displayed). In 'checkboxes' field, hashrefs can contain 'items' elements referring to similar arrays, in this case, the tree is displayed.",
		label_ru => "Словарь данных для поля редактирования: список хэшей с ключами 'id' (возможное значение поля) и 'label' (видимая метка). Для поля типа 'checkboxes' хэши могут содержать элементы 'items' со ссылками на аналогичные массивы: в этом случае отрисовывается дерево.",
	},

	{
		name     => 'empty',
		label_en => "Label corresponding to a non-positive value (first in list), like '[no value]', '<Choose sometyhing!>' etc.",
		label_ru => "Надпись, соответствующая неположительному значению поля, появляется первой в списке. Как правило, это '[не определено]', '<Выберите!>' и т. п.",
	},

	{
		name     => 'esc',
		label_en => "URL referenced by the Escape button (also opened when pressing hardware Esc)",
		label_ru => "Ссылка с кнопки 'выход', открываемая также при нажатии на клавишу Esc",
	},

	{
		name     => 'back',
		label_en => "URL referenced by the Back button (also opened when pressing hardware Esc)",
		label_ru => "Ссылка с кнопки 'назад', открываемая также при нажатии на клавишу Esc",
	},

	{
		name     => 'additional_buttons',
		label_en => "Additional buttons definitions",
		label_ru => "Описания дополнительных кнопок",
	},

	{
		name     => 'label_ok',
		label_en => "Label for the OK button",
		label_ru => "Надпись на кнопке OK",
	},

	{
		name     => 'label_cancel',
		label_en => "Label for the Cancel button",
		label_ru => "Надпись на кнопке Cancel",
	},

	{
		name     => 'no_ok',
		label_en => "If true and 'bottom_toolbar' is undefined then draw_esc_toolbar is invoked instead of draw_ok_esc_toolbar.",
		label_ru => "Если true и 'bottom_toolbar' неопределена, то вместо draw_ok_esc_toolbar вызывается draw_esc_toolbar.",
	},

	{
		name     => 'root',
		label_en => "Supplementary path record inserted before the first one",
		label_ru => "Дополнительная запись path, предшествующая всем осальным.",
	},

);

################################################################################

@subs = (

					#######################################

	{
		name     => 'create_url',
#		options  => [qw()],
		syn      => <<EO,
	create_url (
		type => 'some_other_type',
	);

	# /?type=my_type&id=1&sid=123456&_foo=bar --> /?type=some_other_type&id=1&sid=123456

EO
		label_en => 'Creates the URL inheriting all parameter values but explicitely set and starting with one underscore. Automatically applied to any HASHREF valued "href" option.',
		label_ru => 'Генерирует URL, наследующий значения всех параметров, кроме упомянутых в списке аргументов и тех, чьи имена начинаются с символа \'_\'. Неявно применяется ко всем значениям опции "href", которые заданы как ссылка на хэш',
		see_also => [qw(check_href)],
	},

					#######################################

	{
		name     => 'check_title',
#		options  => [qw()],
		syn      => <<EO,
	check_title ({
		...
		label => 'project "Y"',
		...
	});

	# --> {
	# ...
	# label => 'project "Y"',
	# title => 'title="project &quot;Y&quot;"'
	# ...
	# }

EO
		label_en => 'Adds a properly quoted TITLE tag to options hashref. Defaults to label option.',
		label_ru => 'Добавляет в опции HTML-код для атрибута TITLE. Значение по умолчанию берётся из опции label.',
		see_also => [qw(create_url)],
	},

					#######################################

	{
		name     => 'check_href',
#		options  => [qw()],
		syn      => <<EO,
	check_href ({
		...
		href => "/?type=users",
		...
	});

	# /?type=users --> /?type=users&sid=3543522543214387&_salt=0.635735452454

EO
		label_en => 'Ensures the sid parameter inheritance (session support through URL rewriting) and _salt parameter randomness (prevents from client side cacheing). Automatically applied to any option set with scalar "href" option.',
		label_ru => 'Обеспечивает наследование параметра sid (сессии через URL rewriting) и случайность параметра _salt (защипа от кэширования на клиенте). Неявно применяется ко всем наборам опциий со скалярной "href"',
		see_also => [qw(create_url)],
	},


					#######################################

	{
		name     => 'hotkeys',
		options  => [qw(code data ctrl alt off)],
		syn      => <<EO,
	hotkeys (
		{
			code => F4,
			data => 'edit_button',
			off  => \$_REQUEST {edit},
		},
		{
			code => F10,
			data => 'ok',
			off  => \$_REQUEST {__read_only},
		},
	);
EO
		label_en => 'Setting hotkeys for anchors with known IDs.',
		label_ru => 'Установка клавиатурных ускорителей для гиперссылок с известными атрибутами ID.',
#		see_also => [qw()],
	},

					#######################################

	{
		name     => 'delete_fakes',
		syn      => <<EO,
	delete_fakes ('users');
EO
		label_en => 'Garbage collection: delete fake records which belong to non-active sessions. Automatically invoked before any "do_create_$type" callback sub.',
		label_ru => 'Сборка мусора: удаление всех fake-записей, принадлежащих неактивным сессиям. Автоматически вызывается перед каждой callback-процедурой "do_create_$type".',
#		see_also => [qw()],
	},

					#######################################

	{
		name     => 'download_file',
		options  => [qw(file_name path no_force_download)],
		syn      => <<EO,
	download_file ({
		file_name         => 'report.doc',
		path              => '/i/upload/misc/543543545735-5455',
		no_force_download => 1,
	});
EO
		label_en => 'Sends the file response to the client.',
		label_ru => 'Инициирует загрузку файла на клиент.',
		see_also => [qw(sql_download_file upload_file)],
	},

					#######################################

	{
		name     => 'sql_do_update',
		syn      => <<EO,
	sql_do_update ('users', ['name', 'login']);
EO
		label_en => 'Updates the record with id = $_REQUEST {id} in the table which name is the 1st argument. Updated fields are listed in the 2nd argument. The value for each field $f is $_REQUEST {"_$f"}. Moreover, the "fake" field is set to 0 unless the 3rd argument is true.',
		label_ru => 'Обновляет запись c id = $_REQUEST {id} в таблице, имя которой есть 1-й аргумент. Список обновляемых полей -- 2-й аргумент. Значение каждого поля $f определяется как $_REQUEST {"_$f"}. В поле "fake" записывается 0, если только не является истиной 3-й аргумент.',
		see_also => [qw(sql_do_insert sql_do_delete)]
	},

					#######################################

	{
		name     => 'sql_do_insert',
		syn      => <<EO,
	sql_do_insert ('users', {
		name1	=> 'No',
		name2	=> 'Name',
	});
EO
		label_en => 'Inserts a new record in the table and returns its ID. Unless the "fake" value is set, it defaults to $_REQUEST {sid}.',
		label_ru => 'Вставляет новую запись в таблицу и возвращает её номер. Если значение "fake" не задано, оно принимается равным $_REQUEST {sid}.',
		see_also => [qw(sql_do_update sql_do_delete)]
	},

					#######################################

	{
		name     => 'upload_file',
		options	 => [qw(name dir)],
		syn      => <<EO,
	my \$file = sql_upload_file ({
		name             => 'photo',
		dir		 => 'i/upload/user_photos'
	});

#	{
#		file_name => 'C:\sample.jpg',
#		size      => 86219,
#		type      => 'image/jpeg',
#		path      => 'i/upload/user_photos/57387635438-3543',
#		real_path => '/var/virtualhosts/myapp/docroot/i/upload/user_photos/57387635438-3543'
#	};

EO
		label_en => 'Uploads the file.',
		label_ru => 'Загружает файл на сервер.',
		see_also => [qw(sql_upload_file download_file)],
	},


					#######################################

	{
		name     => 'sql_upload_file',
		options	 => [qw(name dir table path_column type_column file_name_column size_column)],
		syn      => <<EO,
	sql_upload_file ('users', {
		name             => 'photo',
		table            => 'users',
		dir		 => 'i/upload/user_photos'
		path_column      => 'path_photo',
		type_column      => 'type_photo',
		file_name_column => 'flnm_photo',
		size_column      => 'size_photo',
	});
EO
		label_en => 'Uploads the file and stores its info in the table.',
		label_ru => 'Загружает файл еа сервер и записывает его данные в таблицу.',
		see_also => [qw(upload_file sql_download_file)],
	},

					#######################################

	{
		name     => 'sql_download_file',
		options	 => [qw(path_column type_column file_name_column)],
		syn      => <<EO,
	sql_download_file ('users', {
		path_column      => 'path_photo',
		type_column      => 'type_photo',
		file_name_column => 'flnm_photo',
	});
EO
		label_en => 'Sends the file download response. The file info is fetched from the table record with with id = $_REQUEST {id}.',
		label_ru => 'Инициирует загрузку файла на клиент. Информация о файле берётся из записи таблицы с id = $_REQUEST {id}.',
		see_also => [qw(sql_upload_file)],
	},


					#######################################

	{
		name     => 'sql_do_delete',
		syn      => <<EO,
	sql_do_delete ('users', {
		file_path_columns => ['path_photo'],
	});
EO
		label_en => 'Deletes the record with id = $_REQUEST {id} in the table which name is the 1st argument. With all attached files, if any.',
		label_ru => 'Удаляет из таблицы запись с id = $_REQUEST {id}. Если заданы file_path_columns, то стирает соответствующие файлы.',
		see_also => [qw(sql_do_update sql_do_insert)]
	},

					#######################################

	{
		name     => 'sql_last_insert_id',
		syn      => <<EO,
	my $id = sql_last_insert_id;
EO
		label_en => 'Fetches the last INSERT ID. Usually you should not call this sub directly. Use the sql_do_insert return value instead.',
		label_ru => 'Возвращает последний сгенерированный ID. Как правило, вместо этой функции желатенльно использовать значение, вычисляемое sql_do_insert.',
		see_also => [qw(sql_do_insert)]
	},

					#######################################

	{
		name     => 'add_vocabularies',
		syn      => <<EO,
	\$item -> add_vocabularies ('roles', 'departments', 'sexes');
EO
		label_en => 'Add multiple data vocabularies simultanuousely.',
		label_ru => 'Добавляет к объекту сразу несколько словарей данных.',
		see_also => [qw(sql_select_vocabulary)]
	},

					#######################################

	{
		name     => 'sql_do',
		syn      => <<EO,
	sql_do ('INSERT INTO my_table (id, name) VALUES (?, ?)', \$id, \$name);
EO
		label_en => 'Executes the DML statement with the given arguments.',
		label_ru => 'Исполняет оператор DML с заданными аргументами.',
#		see_also => [qw()]
	},

					#######################################

	{
		name     => 'sql_select_all_cnt',
		syn      => <<EOP,
	my (\$rows, \$cnt)= sql_select_all_cnt (<<EOS, ...);
		SELECT
			...
		FROM
			...
		WHERE
			...
		ORDER BY
			...
		LIMIT
			\$start, 15
EOS
EOP
		label_en => 'Executes a given SQL (SELECT) statement with supplied parameters and returns the resultset (listref of hashrefs) and the number of rows in the corresponding selection without the LIMIT clause.',
		label_ru => 'Исполняет оператор SQL с заданными аргументами и возвращает выборку (список хэшей), а также объём выборки без учёта ограничителя LIMIT.',
#		see_also => [qw()]
	},

					#######################################

	{
		name     => 'sql_select_all',
		syn      => <<EOP,
	my \$rows = sql_select_all (<<EOS, ...);
		SELECT
			...
		FROM
			...
		WHERE
			...
		ORDER BY
			...
EOS
EOP
		label_en => 'Executes a given SQL (SELECT) statement with supplied parameters and returns the resultset (listref of hashrefs).',
		label_ru => 'Исполняет оператор SQL с заданными аргументами и возвращает выборку (список хэшей).',
#		see_also => [qw()]
	},

					#######################################

	{
		name     => 'sql_select_col',
		syn      => <<EOP,
	my \@col = sql_select_col (<<EOS, ...);
		SELECT
			id
		FROM
			...
		WHERE
			...
EOS
EOP
		label_en => 'Executes a given SQL (SELECT) statement with supplied parameters and returns the first column of the resultset (list).',
		label_ru => 'Исполняет оператор SQL с заданными аргументами и возвращает первый столбец выборки (список).',
#		see_also => [qw()]
	},

					#######################################

	{
		name     => 'sql_select_array',
		syn      => <<EOP,
	my \$r = sql_select_array (<<EOS, ...);
		SELECT
			...
		FROM
			...
		WHERE
			id = ?
EOS
EOP
		label_en => 'Executes a given SQL (SELECT) statement with supplied parameters and returns the first record of the resultset (array, not arrayref).',
		label_ru => 'Исполняет оператор SQL с заданными аргументами и возвращает первую запись выборки (список, не по ссылке).',
#		see_also => [qw()]
	},

					#######################################

	{
		name     => 'sql_select_scalar',
		syn      => <<EOP,
	my \$label = sql_select_scalar (<<EOS, ...);
		SELECT
			label
		FROM
			...
		WHERE
			id = ?
EOS
EOP
		label_en => 'Executes a given SQL (SELECT) statement with supplied parameters and returns the first field of the first record of the resultset (scalar).',
		label_ru => 'Исполняет оператор SQL с заданными аргументами и возвращает первое поле первой записи выборки (скаляр).',
#		see_also => [qw()]
	},

					#######################################

	{
		name     => 'sql_select_path',
		options  => [qw(id_param/id root)],
		syn      => <<EOP,
	\$item -> {path} = sql_select_path ('rubrics', \$_REQUEST {id}, {
		id_param => 'parent',
		root     => {
			type => 'my_objects',
			name => 'All my objects',
			id   => ''
		}
	});
EOP
		label_en => 'Fetches the path to the current object from the hierarchical (PREV id = parent) table in the form suitable for draw_path sub.',
		label_ru => 'Извлекает путь к текущей записи в иерархической (PREV id = parent) таблице в форме, пригодной для передачи процедуре draw_path.',
		see_also => [qw(draw_path sql_select_subtree)]
	},

					#######################################

	{
		name     => 'sql_select_subtree',
#		options  => [qw()],
		syn      => <<EOP,
	my \@child_rubrics = sql_select_subtree ('rubrics', \$_REQUEST {id});
EOP
		label_en => 'Fetches all the child IDs from the hierarchical (PREV id = parent) table as an array.',
		label_ru => 'Извлекает все дочерние ID из иерархической (PREV id = parent) таблицы в виде массива',
		see_also => [qw(sql_select_path)]
	},

					#######################################

	{
		name     => 'sql_select_hash',
		syn      => <<EOP,

	my \$r = sql_select_hash (<<EOS, $_REQUEST {id});
		SELECT
			*
		FROM
			users
		WHERE
			id = ?
EOS

	my \$user = sql_select_hash ('users');

EOP
		label_en => 'Executes a given SQL (SELECT) statement with supplied parameters and returns the first record of the resultset (hashref). If all fields belong to the same table and the ID is $_REQUEST {id} then you can use the simplified form: only table name is supplied.',
		label_ru => 'Исполняет оператор SQL с заданными аргументами и возвращает первую запись выборки (хэш). Если в запросе участвует только 1 таблица, а ID совпадает с $_REQUEST {id}, то вместо SQL можно указать только имя таблицы.',
#		see_also => [qw()]
	},

					#######################################

	{
		name     => 'sql_select_vocabulary',
		syn      => <<EOP,
	\$item -> {roles} = sql_select_vocabulary ('roles');
EOP
		label_en => 'Selects all records from a given table where fake=0 ordered by label ascending (data vocabulary).',
		label_ru => 'Выбирает из заданной таблицы все записи, для которых fake=0 в опрядке возрастания label (словарь данных).',
		see_also => [qw(add_vocabularies draw_form_field_radio draw_form_field_select)]
	},



					#######################################

	{
		name     => 'draw_centered_toolbar_button',
		options  => [qw(off href target/_self confirm onclick label)],
		label_en => 'Draws a button on a toolbar. Invoked from "draw_centered_toolbar" sub.',
		label_ru => 'Отрисовывает кнопку на панели снизу от формы ввода. Вызывается из-под "draw_centered_toolbar"',
		see_also => [qw(draw_centered_toolbar)]
	},


					#######################################

	{
		name     => 'draw_centered_toolbar',
		options  => [qw()],
		syn      => <<EO,
	draw_centered_toolbar ({}, [
		{
			icon => 'ok',
			label => 'OK',
			href => '#',
			onclick => "document.form.submit()"
		},
		{
			icon => 'cancel',
			label => 'Esc',
			href => '/',
			id => 'esc'
		},
	 ])
EO
		label_en => 'Draws a toolbar on bottom of an input form. Usually you should use draw_ok_esc_toolbar instead.',
		label_ru => 'Отрисовывает панель снизу от формы ввода. Как правило, следует использовать draw_ok_esc_toolbar.',
		see_also => [qw(
			draw_form
			draw_table
			draw_centered_toolbar_button
			draw_back_next_toolbar
			draw_close_toolbar
			draw_esc_toolbar
			draw_ok_esc_toolbar
		)]
	},

					#######################################

	{
		name     => 'draw_back_next_toolbar',
		options  => [qw(additional_buttons back)],
		label_en => 'Draws toolbar with Back and Next buttons. Used in wizards',
		label_ru => 'Отрисовывает панель с кнопками "назад" и "далее". Применяется для пошаговых "мастеров".',
		see_also => [qw(draw_centered_toolbar)]
	},

					#######################################

	{
		name     => 'draw_close_toolbar',
		options  => [qw(additional_buttons)],
		label_en => 'Draws toolbar with a close button. Used in popup windows.',
		label_ru => 'Отрисовывает панель с кнопкой "закрыть". Применяется для всплывающих окон.',
		see_also => [qw(draw_centered_toolbar)]
	},

					#######################################

	{
		name     => 'draw_esc_toolbar',
		options  => [qw(esc additional_buttons)],
		label_en => 'Draws toolbar with an escape button.',
		label_ru => 'Отрисовывает панель с кнопкой "выход"',
		see_also => [qw(draw_centered_toolbar)]
	},

					#######################################

	{
		name     => 'draw_ok_esc_toolbar',
		options  => [qw(name esc/?type=$_REQUEST{type} additional_buttons label_ok/применить label_cancel/вернуться)],
		label_en => 'Draws toolbar with an escape button.',
		label_ru => 'Отрисовывает панель с кнопкой "выход"',
		see_also => [qw(draw_centered_toolbar)]
	},

					#######################################

	{
		name     => 'draw_form',
		options  => [qw(action/update type/$_REQUEST{type} id/$_REQUEST{id} name/form target/invisible bottom_toolbar/draw_ok_esc_toolbar() no_ok)],
		syn      => <<EO,

	my \$data = {				# comes from 'get_item_of_users' callback sub

		id	 => 1,
		name     => 'J. Doe',
		login    => 'scott',
		password => 'tiger',
		id_role  => 1,

		path    => [		# passed to draw_path (see)
			{type => 'users', name => 'Everybody'},
			{type => 'users', name => 'J. Doe', id => 1},
		],

		roles    => [		# vocabulary
			{id => 1, name => 'admin'},
			{id => 2, name => 'user'},
		],

	};

	draw_form ({
			name => 'form1',
			esc  => '/?type=loosers&parent=13',
			additional_buttons => [
				{
					label => 'Disable it',
					href  => "/?type=users&action=disable&id=$$data{id}"
				}
			],
		},

		\$data

		[
			{			# text field -- by default
				name  => 'name',
				label => 'Name',
				size  => 30,
			},
			[			# 2 fields at one line
				{
					name  => 'login',
					mandatory  => 1,
					label => '&login',
					size  => 30,
				},
				{
					name  => 'password',
					label => 'Password',
					type  => 'password',
					size  => 30,
 				},
			],
			{			# drop-down
				name   => 'id_role',
				label  => 'Role',
				type   => 'select',
				values => \$data -> {roles},
			},
		]
	);
EO
		label_en => 'Draws the input form. Individual fields are rendered with "draw_form_field_$type" (default type is "string") subs, see references below. For each input $_, the "value" option defaults to $data -> {$_ -> {name}}. Options are passed to the bottom toolbar rendering subroutine (as usual, draw_ok_esc_toolbar).',
		label_ru => 'Отрисовывает форму ввода данных. Отдельные поля отрисовываются подпрограммами "draw_form_field_$type" (тип по умолчанию -- "string"), см. ссылки ниже. Для каждого поля ввода $_ опция "value" по умолчанию определяется как $data -> {$_ -> {name}}. Опции передаются подрпограмме отрисовки нижней панели с кнопками (обычно draw_ok_esc_toolbar)',
		see_also => [qw(
			draw_ok_esc_toolbar
			draw_form_field_button
			draw_form_field_datetime
			draw_form_field_checkbox
			draw_form_field_checkboxes
			draw_form_field_file
			draw_form_field_image
			draw_form_field_hgroup
			draw_form_field_hidden
			draw_form_field_htmleditor
			draw_form_field_password
			draw_form_field_radio
			draw_form_field_select
			draw_form_field_static
			draw_form_field_string
			draw_form_field_text
		)]
	},

					#######################################

	{
		name     => 'draw_form_field_button',
		options  => [qw(name label onclick)],
		label_en => 'Draws a button. Invoked by draw_form.',
		label_ru => 'Отрисовывает кнопку. Вызывается процедурой draw_form.',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_form_field_htmleditor',
		options  => [qw(name label width height)],
		label_en => 'Draws the WYIWYG HTML editing area (see http://www.fredck.com/FCKeditor/). Invoked by draw_form.',
		label_ru => 'Отрисовывает интерактивный редактор HTML (см. http://www.fredck.com/FCKeditor/). Вызывается процедурой draw_form.',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_form_field_image',
		options  => [qw(name label id_image src width height new_image_url)],
		label_en => 'Draws the image with a button invoking a choose dialog box. Invoked by draw_form.',
		label_ru => 'Отрисовывает картинку и кнопку, вызвыающую диалог выбора нового изображения. Вызывается процедурой draw_form.',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_form_field_select',
		options  => [qw(name label value values off empty max_len onChange height)],
		label_en => 'Draws the drop down listbox. Invoked by draw_form.',
		label_ru => 'Отрисовывает выпадающий список опций. Вызывается процедурой draw_form.',
		see_also => [qw(draw_form sql_select_vocabulary)]
	},

					#######################################

	{
		name     => 'draw_form_field_checkboxes',
		options  => [qw(name label value values off)],
		label_en => 'Draws the group of checkboxes. Invoked by draw_form.',
		label_ru => 'Отрисовывает группу checkbox\'ов. Вызывается процедурой draw_form.',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_form_field_checkbox',
		options  => [qw(name label checked off)],
		label_en => 'Draws the checkbox. Invoked by draw_form.',
		label_ru => 'Отрисовывает поле логигеского ввода (checkbox). Вызывается процедурой draw_form.',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_form_field_radio',
		options  => [qw(name label value values off)],
		label_en => 'Draws the group of radiobuttons. Invoked by draw_form.',
		label_ru => 'Отрисовывает группу радиокнопок. Вызывается процедурой draw_form.',
		see_also => [qw(draw_form sql_select_vocabulary)]
	},

					#######################################

	{
		name     => 'draw_form_field_static',
		options  => [qw(name label value off href values hidden_name hidden_value)],
		label_en => 'Draws the static text in the place of the form input. Used to implement [temporary] read only fields. Invoked by draw_form.',
		label_ru => 'Отрисовывает статический текст на месте поля ввода. Изображает [временно] нередактируемое поле записи. Вызывается процедурой draw_form.',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_form_field_password',
		options  => [qw(name label value off)],
		label_en => 'Draws the password form input. Invoked by draw_form.',
		label_ru => 'Отрисовывает поле ввода пароля. Вызывается процедурой draw_form.',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_form_field_text',
		options  => [qw(name label value off rows/25 cols/60)],
		label_en => 'Draws the textarea form input. Invoked by draw_form.',
		label_ru => 'Отрисовывает многострочное текстовое поле ввода. Вызывается процедурой draw_form.',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_form_field_hgroup',
		options  => [qw(items)],
		label_en => 'Draws the horizontal group of form inputs defined by "items" option. Invoked by draw_form.',
		label_ru => 'Отрисовывает строку полей ввода, описания которых заданы опцией "items". Вызывается процедурой draw_form.',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_form_field_file',
		options  => [qw(name label)],
		label_en => 'Draws the file upload form input. Invoked by draw_form.',
		label_ru => 'Отрисовывает поле ввода для загрузки файла. Вызывается процедурой draw_form.',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_form_field_hidden',
		options  => [qw(name value off)],
		label_en => 'Draws the hidden form input. Invoked by draw_form.',
		label_ru => 'Отрисовывает скрытое поле ввода. Вызывается процедурой draw_form.',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_form_field_string',
		options  => [qw(name label value off size max_len/$$conf{max_len} picture)],
		label_en => 'Draws the text form input. Invoked by draw_form.',
		label_ru => 'Отрисовывает текстовое поле ввода. Вызывается процедурой draw_form.',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_form_field_datetime',
		options  => [qw(name label value off format/$$conf{format_dt} no_time onClose)],
		label_en => 'Draws the calendar form input (DHTML from http://dynarch.com/mishoo/calendar.epl).',
		label_ru => 'Отрисовывает поле ввода типа "календарь" (DHTML-код позаимствован с http://dynarch.com/mishoo/calendar.epl).',
		see_also => [qw(draw_form)]
	},

					#######################################

	{
		name     => 'draw_toolbar_input_text',
		options  => [qw(name label value off)],
		syn      => <<EO,
	draw_toolbar_input_text ({
		label  => 'Search',
		name   => 'q',
	}),
EO
		label_en => 'Draws the text input (usually, for quick search).',
		label_ru => 'Отрисовывает текстовое поле ввода на панели над таблицей (обычно для быстрого поиска).',
		see_also => [qw(draw_toolbar)]
	},

					#######################################

	{
		name     => 'draw_toolbar_input_select',
		options  => [qw(name values value empty)],
		syn      => <<EO,
	draw_toolbar_input_select ({
		name   => 'id_topic',
		values => \$data -> {topics},
		empty  => '[All topics]',
	}),
EO
		label_en => 'Draws the drop-down input (usually, for quick filter).',
		label_ru => 'Отрисовывает выпадающий список на панели над таблицей (обычно для быстрого фильтра).',
		see_also => [qw(draw_toolbar)]
	},


					#######################################

	{
		name     => 'draw_toolbar_pager',
		options  => [qw(cnt total portion/$$conf{portion})],
		syn      => <<EO,
	draw_toolbar_pager ({
		cnt     => 0 + @{$data -> {list}},
		total   => \$data -> {cnt},
		portion => \$data -> {portion},
	})
EO
		label_en => 'Draws the table navigation pager.',
		label_ru => 'Отрисовывает элемент листания таблицы.',
		see_also => [qw(draw_toolbar)]
	},

					#######################################

	{
		name     => 'draw_hr',
		options  => [qw(height/1 class/bgr8)],
		syn      => 'draw_hr (height => 10, class => "bgr0")',
		label_en => 'Draws a vertical spacer (mostly inter-table divider).',
		label_ru => 'Отрисовывает пустой межтабличный разделитель заданной высоты.',
	},


					#######################################

	{
		name     => 'draw_toolbar',
		options  => [qw(off target/invisible keep_params)],
		syn      => <<EO,
	draw_toolbar (

		{
			off => \$_REQUEST {__read_only},
			keep_params => ['parent'],
		},

		draw_toolbar_button ({
			icon => 'create',
			label => 'Create',
			href => "?type=my_objects&action=create",
		}),

		draw_toolbar_input_text ({
			label  => 'Search',
			name   => 'q',
		}),

		draw_toolbar_pager ({
			cnt     => 0 + @{$data -> {list}},
			total   => \$data -> {cnt},
			portion => \$data -> {portion},
		})

	)
EO
		label_en => 'Draws the toolbar on top of the table.',
		label_ru => 'Отрисовывает панель с кнопками поверх таблицы.',
		see_also => [qw(draw_toolbar_button draw_toolbar_input_text draw_toolbar_input_select draw_toolbar_pager)]
	},

					#######################################

	{
		name     => 'draw_toolbar_button',
		options  => [qw(label href target/_self confirm off)],
		syn      => <<EO,
	draw_toolbar_button ({
		icon => 'create',
		label => 'Create',
		href => "?type=my_objects&action=create",
	})
EO
		label_en => 'Draws a button on the toolbar on top of the table.',
		label_ru => 'Отрисовывает кнопку на панели поверх таблицы.',
		see_also => [qw(draw_toolbar)]
	},



					#######################################

	{
		name     => 'draw_window_title',
		options  => [qw(label off)],
		syn      => <<EO,
	draw_window_title ({
		label => "My Fancy Window",
		off   => \$data -> {no_crap},
	})
EO
		label_en => 'Draws the window title.',
		label_ru => 'Отрисовывает заголовок окна.',
	},

					#######################################

	{
		name     => 'draw_path',
		options  => [qw(max_len multiline id_param/id)],
		see_also => [qw(sql_select_path)],
		syn      => <<EO,
	draw_path ([
		{type => rubrics,  name => 'Contents'},
		{type => rubrics,  name => 'Rubric1',    id => 1,  id_param => 'id_rubric'},
		{type => rubrics,  name => 'Rubric2',    id => 2,  id_param => 'id_rubric'},
		{type => articles, name => 'My Article', id => 10},
	])
EO
		label_en => 'Draws the object path (like "Contents/Rubric1/Rubric2/My Article").',
		label_ru => 'Отрисовывает путь к объекту (например, "Contents/Rubric1/Rubric2/My Article").',
	},

					#######################################

	{
		name     => 'draw_text_cells',
		syn      => <<EO,
	draw_text_cells ({href => "/?type=foo&action=bar"}, [
			'foo',
			'bar',
			{
				label   => "100000000",
				picture => '\$ ### ### ###',
			}
		])
EO
		label_en => 'Draws the series of text cells with common options.',
		label_ru => 'Отрисовывает последовательность текстовых клеток с общими опциями.',
		see_also => [qw(draw_table draw_text_cell)]
	},

					#######################################

	{
		name     => 'draw_row_buttons',
		options  => [qw(off)],
		syn      => <<EO,
	draw_row_buttons ({off => 0 + NEVER + EVER}, [
			{
				label   => "[Edit]",
				icon    => "edit",
				href    => "/?type=items&id=\$\$i{id}",
			},
			{
				label   => "[Delete]",
				icon    => "delete",
				href    => "/?type=items&action=delete&id=\$\$i{id}",
				confirm => "Are you sure?!",
			}
		])
EO
		label_en => 'Draws the series of row buttons.',
		label_ru => 'Отрисовывает последовательность кнопок в строке таблицы.',
		see_also => [qw(draw_table draw_row_button)]
	},


					#######################################

	{
		name     => 'draw_text_cell',
		options  => [qw(label max_len/$$conf{max_len} picture attributes off href target/invisible a_class/lnk4)],
		syn      => <<EO,
	draw_text_cell ('foo')

	draw_text_cell ({
		label   => "100000000",
		picture => '\$ ### ### ###',
		href    => "/?type=foo&action=bar",
	})
EO
		label_en => 'Draws table cell containing an input field.',
		label_ru => 'Отрисовывает клетку таблицы с текстовым полем ввода.',
		see_also => [qw(draw_table draw_text_cells)]
	},

					#######################################

	{
		name     => 'draw_input_cell',
		options  => [qw(name label off/0 read_only/0 max_len/$$conf{max_len} size/30 attributes a_class/lnk4)],
		syn      => <<EO,
	draw_input_cell ({
		name  => "_B5",
		label => \$i -> {B5},
	})
EO
		label_en => 'Draws table cell containing an input field.',
		label_ru => 'Отрисовывает клетку таблицы с текстовым полем ввода.',
		see_also => [qw(draw_table)]
	},

					#######################################

	{
		name     => 'draw_checkbox_cell',
		options  => [qw(name value/1 attributes)],
		syn      => <<EO,
	draw_checkbox_cell ({
		name  => "_adding_\$\$i{id}",
	})
EO
		label_en => 'Draws table cell containing an input field.',
		label_ru => 'Отрисовывает клетку таблицы с текстовым полем ввода.',
		see_also => [qw(draw_table)]
	},

					#######################################

	{
		name     => 'draw_row_button',
		options  => [qw(label icon href target/invisible confirm off)],
		syn      => <<EO,
	draw_row_button ({
		label   => "[Delete]",
		icon    => "delete",
		href    => "/?type=items&action=delete&id=\$\$i{id}",
		confirm => "Are you sure?!",
	})
EO
		label_en => 'Draws a button in the table row.',
		label_ru => 'Отрисовывает кнопку в строке таблицы.',
		see_also => [qw(draw_table)]
	},

					#######################################

	{
		name     => 'draw_table',
		options  => [qw(off .. name type/$_REQUEST{type} action/add toolbar js_ok_escape)],
		syn      => <<EO,
	draw_table (

		['Name', 'Phone'],

		sub {
			draw_text_cell  ({ \$i -> {label} }),
			draw_input_cell ({
				name  => '_phone_' . \$i -> {id},
				label => \$i -> {phone},
			}),
		},

		[
			{id => 1, label => 'McFoo', phone => '001-01-01-001'},
			{id => 2, label => 'Dubar', phone => '0'},
		],

		{
			'..'    => 1,
			name    => 'form_phones',
			toolbar => draw_ok_esc_toolbar (),
		}

	)
EO
		label_en => 'Draws the data table with the given headers, callback sub and data array. Data are passed to the callback sub through the global variable $i.',
		label_ru => 'Отрисовывает таблицу данных с заданными заголовками, callback-процедурой и массивом данных. Данные передаются в callback-процедуру через глобальную переменную $i.',
		see_also => [qw(draw_text_cells draw_text_cell draw_input_cell draw_checkbox_cell draw_row_button draw_row_buttons)]
	},

					#######################################


);

################################################################################

@params = (

	{
		name => 'type',
		label_en => 'Screen type for the current request. Determines (with id and action) what callbacks (e.g. "select_$type", "draw_item_of_$type", "do_$action_$type") are to be invoked.',
		label_ru => 'Текущий тип экрана. Определяет (совместно с id и action), какие подпрограммы (например, "select_$type", "draw_item_of_$type", "do_$action_$type") должны быть вызваны.',
		default => 'logon',
	},

	{
		name => 'action',
		label_en => 'Action name. If set, "validate_$action_$type" and "do_$action_$type" callbackcs are invoked, then the user is redirected to the URL with an empty action.',
		label_ru => 'Имя действия. Если задан, то вызываются подапрограммы "validate_$action_$type" и "do_$action_$type", после чего пользователь перенаправляется на URL с пустым action',
	},

	{
		name => '__include_js',
		label_en => 'ARRAYREF of names of custom javaScript files located in application_root/doc_root/i/.',
		label_ru => 'Ссылка на список дополнительных javaScript-файлов, расположенных в директории  application_root/doc_root/i/.',
		default => '[\'js\']',
	},

	{
		name => '__include_css',
		label_en => 'ARRAYREF of names of custom CSS files located in application_root/doc_root/i/.',
		label_ru => 'Ссылка на список дополнительных CSS-файлов, расположенных в директории  application_root/doc_root/i/.',
	},

	{
		name => 'keepalive',
		label_en => 'If set, extends the lifetime for the session which number is his value. Internal paramerter, not to be used in application developpment.',
		label_ru => 'Если задан, то продлевает время жизни сессии, чей номер совпадает с его значением. Внутренний параметр, не должен использоваться напрямую.',
	},

	{
		name => 'sid',
		label_en => 'Session ID. If set, determines the current session => current user, otherwise the client is redirecred to the logon screen (type=logon).',
		label_ru => 'Если задан, то определяет ID сессии => текущего пользователя, в противном случае клиент перенаправляется на входную форму (type=logon).',
	},

	{
		name => 'salt',
		label_en => 'Fake parameter with random values. Used for preventing browser from using local HTML cache.',
		label_ru => 'Фиктивный параметр со случайными значениями. Используется для предотвращения кэширования HTML на стороне клиента.',
	},

	{
		name => '_frame',
		label_en => 'Reserved for browsers not suppotring IFRAME tag.',
		label_ru => 'Зарезервировано для браузеров без поддержки тега IFRAME.',
	},

	{
		name => 'error',
		label_en => 'Error message text. Must not be set directly, it\'s calulated from "validate_$action_$type" return value.',
		label_ru => 'Текст сообщения об ошибке. Не должен задаваться явно, так как вычисляется на основе значения, возвращаемого подпрограммой "validate_$action_$type".',
	},

	{
		name => '__response_sent',
		label_en => 'If set, no "draw_$type" or "draw_item_of_$type" sub is called and no HTML is sent to the client.',
		label_ru => 'Если задан, то процедура "draw_$type" или "draw_item_of_$type" не вызывается и сгенерированный HTML не пересылается клиенту.',
	},

	{
		name => 'redirect_params',
		label_en => 'Set this parameter to Data::Dumper($some_hashref) if you want to restore %$some_hashref as %_REQUEST after the next logon. Normally must appear only at logon screen as a hidden input.',
		label_ru => 'Установите значение этого параметра в Data::Dumper($some_hashref), если хотите, чтобы %$some_hashref был восстановлен как %_REQUEST после следующего входа в систему. В норме должен присутствовать только на ворме входа в виде скрытого поля ввода.',
	},

	{
		name => 'id',
		label_en => 'If set, "get_item_of_$type" and "draw_item_of_$type" will be called instead of "select_$type" and "draw_$type".',
		label_ru => 'Если установлен, то "get_item_of_$type" и "draw_item_of_$type" будут вызваны вместо "select_$type" and "draw_$type".',
	},

	{
		name => 'dbf',
		label_en => 'Obsoleted by __response_sent.',
		label_ru => 'Атавизм. Следует использовать __response_sent.'
	},

	{
		name => 'xls',
		label_en => 'If set, the table with lpt attribute set to 1 is cropped from the output and sent to the client as an Excel worksheet.',
		label_ru => 'Если установлен, то из HTML страницы вызезается таблица с атрибутом lpt=1 и возвращается на клиент в виде рабочего листа Excel.'
	},

	{
		name => 'lpt',
		label_en => 'If set, the table with lpt attribute set to 1 is cropped from the output and sent to the client in the printer friendly form.',
		label_ru => 'Если установлен, то из HTML страницы вызезается таблица с атрибутом lpt=1 и возвращается на клиент в виде, пригодном для распечатки.'
	},

	{
		name => 'role',
		label_en => 'Current role ID. Used in multirole alpplications only.',
		label_ru => 'ID текущей роли. Имеет смысл только в приложениях, где один пользователь может работать в нескольких ролях.'
	},

	{
		name => 'order',
		label_en => 'Name of the sort column. Set in hrefs produced by headers sub, used in SQL generated by order sub.',
		label_ru => 'Имя столбца, по которому производится сортировка. Устанавливается в ссылках, сгенерироанных headers, используется в SQL, сгенерированном order.',
	},

	{
		name => 'desc',
		label_en => 'If true, the sort order is descending. Set in hrefs produced by headers sub, used in SQL generated by order sub.',
		label_ru => 'Если истина, то порядок сортировки обратный. Устанавливается в ссылках, сгенерироанных headers, используется в SQL, сгенерированном order.',
	},

	{
		name => '__content_type',
		label_en => 'MIME type of the HTTP responce sent to the client.',
		label_ru => 'MIME-тип HTTP-ответа',
		default => 'text/html; charset=windows-1251',
	},

	{
		name => 'period',
		label_en => 'Always tranferred by <a href=../check_href.html>check_href</a> sub. Reserved for calendar-like applications.',
		label_ru => 'Всегда передаётся по ссылкам через <a href=../check_href.html>check_href</a>. Зарезервировано для календарных приложений.',
	},

	{
		name => '__read_only',
		label_en => 'If true, all input fields are disabled.',
		label_ru => 'Если истина, все поля ввода превращаются в надписи.',
	},

	{
		name => '__pack',
		label_en => 'If true, the browser window is packed around the main form/table. Used in popup windows.',
		label_ru => 'Если истина, окно браузера сжимается до минимума, охватывающего страницу. Используется во всплывающих окнах.',
	},

	{
		name => '__popup',
		label_en => 'If true, set all of __read_only, __pack and __no_navigation to true.',
		label_ru => 'Если истина, то истинны также __read_only, __pack и __no_navigation.',
	},

	{
		name => '__no_navigation',
		label_en => 'If true, no top navigation bar (user name/calendar/logout) is shown. Used in popup windows.',
		label_ru => 'Если истина, то не показывается верняя панель навигации (пользователь/календарь/выход). Используется во всплывающих окнах.',
	},

	{
		name => '_xml',
		label_en => 'If set, is surrounded with XML tags and placed in HEAD section. Used for MS Office 2000 HTML emulation.',
		label_ru => 'Если непуст, то окружается тегами XML и помещается в раздел HEAD. Используется для эмуляции MS Office 2000 HTML.',
	},

	{
		name => '__scrollable_table_row',
		label_en => 'Numer of table row highlighted by the slider at page load.',
		label_ru => 'Номер строки таблицы, на которой располагается слайдер при загрузке страницы.',
		default => '0',
	},

	{
		name => '__meta_refresh',
		label_en => 'The value for <META HTTP-EQUIV=Refresh ... > tag.',
		label_ru => 'Значение для тега <META HTTP-EQUIV=Refresh ... >.',
	},

	{
		name => '__focused_input',
		label_en => 'The NAME of the input to be focused at page load. Unless set, the first text inpyt is focused.',
		label_ru => 'Значение атрибута NAME поля ввода, на котором должен стоять фокус ввода при загрузке страницы. Если не установлен, фокусируется первое текстовое поле.',
	},

	{
		name => '__blur_all',
		label_en => 'If true, no input is focused.',
		label_ru => 'Если установлен, ни одно поле ввода не имеет фокуса.',
	},

	{
		name => '__help_url',
		label_en => 'URL to be activated on F1 press or [Help] link.',
		label_ru => 'URL, активизируемый при нажатии на F1 или ссылку [Справка].',
	},

	{
		name => '__path',
		label_en => 'Set internally by <a href=../draw_path.html>draw_path</a> for implement \'..\' facility in <a href=../draw_table.html>draw_table</a>.',
		label_ru => 'Устанавливается внутри <a href=../draw_path.html>draw_path</a>, чтобы реализовать опцию \'..\' в <a href=../draw_table.html>draw_table</a>.',
	},

	{
		name => '__toolbars_number',
		label_en => 'Set internally by <a href=../draw_toolbar.html>draw_toolbar</a> for proper toolbar indexing.',
		label_ru => 'Устанавливается внутри <a href=../draw_toolbar.html>draw_toolbar</a> для индексации панелей управления.',
	},

	{
		name => 'start',
		label_en => 'Number of first displayed record in multipage recordsets.',
		label_ru => 'Номер первой записи выборки, показываемой на странице (при наличии нарезки).',
	},

);

################################################################################

%i18n = (
	NAME => {
		en => 'NAME',
		ru => 'НАЗВАНИЕ',
	},
	SYNOPSIS => {
		en => 'SYNOPSIS',
		ru => 'ИСПОЛЬЗОВАНИЕ',
	},
	DESCRIPTION => {
		en => 'DESCRIPTION',
		ru => 'ОПИСАНИЕ',
	},
	OPTIONS => {
		en => 'OPTIONS',
		ru => 'ОПЦИИ',
	},
	DEFAULT => {
		en => 'DEFAULT',
		ru => 'ПО УМОЛЧАНИЮ',
	},
	SEE_ALSO => {
		en => 'SEE ALSO',
		ru => 'СМ. ТАКЖЕ',
	},
	DEFAULT => {
		en => 'DEFAULT VALUE',
		ru => 'ПО УМОЛЧАНИЮ',
	},
	'API Reference' => {
		en => 'API Reference',
		ru => 'Подпрограммы',
	},
);

################################################################################

sub generate_param {

	my ($lang, $s) = @_;

	my $see_also = '';
	foreach my $sa (sort @{$s -> {see_also}}) {
		$see_also .= qq{<li><a href="$sa.html">$sa</a>};
	}

	$see_also and $see_also = <<EOF;
					<dt>${$i18n{SEE_ALSO}}{$lang}
					<dd><ul>$see_also</ul>
EOF

	open (F, ">$lang/params/$$s{name}.html");
	print F <<EOF;
		<HTML>
			<HEAD>
				<TITLE>Zanas.pm documentation: parameter $$s{name}
				
			
			
				
${$i18n{NAME}}{$lang}
\$_REQUEST {$$s{name}} @{[ $$s{default} ? <${$i18n{DEFAULT}}{$lang}
$$s{default}
EOD
${$i18n{DESCRIPTION}}{$lang}
$$s{"label_$lang"} $see_also
EOF close (F); } ################################################################################ sub generate_sub { my ($lang, $s) = @_; my $options = ''; foreach my $o (@{$s -> {options}}) { my ($name, $default) = split /\//, $o; $default ||= ' '; my ($o_def) = grep {$_ -> {name} eq $name} @options; $o_def or die "Option not defined: $name.\n"; my $label = $o_def -> {"label_$lang"}; $options .= qq{$name$label$default}; } $options and $options = <${$i18n{OPTIONS}}{$lang}

${$i18n{NAME}}{$lang}${$i18n{DESCRIPTION}}{$lang}${$i18n{DEFAULT}}{$lang} $options
EOF my $see_also = ''; foreach my $sa (sort @{$s -> {see_also}}) { $see_also .= qq{
  • $sa}; } $see_also and $see_also = <${$i18n{SEE_ALSO}}{$lang}
      $see_also
    EOF open (F, ">$lang/$$s{name}.html"); print F < Zanas.pm documentation: $$s{name}
    ${$i18n{NAME}}{$lang}
    $$s{name}
    ${$i18n{SYNOPSIS}}{$lang}
    $$s{syn}
    ${$i18n{DESCRIPTION}}{$lang}
    $$s{"label_$lang"} $options $see_also
    EOF close (F); } ################################################################################ sub generate_left { my ($lang) = @_; my $subs = ''; foreach my $s (sort {$a -> {name} cmp $b -> {name}} @subs) { $subs .= qq{$$s{name}
    }; generate_sub ($lang, $s); } my $params = ''; foreach my $s (sort {$a -> {name} cmp $b -> {name}} @params) { $params .= qq{$$s{name}
    }; generate_param ($lang, $s); } open (F, ">$lang/left.html"); print F < Zanas.pm documentation @{[ map { <$_ EO

    ${$i18n{'API Reference'}}{$lang}

    $subs

    %_REQUEST

    $params EOF close (F); } ################################################################################ sub generate_index { my ($lang) = @_; open (F, ">$lang/index.html"); print F < Zanas.pm documentation EOF close (F); } ################################################################################ sub generate_for_lang { my ($lang) = @_; mkdir $lang; mkdir "$lang/params"; generate_index ($lang); generate_left ($lang); } ################################################################################ sub generate { map { generate_for_lang ($_) } @langs; mkdir 'css'; open (F, ">css/z.css"); print F <

    ./Zanas/Request.pm

    package Zanas::Request;
    require Zanas::Request::Upload;
    
    ################################################################################
    
    sub new {
    	my $proto = shift;
    	my $class = ref($proto) || $proto;
    
    	my $self  = {};
    	$self -> {Q} = new CGI;
    
    #	if ($ENV{PATH_TRANSLATED} =~ /$ENV{DOCUMENT_ROOT}\/+index\.html/) {
    #		$self -> {Filename} = '';
    #	} else {
    #		$ENV{PATH_TRANSLATED} =~ /$ENV{DOCUMENT_ROOT}\/+(.*)/;
    #		$self -> {Filename} = $1;
    #	}
    
    #	$self -> {Filename} = $self -> {Q} -> script_name;
    #	$self -> {Filename} = '/' if $self -> {Filename} =~ /index\.pl/;
    
    	$self -> {Filename} = $ENV{PATH_INFO};
    	$self -> {Filename} = '/' if $self -> {Filename} =~ /index\./;
    
    	$self -> {Document_root} = $ENV{DOCUMENT_ROOT};
    	$self -> {Out_headers} = {-type => 'text/html', -status=> 200};
    
    	bless ($self, $class);
    
    	return $self;
    }
    
    ################################################################################
    
    sub internal_redirect {
    
    	my $self = shift;
    	my $q = $self -> {Q};
    
    	my $url = $_[0];
    
    #print STDERR "URL: $url ";
    
    	unless ($url =~ /^http:\/\//) {
    		$url =~ s{^/}{};
    		$url = "http://$ENV{HTTP_HOST}/$url" ;
    	}
    
    #print STDERR " --> $url\n";
    
    	print $q -> redirect (-uri => $url);
    
    }
    
    ################################################################################
    
    sub header_in {
    	my $self = shift;
    	my $q = $self -> {Q};
    	return $q -> http ($_ [0]);
    }
    
    ################################################################################
    
    sub content_type {
    
    	my $self = shift;
    	my $q = $self -> {Q};
    
    	if ($_ [0]) {
    		$self -> {Out_headers} -> {-type} = $_ [0];
    	} else {
    		return $self -> {Out_headers} -> {-type};
    	}
    
    }
    
    ################################################################################
    
    sub status {
    
    	my $self = shift;
    	my $q = $self -> {Q};
    	if ($_ [0]) {
    		$self -> {Out_headers} -> {-status} = $_ [0];
    	} else {
    		return $self -> {Out_headers} -> {-status};
    	}
    
    }
    
    ################################################################################
    
    sub header_out {
    
    	my $self = shift;
    	my $q = $self -> {Q};
    
    	$self -> {Out_headers} -> {"-$_[0]"} = $_[1];
    
    }
    
    ################################################################################
    
    sub send_http_header {
    
    	my $self = shift;
    	my $q = $self -> {Q};
    
    	my @params = ();
    
    	foreach $header (keys %{$self -> {Out_headers}}) {
    		push (@params, $header, $self -> {Out_headers} -> {$header});
    	}
    
    	print $q -> header (@params);
    }
    
    ################################################################################
    
    sub send_fd {
    
    	my $self = shift;
    	my $q = $self -> {Q};
    
    	my $fh = CGI::to_filehandle($_ [0]);
    	binmode($fh);
    
    	my $buf;
    
    	while (read ($fh, $buff, 8 * 2**10)) {
    		print STDOUT $buff;
    	}
    
    }
    
    ################################################################################
    
    sub filename {
    
    	my $self = shift;
    
    	return $self -> {Filename};
    
    }
    
    ################################################################################
    
    sub connection {
    
    	my $self = shift;
    
    	return $self;
    
    }
    
    ################################################################################
    
    sub remote_ip {
    
    	return $ENV {REMOTE_ADDR};
    
    }
    
    ################################################################################
    
    sub document_root {
    
    	my $self = shift;
    
    	return $self -> {Document_root};
    
    }
    
    ################################################################################
    
    sub parms {
    
    	my $self = shift;
    	my $q = $self -> {Q};
    	my %vars = $q -> Vars;
    	return \%vars;
    
    }
    
    ################################################################################
    
    sub param {
    
    	my $self = shift;
    	my $q = $self -> {Q};
    
    	return $q -> param ($_ [0]);
    
    }
    
    ################################################################################
    
    sub upload {
    
    	my $self = shift;
    	my $q = $self -> {Q};
    
    	my $param = $_ [0];
    	return $self -> {$param} if ($self -> {$param});
    
    	$self -> {$param} = Zanas::Request::Upload -> new($q, $param);
    
    	return $self -> {$param};
    
    }
    
    ################################################################################
    
    package Apache::Constants;
    
    sub OK () {
    	return 200;
    }
    
    1;

    ./Zanas/Presentation

    ./Zanas/Presentation/MSIE_5.pm

    no warnings;
    
    ################################################################################
    
    sub register_hotkey {
    
    	my ($hashref, $type, $data, $options) = @_;
    
    	$hashref -> {label} =~ s{\&(.)}{$1} or return;
    
    	my $c = $1;
    
    	my $code = 0;
    
    	if ($c eq '<') {
    		$code = 37;
    	}
    	elsif ($c eq '>') {
    		$code = 39;
    	}
    	elsif (lc $c eq 'ж') {
    		$code = 186;
    	}
    	elsif (lc $c eq 'э') {
    		$code = 222;
    	}
    	else {
    		$c =~ y{ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮйцукенгшщзхъфывапролджэячсмитьбю}{qwertyuiop[]asdfghjkl;'zxcvbnm,.qwertyuiop[]asdfghjkl;'zxcvbnm,.};
    		$code = (ord ($c) - 32);
    	}
    
    	push @scan2names, {
    		code => $code,
    		type => $type,
    		data => $data,
    		ctrl => $options -> {ctrl},
    		alt  => $options -> {alt},
    	};
    
    }
    
    ################################################################################
    
    sub hotkeys {
    
    	map { hotkey ($_) } @_;
    
    }
    
    ################################################################################
    
    sub hotkey {
    
    	my ($def) = $_[0];
    
    	return if $def -> {off};
    
    	$def -> {type} ||= 'href';
    	if ($def -> {code} =~ /^F(\d+)/) {
    		$def -> {code} = 111 + $1;
    	}
    
    	push @scan2names, $def;
    
    }
    
    ################################################################################
    
    sub handle_hotkey_focus {
    
    	my ($r) = @_;
    
    	< {ctrl} ? '' : '!';
    	my $alt  = $r -> {alt}  ? '' : '!';
    
    	< {type};
    			$renderrer = 'draw_item_of_' . $page -> {type};
    		}
    		elsif ($_REQUEST {dbf}) {
    			$selector  = 'select_' . $page -> {type};
    			$renderrer = 'dbf_write_' . $page -> {type};
    		}
    		else {
    			$selector  = 'select_' . $page -> {type};
    			$renderrer = 'draw_' . $page -> {type};
    		}
    
    		my $content;
    
    		eval {
    			$content = call_for_role ($selector);
    		};
    
    		print STDERR $@ if $@;
    
    		return '' if $_REQUEST {__response_sent};
    
    		if ($_REQUEST {__popup}) {
    			$_REQUEST {__read_only} = 1;
    			$_REQUEST {__pack} = 1;
    			$_REQUEST {__no_navigation} = 1;
    		}
    
    		eval {
    			$body = call_for_role ($renderrer, $content);
    		};
    
    		$_REQUEST {error} = $@ if $@;
    
    	}
    
    	if ($_REQUEST {error}) {
    
    		my $message = js_escape ($_REQUEST {error});
    
    		my $html = <
    				
    				
    				
    			
    EOH
    
    		return $html;
    
    	}
    
    	if ($_REQUEST{dbf}) {
    		return $body;
    	}
    	elsif ($_REQUEST{lpt}) {
    
    		$body =~ s{^.*?\]*lpt\=\"?1\"?[^\>]*\>}{}sm; #"
    
    		$_REQUEST{_xls_checksum} and $body =~ s{
    }{$_REQUEST{_xls_checksum}}; $_REQUEST{xls} and $body =~ s{ $$conf{page_title} $_REQUEST{_xml} $body EOH } $_USER -> {role} eq 'admin' and $_REQUEST{id} or my $lpt = $body =~ s{]*lpt\=\"?1\"?[^\>]*\>}{\}gsm; #" my $menu = draw_menu ($page -> {menu}, $page -> {highlighted_type}); $_REQUEST {__scrollable_table_row} ||= 0; my $meta_refresh = $_REQUEST {__meta_refresh} ? qq{} : ''; my $auth_toolbar = draw_auth_toolbar ({lpt => $lpt}); my $keepalive = $_REQUEST{sid} ? < EOH my $win_dirty_hack = $^O eq 'MSWin32' ? '/i' : ''; my $request_package = ref $apr; my $mod_perl = $ENV {MOD_PERL}; $mod_perl ||= 'NO mod_perl AT ALL'; return < $$conf{page_title} $meta_refresh @{[ map {< EOJS @{[ map {< EOCSS @{[ $_REQUEST{__help_url} ? < window.open ('$_REQUEST{__help_url}', '_blank', 'toolbar=no,resizable=yes'); event.returnValue = false; EOHELP
    $auth_toolbar $menu $body
    $keepalive EOH } ################################################################################ sub draw_form_field_button { my ($options, $data) = @_; my $s = $$data{$$options{name}}; $s ||= $$options{value}; $s =~ s/\"/\"\;/gsm; #" my $onclick = $$options{onclick} || ''; return qq {}; } ################################################################################ sub draw_menu { my ($types, $cursor) = @_; @$types or return ''; $_REQUEST {__no_navigation} and return ''; my ($tr1, $tr2, $tr3, $divs) = ('', '', '', ''); foreach my $type (@$types) { next if $type -> {off}; $conf -> {kb_options_menu} ||= {ctrl => 1, alt => 1}; register_hotkey ($type, 'href', 'main_menu_' . $type -> {name}, $conf -> {kb_options_menu}); $tr1 .= <
    EOH # my $aclass = $$type{name} eq $cursor ? 'lnk1' : 'lnk0'; # my $tclass = $$type{name} eq $cursor ? 'bgr4' : 'bgr8'; my ($aclass, $tclass); if ($type -> {role}) { $aclass = "$$type{name}_for_$$type{role}" eq $cursor ? 'lnk1' : 'lnk0'; $tclass = "$$type{name}_for_$$type{role}" eq $cursor ? 'bgr4' : 'bgr8'; } else { $aclass = $$type{name} eq $cursor ? 'lnk1' : 'lnk0'; $tclass = $$type{name} eq $cursor ? 'bgr4' : 'bgr8'; } # my $onhover = ''; if (ref $type -> {items} eq ARRAY) { $divs .= draw_vert_menu ($type -> {name}, $type -> {items}); $onhover = qq {onmouseover="open_popup_menu ('$$type{name}')"}; } my $href = $type -> {no_page} ? '#' : "/?type=$$type{name}&sid=$_REQUEST{sid}@{[$_REQUEST{period} ? '&period=' . $_REQUEST {period} : '']}@{[$type->{role} ? '&role=' . $type->{role} : '']}"; $tr2 .= < EOH $tr3 .= < EOH } return < $tr2 $tr3
      $$type{label}    $$type{label}  
    $divs EOH } ################################################################################ sub draw_vert_menu { my ($name, $types) = @_; my $tr2 = ''; foreach my $type (@$types) { if ($type eq BREAK) { $tr2 .= < EOH } else { $tr2 .= <   $$type{label}   EOH } } return <

    ./Zanas/Presentation/Mozilla_3.pm

    ################################################################################
    
    sub Mozilla_3_draw_page {
    
    	unless ($_REQUEST {'_frame'} or $_REQUEST {'error'}) {
    
    		my $url = create_url () . '&_frame=1';
    
    		return <
    			
    				
    				
    				
    				
    			
    		
    EOH
    
    	}
    
    
    	my ($page) = @_;
    
    	my $body = '';
    
    	if (1) {
    
    		my ($selector, $renderrer);
    
    		if ($_REQUEST {error}) {
    
    			my $message = js_escape ($_REQUEST {error});
    
    			my $html = <
    					
    					
    					
    				
    EOH
    
    			return $html;
    
    		}
    		else {
    
    			if ($_REQUEST {id}) {
    				$selector  = 'get_item_of_' . $page -> {type};
    				$renderrer = 'draw_item_of_' . $page -> {type};
    			}
    			elsif ($_REQUEST {dbf}) {
    				$selector  = 'select_' . $page -> {type};
    				$renderrer = 'dbf_write_' . $page -> {type};
    			}
    			else {
    				$selector  = 'select_' . $page -> {type};
    				$renderrer = 'draw_' . $page -> {type};
    			}
    
    			$body = call_for_role ($renderrer, call_for_role ($selector));
    
    		}
    
    
    	} else {
    
    		$body = "



    Тип данных '$$page{type}' не определён. Сообщите администратору."; } if ($_REQUEST{dbf}) { return $body; } elsif ($_REQUEST{lpt}) { $body =~ s{^.*?\]*lpt\=\"?1\"?[^\>]*\>}{}sm; #" return < НПФ $body EOH } $_USER -> {role} eq 'admin' and $_REQUEST{id} or my $lpt = $body =~ s{]*lpt\=\"?1\"?[^\>]*\>}{\
    }gsm; #" my $menu = draw_menu ($page -> {menu}, $page -> {highlighted_type}); return < $$conf{page_title} @{[ draw_auth_toolbar ({lpt => $lpt}) ]} $menu $body EOH } ################################################################################ sub Mozilla_3_draw_menu { my ($types, $cursor) = @_; @$types or return ''; my ($tr2, $tr3) = ('', ''); foreach my $type (@$types) { my ($abra, $aket) = $$type{name} eq $cursor ? ('', '') : ('', ''); my $tcolor = $$type{name} eq $cursor ? 'efefef' : 'd5d5d5'; $tr2 .= < EOH my $bgcolor = $$type{name} eq $cursor ? '596084' : 'd5d5d5'; $tr3 .= < EOH } return < $tr2 $tr3
      $abra$$type{label}$aket  
    EOH } ################################################################################ sub Mozilla_3_draw_hr { my %options = @_; $options {height} ||= 1; $options {class} ||= bgr8; return < EOH } ################################################################################ sub Mozilla_3_draw_text_cell { my ($data) = @_; return '' if $data -> {off}; my $txt = $data -> {label}; $txt ||= ' '; $txt = "$txt"; if ($data -> {href}) { $data -> {href} =~ /sid\=\d/ or $data -> {href} .= "\&sid=$_REQUEST{sid}"; my $target = $data -> {target} ? "target='$$data{target}'" : ''; $txt = qq { $txt }; } return qq {$txt}; } ################################################################################ sub Mozilla_3_draw_tr { my ($options, @tds) = @_; return qq {@tds}; } ################################################################################ sub Mozilla_3_draw_one_cell_table { my ($options, $body) = @_; return <
    $body
    EOH } ################################################################################ sub Mozilla_3_draw_table { my ($headers, $ths) = ([], ''); unless (ref $_[0] eq CODE) { $headers = shift; } if (@$headers) { $ths = '' . (join '', map { ref $_ eq HASH ? ($$_{off} ? '' : "$$_{label}\ ") : "$_\ " } @$headers); } my ($tr_callback, $list) = @_; my $trs = ''; foreach our $i (@$list) { $trs .= ''; $trs .= &$tr_callback ($item); } return < $ths $trs EOH } ################################################################################ sub Mozilla_3_draw_path { $_REQUEST{lpt} and return ''; my ($options, $list) = @_; $options -> {id_param} ||= 'id'; $path = ''; foreach my $item (@$list) { $path and $path .= ' / '; $id_param = $item -> {id_param}; $id_param ||= $options -> {id_param}; $path .= <$$item{name} EOH } return draw_hr (height => 10) . <
     $path 
    EOH } ################################################################################ sub Mozilla_3_draw_window_title { my ($options) = @_; return '' if $options -> {off}; return <   $$options{label} EOH } ################################################################################ sub Mozilla_3_draw_toolbar { my ($options, @buttons) = @_; return '' if $options -> {off}; my $colspan = 2 * @buttons + 3; return <
      @buttons  
    EOH } ################################################################################ sub Mozilla_3_draw_toolbar_button { my ($options) = @_; return < [$$options{label}] EOH } ################################################################################ sub Mozilla_3_draw_toolbar_input_text { my ($options) = @_; return <$$options{label}: EOH } ################################################################################ sub Mozilla_3_draw_toolbar_pager { my ($options) = @_; my $start = $_REQUEST {start} + 0; my $label = ''; if ($start > 0) { $url = create_url (start => $start - $conf -> {portion}); $label .= qq { << }; } $label .= ($start + 1) . ' - ' . ($start + $$options{cnt}) . ' из ' . $$options{total}; if ($start + $$options{cnt} < $$options{total}) { $url = create_url (start => $start + $conf -> {portion}); $label .= qq { >> }; } return <$label EOH } ################################################################################ sub Mozilla_3_js_escape { my ($s) = @_; $s =~ y/\"/\'/; $s =~ s/([^\w])/\\$1/gsm; return "'$s'"; } ################################################################################ sub Mozilla_3_draw_row_button { my ($options) = @_; return '' if $options -> {off}; if ($options -> {confirm}) { my $salt = rand; my $msg = js_escape ($options -> {confirm}); $options -> {onclick} = qq [if (confirm ($msg)) {window.open('$$options{href}&_salt=$salt&sid=$_REQUEST{sid}', '_top')}]; $options -> {href} = '#'; } else { $options -> {href} .= "&sid=$_REQUEST{sid}"; } my $target = $options -> {href} eq '#' ? '' : 'target="_top"'; return qq {\ [$$options{label}]\ }; } ################################################################################ sub Mozilla_3_draw_row_buttons { my ($options, $buttons) = @_; return '' . (join '', map {draw_row_button ($_)} @$buttons) . ''; } ################################################################################ sub Mozilla_3_draw_form { my ($options, $data, $fields) = @_; my $action = $options -> {action}; $action ||= 'update'; my $type = $options -> {type}; $type ||= $_REQUEST{type}; my $id = $options -> {id}; $id ||= $_REQUEST{id}; my $trs = ''; my $n = 0; foreach my $field (@$fields) { next if $field -> {off}; my $type = $field -> {type}; $type ||= 'string'; my $html = &{"draw_form_field_$type"} ($field, $data); # my ($c1, $c2) = $n++ % 2 ? (5, 4) : (4, 0); my $bgcolor = $n++ % 2 ? 'efefef' : 'ffffff'; $trs .= $type eq 'hidden' ? $html : < $$field{label}: $html EOH } my $path = $data -> {path} ? draw_path ({}, $data -> {path}) : ''; my $bottom_toolbar = $options -> {bottom_toolbar} ? $options -> {bottom_toolbar} : $options -> {back} ? draw_back_next_toolbar ($options) : draw_ok_esc_toolbar ($options); return <
    $trs
    $bottom_toolbar EOH } ################################################################################ sub Mozilla_3_draw_form_field_string { my ($options, $data) = @_; my $s = $$data{$$options{name}}; $s =~ s/\"/\"\;/gsm; #" my $size = $options -> {size} ? "size=$$options{size} maxlength=$$options{size}" : "size=80"; return qq {}; } ################################################################################ sub Mozilla_3_draw_form_field_hgroup { my ($options, $data) = @_; return join '  ', map {$_ -> {label} . ': ' . &{'draw_form_field_' . $_ -> {type}}($_, $data)} @{$options -> {items}}; } ################################################################################ sub Mozilla_3_draw_form_field_text { my ($options, $data) = @_; my $s = $$data{$$options{name}}; $s =~ s/\"/\"\;/gsm; #" return qq {}; } ################################################################################ sub Mozilla_3_draw_form_field_hidden { my ($options, $data) = @_; my $s = $$data{$$options{name}}; $s ||= $$options{value}; $s =~ s/\"/\"\;/gsm; #" return qq {}; } ################################################################################ sub Mozilla_3_draw_form_field_password { my ($options, $data) = @_; return qq {}; } ################################################################################ sub Mozilla_3_draw_form_field_static { my ($options, $data) = @_; return $$data{$$options{name}}; } ################################################################################ sub Mozilla_3_draw_form_field_checkbox { my ($options, $data) = @_; my $s = $$data{$$options{name}}; $s =~ s/\"/\"\;/gsm; #" my $checked = $s ? 'checked' : ''; return qq {}; } ################################################################################ sub Mozilla_3_draw_form_field_checkboxes { my ($options, $data) = @_; my $html = ''; foreach my $value (@{$options -> {values}}) { my $checked = $data -> {$options -> {name}} == $value -> {id} ? 'checked' : ''; $html .= qq { $$value{label}
    }; } return $html; } ################################################################################ sub Mozilla_3_draw_form_field_radio { my ($options, $data) = @_; my $html = ''; foreach my $value (@{$options -> {values}}) { my $checked = $data -> {$options -> {name}} == $value -> {id} ? 'checked' : ''; $html .= qq { $$value{label}
    }; } return $html; } ################################################################################ sub Mozilla_3_draw_form_field_select { my ($options, $data) = @_; my $html = ''; foreach my $value (@{$options -> {values}}) { my $selected = $data -> {$options -> {name}} == $value -> {id} ? 'selected' : ''; $html .= qq {}; } return < $html EOH } ################################################################################ sub Mozilla_3_draw_ok_esc_toolbar { my ($options) = @_; my $esc = $options -> {esc}; $esc ||= "/?type=$_REQUEST{type}"; draw_centered_toolbar ($options, [ {icon => 'ok', label => 'применить', href => '#', onclick => 'document.form.submit()'}, {icon => 'cancel', label => 'вернуться', href => "$esc&sid=$_REQUEST{sid}"}, ]) } ################################################################################ sub Mozilla_3_draw_back_next_toolbar { my ($options) = @_; my $type = $options -> {type}; $type ||= $_REQUEST {type}; my $back = $options -> {back}; $back ||= "/?type=$type"; draw_centered_toolbar ($options, [ {icon => 'back', label => '<< назад', href => $back}, {icon => 'next', label => 'продолжить >>', href => '#', onclick => 'document.form.submit()'}, ]) } ################################################################################ sub Mozilla_3_draw_centered_toolbar_button { my ($options) = @_; if ($options -> {href} !~ /^java/ and $options -> {href} !~ /\&sid=/ and $options -> {href} ne '#') { $options -> {href} .= "\&sid=$_REQUEST{sid}"; } my $target = $options -> {href} eq '#' ? '' : 'target="_top"'; return <-->  [$$options{label}]  EOH } ################################################################################ sub Mozilla_3_draw_centered_toolbar { $_REQUEST{lpt} and return ''; my ($options, $list) = @_; my $colspan = 3 * (1 + @$list) + 1; return < @{[ map {draw_centered_toolbar_button ($_)} @$list]}
    EOH } ################################################################################ sub Mozilla_3_draw_auth_toolbar { my $options = shift; my $calendar = <@{[ $_CALENDAR -> draw () ]}
    EOH return < @{[ $_USER ? <[Выход]   EOEXIT
       Пользователь: @{[ $_USER ? $_USER -> {name} : 'Пользователь неопределён']}  $calendar
    EOH } ################################################################################ sub Mozilla_3_draw_calendar { my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime (time); $year += 1900; return "Сегодня: $mday $month_names[$mon] $year"; # $html = ' <<'; # $html .= ' <' if $_CALENDAR -> granularity < 12; # $html .= ' '; # if ($_CALENDAR -> granularity < 12) { # if ($_CALENDAR -> granularity == 1) { # $html .= $month_names [$_CALENDAR -> number]; # } # else { # $html .= (1 + $_CALENDAR -> number) . ' '; # my ($g) = grep {$_ -> {id} == $_CALENDAR -> granularity} @$periods; # $html .= $g -> {label}; # } # $html .= ' '; # } # $html .= $_CALENDAR -> year . ' '; # $html .= ' >' if $_CALENDAR -> granularity < 12; # $html .= ' >>'; # return $html; } 1;

    ./Zanas/static

    ./Zanas/static/navigation.js.pm

    var typeAheadInfo = {last:0,
    	accumString:"",
    	delay:500,
    	timeout:null,
    	reset:function() {this.last=0; this.accumString=""}
    };
    
    function nop() {}
    
    function typeAhead() { // borrowed from http://www.oreillynet.com/javascript/2003/09/03/examples/jsdhtmlcb_bonus2_example.html
       if (window.event && !window.event.ctrlKey) {
          var now = new Date();
          if (typeAheadInfo.accumString == "" || now - typeAheadInfo.last < typeAheadInfo.delay) {
    	 var evt = window.event;
    	 var selectElem = evt.srcElement;
    	 var charCode = evt.keyCode;
    	 var newChar =  String.fromCharCode(charCode).toUpperCase();
    	 typeAheadInfo.accumString += newChar;
    	 var selectOptions = selectElem.options;
    	 var txt, nearest;
    	 for (var i = 0; i < selectOptions.length; i++) {
    	    txt = selectOptions[i].text.toUpperCase();
    	    nearest = (typeAheadInfo.accumString > txt.substr(0, typeAheadInfo.accumString.length)) ? i : nearest;
    	    if (txt.indexOf(typeAheadInfo.accumString) == 0) {
    	       clearTimeout(typeAheadInfo.timeout);
    	       typeAheadInfo.last = now;
    	       typeAheadInfo.timeout = setTimeout("typeAheadInfo.reset()", typeAheadInfo.delay);
    	       selectElem.selectedIndex = i;
    	       evt.cancelBubble = true;
    	       evt.returnValue = false;
    	       return false;
    	    }
    	 }
    	 if (nearest != null) {
    	    selectElem.selectedIndex = nearest;
    	 }
          } else {
    	 clearTimeout(typeAheadInfo.timeout);
          }
          typeAheadInfo.reset();
       }
       return true;
    }
    
    function activate_link (href, target) {
    
    	if (href.indexOf ('javascript:') == 0) {
    		var code = href.substr (11).replace (/%20/g, ' ');
    		eval (code);
    	}
    	else {
    
    		href = href + '&_salt=' + Math.random ();
    		if (target == null || target == '') target = '_self';
    		window.open (href, target, 'toolbar=no,resizable=yes');
    
    	}
    
    }
    
    function open_popup_menu (type) {
    
    	var oPopup = window.createPopup ();
    	var div = document.getElementById ('vert_menu_' + type);
    	var table = document.getElementById ('vert_menu_table_' + type);
    
    	var w = table.offsetWidth;
    	var h = table.offsetHeight;
    
    	oPopup.document.body.innerHTML = div.innerHTML;
    	oPopup.show (-9, 17, w, h, document.getElementById ('main_menu_' + type));
    
    }
    
    
    function setVisible (id, isVisible) {
    	document.getElementById (id).style.display = isVisible ? 'block' : 'none'
    };
    
    function blur_all_inputs () {
    	var inputs = document.body.getElementsByTagName ('input');
    	if (!inputs) return 1;
    	for (var i = 0; i < inputs.length; i++) inputs [i].blur ();
    	return 0;
    }
    
    function focus_on_first_input (td) {
    	if (!td) return blur_all_inputs ();
    	var inputs = td.getElementsByTagName ('input');
    	if (!inputs || !inputs.length) return blur_all_inputs ();
    	inputs [0].focus ();
    	return 0;
    }
    
    function handle_basic_navigation_keys () {
    
    	if (scrollable_table && !scrollable_table_is_blocked) {
    
    		if (
    			(window.event.keyCode >= 65 && window.event.keyCode <= 90)
    			|| (window.event.keyCode >= 48 && window.event.keyCode <= 57)
    			|| (window.event.keyCode >= 96 && window.event.keyCode <= 105)
    			|| window.event.keyCode == 107 || window.event.keyCode == 109
    			|| window.event.keyCode == 219 || window.event.keyCode == 221
    			|| window.event.keyCode == 186 || window.event.keyCode == 222
    			|| window.event.keyCode == 188 || window.event.keyCode == 190 || window.event.keyCode == 191
    		) {
    
    			if (!window.event.altKey && !window.event.ctrlKey && document.toolbar_form && !q_is_focused) {
    
    				var input = null;
    
    				var children = scrollable_rows [scrollable_table_row].cells [scrollable_table_row_cell].getElementsByTagName ('input');
    				if (children != null && children.length > 0) {
    					input = children [0];
    				}
    				else if (document.toolbar_form && document.toolbar_form.q) {
    					input = document.toolbar_form.q;
    				}
    
    				if (input) {
    					input.value = '';
    					input.focus ();
    					return;
    				}
    
    			}
    
    		}
    
    		if (window.event.keyCode == 40 && scrollable_table_row < scrollable_rows.length - 1) {
    
    			var effective_scrollable_cell = Math.min (scrollable_table_row_cell, scrollable_rows [scrollable_table_row].cells.length - 1);
    			scrollable_rows [scrollable_table_row].cells [effective_scrollable_cell].className = scrollable_table_row_cell_old_style;
    			scrollable_table_row ++;
    			effective_scrollable_cell = Math.min (scrollable_table_row_cell, scrollable_rows [scrollable_table_row].cells.length - 1);
    			scrollable_table_row_cell_old_style = scrollable_rows [scrollable_table_row].cells [effective_scrollable_cell].className;
    			scrollable_rows [scrollable_table_row].cells [effective_scrollable_cell].className = 'txt6';
    			scrollable_rows [scrollable_table_row].cells [effective_scrollable_cell].scrollIntoView (false);
    			focus_on_first_input (scrollable_rows [scrollable_table_row].cells [effective_scrollable_cell]);
    			return false;
    
    		}
    
    		if (window.event.keyCode == 38 && scrollable_table_row > 0) {
    
    			var effective_scrollable_cell = Math.min (scrollable_table_row_cell, scrollable_rows [scrollable_table_row].cells.length - 1);
    			scrollable_rows [scrollable_table_row].cells [effective_scrollable_cell].className = scrollable_table_row_cell_old_style;
    			scrollable_table_row --;
    			effective_scrollable_cell = Math.min (scrollable_table_row_cell, scrollable_rows [scrollable_table_row].cells.length - 1);
    			scrollable_table_row_cell_old_style = scrollable_rows [scrollable_table_row].cells [effective_scrollable_cell].className;
    			scrollable_rows [scrollable_table_row].cells [effective_scrollable_cell].className = 'txt6';
    			scrollable_rows [scrollable_table_row].cells [effective_scrollable_cell].scrollIntoView ();
    			focus_on_first_input (scrollable_rows [scrollable_table_row].cells [effective_scrollable_cell]);
    			return false;
    
    		}
    
    		if (window.event.keyCode == 37 && scrollable_table_row_cell > 0) {
    			effective_scrollable_cell = Math.min (scrollable_table_row_cell, scrollable_rows [scrollable_table_row].cells.length - 1);
    			if (effective_scrollable_cell > 0) {
    				scrollable_rows [scrollable_table_row].cells [scrollable_table_row_cell].className = scrollable_table_row_cell_old_style;
    				scrollable_table_row_cell --;
    				scrollable_table_row_cell_old_style = scrollable_rows [scrollable_table_row].cells [scrollable_table_row_cell].className;
    				scrollable_rows [scrollable_table_row].cells [scrollable_table_row_cell].className = 'txt6';
    				focus_on_first_input (scrollable_rows [scrollable_table_row].cells [effective_scrollable_cell]);
    				return false;
    			}
    		}
    
    		if (window.event.keyCode == 39 && scrollable_table_row_cell < scrollable_rows [scrollable_table_row].cells.length - 1) {
    
    			scrollable_rows [scrollable_table_row].cells [scrollable_table_row_cell].className = scrollable_table_row_cell_old_style;
    			scrollable_table_row_cell ++;
    			scrollable_table_row_cell_old_style = scrollable_rows [scrollable_table_row].cells [scrollable_table_row_cell].className;
    			scrollable_rows [scrollable_table_row].cells [scrollable_table_row_cell].className = 'txt6';
    			focus_on_first_input (scrollable_rows [scrollable_table_row].cells [effective_scrollable_cell]);
    			return false;
    
    		}
    
    		if (window.event.keyCode == 32) {
    
    			var children = scrollable_rows [scrollable_table_row].cells [scrollable_table_row_cell].getElementsByTagName ('input');
    			if (children != null && children.length > 0) children [0].checked = !children [0].checked;
    		//	return false;
    
    		}
    
    		if (window.event.keyCode == 13) {
    
    			var children = scrollable_rows [scrollable_table_row].cells [scrollable_table_row_cell].getElementsByTagName ('a');
    			if (children != null && children.length > 0) activate_link (children [0].href, children [0].target);
    			return false;
    
    		}
    
    	}
    
    
    }
    
    

    ./Zanas/static/zanas.css.pm

    BODY {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #d5d5d5
    }
    TD, TH {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana
    }
    .bgr0 {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #ffffff
    }
    .bgr1 {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #485F70 /*#596084*/ /*#5f7452*/
    }
    .bgr2 {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #293869
    }
    .bgr3 {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #aab3d1
    }
    .bgr4 {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #efefef
    }
    .bgr5 {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #dededc
    }
    .bgr6 {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #888888
    }
    .bgr7 {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #b0b0b0
    }
    .bgr8 {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #d5d5d5
    }
    .bgr9 {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #dde5ef
    }
    .bgr13 {
    	FONT-SIZE: 11px; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #ffe0e0
    }
    .outter {
    	BORDER-RIGHT: #ffffff 1px solid; BORDER-TOP: #2d2d2d 1px solid; BORDER-LEFT: #2d2d2d 1px solid; BORDER-BOTTOM: #ffffff 1px solid; HEIGHT: 100%
    }
    .serv {
    	BORDER-RIGHT: #2d2d2d 1px solid; BORDER-TOP: #ffffff 1px solid; BORDER-LEFT: #ffffff 1px solid; BORDER-BOTTOM: #2d2d2d 1px solid; BACKGROUND-COLOR: #d0cfcf
    }
    .inner {
    	BORDER-RIGHT: #ffffff 1px solid; BORDER-TOP: #2d2d2d 1px solid; BORDER-LEFT: #2d2d2d 1px solid; WIDTH: 100%; BORDER-BOTTOM: #ffffff 1px solid; HEIGHT: 100%
    }
    .color0 {
    	FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana
    }
    .color1 {
    	FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana
    }
    .color2 {
    	FONT-SIZE: 11px; COLOR: #5e5e5e; FONT-FAMILY: Verdana
    }
    .color3 {
    	FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana
    }
    .color4 {
    	FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana
    }
    .header0 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #5f7452
    }
    .header1 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #293869
    }
    .header2 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #888888
    }
    .header3 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #b0b0b0
    }
    .header4 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #ffffff
    }
    .header5 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #efefef
    }
    .header6 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #dededc
    }
    .header7 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #ffffff
    }
    .header8 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #efefef
    }
    .header9 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #dededc
    }
    .header10 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #ffffff
    }
    .header11 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #efefef
    }
    .header12 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #dededc
    }
    .header13 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #888888
    }
    .header14 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #b0b0b0
    }
    .header15 {
    	FONT-WEIGHT: bold; FONT-SIZE: 9pt; COLOR: #ffffff; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #8e8e8e
    }
    .txt0 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #5f7452
    }
    .txt1 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #293869
    }
    .txt2 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #888888
    }
    .txt3 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #b0b0b0
    }
    .txt4 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #ffffff
    }
    .txt5 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #efefef
    }
    .txt6 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #dededc
    }
    .txt7 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #ffffff
    }
    .txt8 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #efefef
    }
    .txt9 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #dededc
    }
    .txt10 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #c8c8c8; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #ffffff
    }
    .txt11 {
    	FONT-WEIGHT: normal; FONT-SIZE: 9pt; COLOR: #000000; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #ffffff
    }
    .txt12 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #efefef
    }
    .txt13 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; BACKGROUND-COLOR: #ffffff
    }
    A {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #b0b0b0; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #b0b0b0; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #b0b0b0; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #b0b0b0; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #b0b0b0; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    
    A.lnk0 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk0:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk0:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk0:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk0:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    
    A.lnk1 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #596084; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk1:link {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #596084; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk1:active {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #596084; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk1:hover {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #596084; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk1:visited {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #596084; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    
    A.lnk2 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk2:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk2:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk2:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk2:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk3 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk3:link {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk3:active {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk3:hover {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk3:visited {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk4 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk4:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk4:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk4:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk4:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk5 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk5:link {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk5:active {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk5:hover {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk5:visited {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk6 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk6:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk6:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk6:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk6:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk7 {
    	FONT-WEIGHT: normal; FONT-SIZE: 10px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk7:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 10px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk7:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 10px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk7:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 10px; COLOR: #5e5e5e; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk7:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 10px; COLOR: #5e5e5e; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk8 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk8:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk8:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk8:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk8:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk9 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk9:link {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk9:active {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk9:hover {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk9:visited {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #ab300f; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk10 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk10:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk10:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk10:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk10:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk11 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk11:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk11:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk11:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk11:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk11:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #909090; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk12 {
    	FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk12:link {
    	FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk12:active {
    	FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk12:hover {
    	FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk12:visited {
    	FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk13 {
    	FONT-WEIGHT: normal; FONT-SIZE: 10px; COLOR: #ff0000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk13:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 10px; COLOR: #ff0000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk13:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 10px; COLOR: #ff0000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk13:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 10px; COLOR: #ff0000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk13:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 10px; COLOR: #ff0000; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk13:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #ffffff; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk14 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #415d8c; FONT-STYLE: italic; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk14:link {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #415d8c; FONT-STYLE: italic; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk14:active {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #415d8c; FONT-STYLE: italic; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk14:hover {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #415d8c; FONT-STYLE: italic; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk14:visited {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #415d8c; FONT-STYLE: italic; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk15 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #909090; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk15:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #909090; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk15:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #909090; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk15:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #909090; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk15:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #909090; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk16 {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #909090; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk16:link {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #909090; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk16:active {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #909090; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk16:hover {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #909090; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk16:visited {
    	FONT-WEIGHT: bold; FONT-SIZE: 11px; COLOR: #909090; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    
    A.lnk17 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk17:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk17:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk17:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    A.lnk17:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #293869; FONT-FAMILY: Verdana; TEXT-DECORATION: none
    }
    
    A.lnk18 {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none; font-style: italic
    }
    A.lnk18:link {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none; font-style: italic
    }
    A.lnk18:active {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none; font-style: italic
    }
    A.lnk18:hover {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none; font-style: italic
    }
    A.lnk18:visited {
    	FONT-WEIGHT: normal; FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana; TEXT-DECORATION: none; font-style: italic
    }
    
    INPUT {
    	FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana
    }
    SELECT {
    	FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana
    }
    OPTION {
    	FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana
    }
    TEXTAREA {
    	FONT-SIZE: 11px; COLOR: #000000; FONT-FAMILY: Verdana
    }
    

    ./Zanas/static/0.html.pm

    ./Zanas/Request

    ./Zanas/Request/Upload.pm

    package Zanas::Request::Upload;
    
    ################################################################################
    
    sub new {
    
    	my $proto = shift;
    	my $class = ref ($proto) || $proto;
    
    	my $self  = {};
    	$self -> {Q} = $_ [0];
    	$self -> {Param} = $_ [1];
    	$self -> {FH} = $self -> {Q} -> upload ($self -> {Param});
    	$self -> {FN} = $self -> {Q} -> param ($self -> {Param});
    	$self -> {Type} = $self -> {Q} -> uploadInfo ($self -> {FN}) -> {'Content-Type'};
    	my $current_position = tell ($self -> {FH});
    	seek ($self -> {FH},0,2);
    	$self -> {Size} = tell ($self -> {FH});
    	seek ($self -> {FH}, $current_position, 0);
    
    	bless ($self, $class);
    
    	return $self;
    }
    
    ################################################################################
    
    sub size {
    	my $self = shift;
    	return $self -> {Size};
    }
    
    ################################################################################
    
    sub fh {
    	my $self = shift;
    	return $self -> {FH};
    }
    
    ################################################################################
    
    sub filename {
    	my $self = shift;
    	return $self -> {FN};
    }
    
    ################################################################################
    
    sub type {
    	my $self = shift;
    	return $self -> {Type};
    }
    
    1;