Table of Contents
List of Tables
The Address Verification System (AVS) is used to check the likeliness that the customer claiming to own the credit card is the real owner.
Interchange allows you to create "virtual pages" by associating code (such as Perl subroutines or ITL tags) with parts of an URL. This can be used for anything from implementing one-click searches and orders to displaying on-the-fly data.
On every catalog access, the leading part of a requested URL is taken and checked if it represents a valid action. If it does, the action is invoked with the rest of the arguments provided in the URL.
Some of the predefined actions (which you might recognize from special page names that you access in your catalog) are:
process
- Generic form processing function
order
- Order items
scan
- Search based on submitted URL
search
- Search based on submitted form variables
A rectangular graphic image which is included on a content page for advertising and promotional purposes.
In Interchange, when "banner" or "ad" is used to describe the functionality of
the banner
tag and it does not necessarily mean an image, as you can
put anything in the content placeholder. In fact, banner examples from
???? HOW-TO use plain
text.
Voluntary ad unit guidelines as suggested by the Interactive Advertising Bureau (specified in pixels, in ascending order):
Micro Bar - 88x31
Button 2 - 120x60
Button 1 - 120x90
Half Banner - 234x60
Square Button - 125x125
Full Banner - 468x60
Vertical Banner - 120x240
Rectangle - 180x150
Medium Rectangle - 300x250
Vertical Rectangle - 240x400
Square Pop-up - 250x250
Large Rectangle - 336x280
Skyscraper - 120x600
Wide Skyscraper - 160x600
You can see the above dimensions in practice at MOTIVE Resources.
When giving links to your website to your partners, often times you want to embed an identification string that will link visits or eventual sales to the corresponding partner.
Interchange supports affiliate tracking; the source of the visit or order
is always accessible using [data session source]
.
So when giving links to your affiliates, make sure the affiliate code you assign them is embedded in one of the following ways:
http://myhost.mydomain.local/cgi-bin/standard/index?;;thesource (old Minivend 3 way, still works) http://myhost.mydomain.local/cgi-bin/standard/index?mv_pc=thesource (Interchange way, still works) http://myhost.mydomain.local/cgi-bin/standard/index?mv_source=thesource (current way)
Note that mv_source
was added as an affiliate code-passing option
only in Interchange 4.9.x. Before that, affiliate code was passed in mv_pc
.
The mv_pc
variable is used as a variable containing a random number
to prevent caching of pages. So if you want to use mv_pc as a way of
carrying over the affiliate code, it must contain at least one non-digit.
(mv_pc=A12
is a good affiliate code,
mv_pc=12
is not). If using mv_source
, affiliate
code is free of this restriction.
Sometimes you want the affiliate code to disappear from the URL after
the visitors get redirected to your page. To enable that feature, see
BounceReferrals
configuration directive.
In general terms, "array" can be considered a synonym for "list". It implies a list of elements.
In the Perl programming language, "list" refers to an unnamed list of any kind, while "array" refers to a practical, named Perl variable of list type.
Attributes (sometimes also called modifiers, options or params) are various "sub-features" of a product. If you are selling t-shirts in different colors and sizes, color and size are ideal candidates for item attributes. Interchange allows attributes to be set for each ordered item. This allows a varying size, color, or any other modifier to be attached to an otherwise common part number.
See UseModifier
for more information and concrete examples.
Besides setting modifier names in the config files (via the above
UseModifier
), you can also set them as
scratch variables with mv_UseModifier
. For example,
the above modifiers would be set with
[set mv_UseModifier]size color[/set]
.
This effectively allows you to have different product options for different
or even same product SKUs. Those specified in mv_UseModifier
at
the time of order will be used (just be careful, because you cannot set it
more than once on the same page).
![]() | Note |
---|---|
When choosing modifier names, do not use anything that begins with
You also need to make sure that no fields in your HTML
forms have digits appended to their names, if their non-digit name part
is equal to any used attribute. (This is because Interchange treats
say, |
In addition, setting SeparateItems
or mv_separate_items
places each ordered item on a separate line even if they have the same
SKU, simplifying attribute handling.
The modifier value is accessed in the item-list
loop with the
[item-modifier
tag,
and form input fields are created with
attribute_name
][modifier-name
.
This is similar to the way that quantity
is handled, except that attributes can be "stacked" by setting multiple
values in an input form (whereas there can be only one quantity field
for each item).
attribute
]
When you want to provide a series of modifiers for an element,
you can use the standard loop
tag (such as
[loop arg="
),
or you can use the built-in item item item
"]PREFIX-accessories
tag available with
most Interchange list operations.
The modifier value can then be used to select data from an arbitrary database
for attribute selection and display.
Below is a fragment from a shopping basket display form which
shows a selectable size with "sticky" setting. Note that the example
can only work within the item-list
tag.
<select name="[modifier-name size]"> <option [selected [modifier-name size] S] /> S <option [selected [modifier-name size] M] /> M <option [selected [modifier-name size] L] /> L <option [selected [modifier-name size] XL] /> XL </select>
It could just as easily be done with a radio button group as well (when you combine them with the <checked> HTML tag).
In addition, Interchange would automatically generate the above select box
if you called [accessories
or code
size][item-accessories size]
.
A boolean variable is one that can only represent a true or false value.
The variable type was named after George Boole.
Note that, in Interchange configuration directives parlance,
boolean
is used somewhat awkwardly. Instead of adhering
to above definition of boolean
, it actually signifies
an array in which you can search for the presence or absence of
a value. Real boolean variables are called yesno
s in
Interchange.
This naming confusion is unfortunate, but is fortunately quarantined to the configuration directives "space".
The HTTP (Web) protocol does not use the same mechanism to send data from server to client, and from client to server. Client to server communication must usually happen over CGI (Common Gateway Interface), by having users submit HTML forms.
Form data submitted usually consists of
pairs. One other option are just values following one another
(key
=value
); those are called
"ISINDEX" queries, and are not generally used with Interchange.
value1
+value2
+value3
...
Form submission can happen in two ways.
The "GET" method is very basic, as it
just embeds form values in the URL being sent to the server. One example of
a GET query is
http://myhost.mydomain.local/cgi-bin/ic/test?mv_arg=1&mv_pc=14
.
I think it's simple enough to notice variables mv_arg
and
mv_pc
being submitted. The GET method is very convenient
because all data is embedded in the URL, making it very easy to copy and
share links with other people.
It is recommended to create these links in Interchange with area
.,e.g.:
<a href="[area href="" form="param1=foo param2=bar"]">FOO & BAR</a> </a>
The other method is called POST. This way, the information is sent in a way not visible to the user. POST forms have this disadvantage of not being suitable for copy-pasting HTML links directly, but they do offer greater flexibility, especially if a lot of form data is being sent.
When forms are submitted using the POST method, they can also embed data
in the URL, effectively passing both POST and GET data at once. Interchange ignores
GET data on POST forms, but can be instructed to parse both using the
TolerateGet
directive.
In the end, it turns out you can just use GET in most situations. It's simpler, more convenient, and gets the job done just as well.
CGI variables in Interchange are accessible using the cgi
tag, and
only on a page directly following the form
submission. This is logical, of course. A page request reaches the Interchange daemon,
and it either has or doesn't have the accompanying form data; there's no
"history" mechanism included. (However, Interchange does allow you to save values
for future reference, usually in the value or scratch space).
Interchange is, by default, eager to collect user information, at least for the
duration of the session (so the users don't have to retype it again).
During processing, CGI variables are therefore propagated to the values
space, for subsequent requests. The FormIgnore
directive specifies
which CGI variables should not be propagated.
Users have complete control over CGI data they will send. Therefore, this input should never be trusted. It's raw data, and it is a security risk to save it in a database or display in a page before sanitization. The most common security risk is displaying HTML code which allows remote scripting exploits like cookie-stealing.
Never do something like the following:
[cgi VARNAME
]
or
[calc]
my $out = $CGI->{VARNAME
};
return $out;
[/calc]
Fortunately, Interchange offers a number of ways to take care of the data, usually by filtering it. For more discussion and help on filtering, see the filter glossary entry. A safe no-brainer approach is to just use the <filter>encode_entities</filter> filter on the input.
So, to obtain a "safe" value while keeping the original intact, use:
[cgi name=VARNAME
filter=entities]
or:
[filter entities][cgi VARNAME
][/filter]
or:
[calc]
my $out = $Tag->cgi({ name => 'VARNAME
', filter => 'entities' });
return $out;
[/calc]
or:
[calc]
my $out = $Tag->filter($CGI->VARNAME
, 'entities');
return $out;
[/calc]
One interesting feature in Interchange is that you can set CGI values yourself.
This has two common uses. You can set a value and pretend as
if it was sent by the user (so the rest of your code doesn't need to split
in two execution paths, depending on whether the variable was set or not).
Another thing you can do, is set special CGI variables (the
mv_*
ones that affect how Interchange processes the page) and let
Interchange do its magic. Heck, not only you can set them once, but you can change
their value during processing, achieving different
behavior in different parts of the page.
You can set values by providing
set=
attributes
to the VALUE
hide=1cgi
tag, or by simple assignment in Perl
($CGI->{
).
VARNAME
} = 'VALUE
'
Here's a complete list of ways to access CGI variables:
In ITL:
Access syntax | Notes |
---|---|
[cgi VARNAME ] | Doesn't prevent users from injecting ITL code; don't use it! |
[cgi name=VARNAME filter=entities] | A safe and correct way to go |
In embedded Perl:
Access syntax | Notes |
---|---|
$CGI->{VARNAME } | Retrieves raw CGI value; don't use before filtering |
$Tag->cgi({ name => 'VARNAME ', filter => 'entities' }); | A safe and correct way to go |
$Tag->filter($CGI->{VARNAME }, 'entities'); | A safe and correct way to go |
Each item in the cart consists of the following fields:
code
quantity
mv_ip
mv_mi
mv_si
Additional fields can be added with the UseModifier
and AutoModifier
configuration
directives.
A catalog is the basic functional unit in Interchange. A catalog is to Interchange what a web site is to a Web server.
Catalogs to configure and offer on the Interchange server are defined in the
global configuration file, interchange.cfg
(or some of the files it includes, of course,
depending on the actual file layout). The definition directive is called
Catalog
. The directive you should use to register a catalog is
— incidentally — Catalog
.
Each catalog directory (specified as one of Catalog
parameters) must
have the file catalog.cfg
in it. For the mandatory directives that need to be
present in the file, see Catalog
reference page.
For the general syntax accepted in configuration files, see configuration glossary entry.
For the list of available configuration directives, see Interchange Reference Pages: Configuration directives.
The check-out process consists of users filling in information via HTML forms, and Interchange verifying their input on arbitrary number of levels using so-called profiles.
Profiles can be defined in external files (and activated using
OrderProfile
) or in scratch variables. External files are,
by convention, kept in CATROOT/etc/
and
begin with profiles.
. Multiple profiles
can be defined in each file.
You can learn about the principle and syntax of the profile files in the profile glossary entry. Only when the input "passes" the profile check, is the check-out process able to proceed.
Most of the time, you will want the successful check-out operation (order completion) to generate some kind of notification. In most common setups, this will include e-mailing order reports.
Simple order report file, CATROOT/etc/report
, defines
the layout (template) of the order report. All form variables are accessible
from the report file by using the familiar Perl $
syntax.
Order Date: $date Name: $name Email address: $email Shipping address: $addr Town, State, Zip: $town, $state $zip Country: $country
You can specify fully-configurable order reports by setting the hidden
form variable mv_order_report
to an existing Interchange catalog page.
This page will be processed (interpolated and all) as standard Interchange
page before being sent by email. That said,
you see you could include HTML in the file. Although many mail clients
will parse HTML, it seems that the initial excitement
among the ordinary people vanished and they
again prefer plain-text e-mails. If you wanted to provide a HTML
version, you could always provide a link to a copy on your web server.
An order counter can be enabled simply — just set the OrderCounter
directive to the appropriate file name.
An incrementing count of all orders will be
kept and assigned as orders are placed. By default, the number
starts at 0
, but you can edit the file and change the
starting or current number at any time.
This feature is made possible by the File::CounterFile
Perl module.
The default basket and order pages contain a number of form fields,
allowing customers to enter the necessary information. This, however, can't
satisfy all individual needs. To remove some of the fields, simply delete
them from the HTML pages (or, better yet, disable by using the
comment
tag). Do not forget to also deactivate any entries in
the profile files.
To add new fields, simply add them to the pages. The information will
automatically be included in the report files. Here's a template you
could re-use for your own fields, replacing town
with your values:
<input type="text" name="town" value="[value town]" size="30" maxlen="40" />
![]() | Note |
---|---|
Using |
Interchange supports multiple catalogs, and therefore splits its configuration
into two pieces. One is global, specified in interchange.cfg
, and affects every
catalog running under the same Interchange server installation.
The other — catalog part, is specified in each catalog's
CATROOT/catalog.cfg
, and has no effect on other catalogs.
Each configuration directive is accessible on global or catalog level,
or both. There's a special field named "DIRECTIVE TYPE
"
present in each directive's reference page, where you can look this up.
Keep in mind, however, that the directives on
global and catalog level don't have to be parsed by the same code —
in fact, they're mostly parsed by related but different code blocks.
Configuration directives are normally specified with the directive name as the first word on the line, with its value or values following. Capitalization of the directive name is not significant, but it helps readability and consistency. Additionally, any leading and trailing whitespace is removed ("stripped") before processing. Here's a simple example:
DirectiveName
value
Besides specifying directive values inline, one can conveniently use the following syntax to obtain value from external files:
DirectiveName
<include_filename
![]() | Note |
---|---|
Note that this syntax can be used anywhere on a line, such as in
|
Files included from interchange.cfg
are relative to ICROOT. Files included
from catalog.cfg
are relative to specific catalog's CATROOT.
So-called "here document" syntax is supported as well. You can use it to spread directive values over several lines, with the usual Perl <<MARKER syntax (but unlike Perl, Interchange syntax uses no semicolon to terminate the marker). The closing marker must be the only thing on the line. No leading or trailing characters are allowed, not even whitespace. Here is a hypothetical directive using a here document:
DirectiveName
<<EODsetting1 setting2 setting3
EOD
The above is equivalent to:
DirectiveName
setting1 setting2 setting3
Other configuration files can also be included from the current one. For example, common settings can be defined in a single file:
include common.cfg
Or all files loaded from a directory:
include usertag/*
The familiar ifdef/endif
and ifndef/endif
pairs can be used to affect configuration processing:
Variable ORDERS_TO email_address ifdef ORDERS_TO ParseVariables Yes MailOrderTo __ORDERS_TO__ ParseVariables No endif ifdef ORDERS_TO =~ /\@foo\.com$/ # Send all orders at foo.com to one place now # Set ORDERS_TO to stop default setting Variable ORDERS_TO 1 MailOrderTo orders@foo.com endif ifdef ORDERS_TO eq 'nobody@nowhere.com' # Better change to something else, set ORDERS_TO to stop default Variable ORDERS_TO 1 MailOrderTo someone@somewhere.com endif ifndef ORDERS_TO #Needs to go somewhere.... MailOrderTo webmaster@mydomain.local endif
It is possible to define configuration directives for the duration
of the block, using the <
notation:
DIRECTIVE
VALUE
> ... </DIRECTIVE
>
Variable HELLO Hello World! ParseVariables No Message Our greeting is: __HELLO__ # Will print: Our greeting is: __HELLO__ <ParseVariables Yes> Message Our greeting is the shiny __HELLO__ # Will print: Our greeting is the shiny Hello World! </ParseVariables> Message Our greeting is back to: __HELLO__ # Will print: Our greeting is back to: __HELLO__
Interchange, of course, offers a way to define variables. Variables defined
in your interchange.cfg
or catalog.cfg
can be referenced from both configuration files
themselves and the usual Interchange pages later, when the catalog is running.
Variables are defined using the Variable
directive (reading its
short reference now would be a good idea). The usual way to expand
a variable to it's value is to use the
__
notation.
This notation, however, is by default not enabled in RHS
("Right-Hand Side") values in configuration files. To enable it, use the
VARIABLE_NAME
__ParseVariables
directive which immediately affects the way
Interchange parses variables in config files. Here's an example to clarify
what we're talking about:
# Let's define two variables Variable SERVER_NAME myhost.mydomain.local Variable CGI_URL /cgi-bin/ic/tutorial # Let's make VendURL directive be a combination of __SERVER_NAME__ and __CGI_URL__ VendURL http://__SERVER_NAME____CGI_URL__ # To your surprise, after the above, VendURL would literally contain # "http://__SERVER_NAME____CGI_URL__". This is not what we want, so # we need to enable ParseVariables to achieve the desired effect: ParseVariables Yes VendURL http://__SERVER_NAME____CGI_URL__ ParseVariables No # VendURL now contains "http://myhost.mydomain.local/cgi-bin/ic/tutorial"
It may come to you as a surprise, to learn that any configuration directive
can be "tied" to a Perl subroutine (if the Tie::Watch
Perl module is installed). This allows for a kind of triggers, watch points,
or numerous other interesting applications.
Similar to "here documents" ("<<
"), subroutine watches
are defined using the "<&
" notation. Consider the
following example:
MailOrderTo orders@myhost.mydomain.local MailOrderTo <&EOF sub { my($self, $default) = @_; if($Values->{special_handling}) { return 'vip@myhost.mydomain.local'; } else { return $default; } } EOF
When Interchange tries to retrieve the value of the MailOrderTo
configuration
directive (usually to e-mail out an order), our subroutine watch is called. In
turn, it returns a special value (a separate e-mail address) for customers
having value "special_handling
" defined in their
session. For the rest, it simply returns the default value.
Now that you've grasped the basics, there's more to the story.
From the above example, you see our watch subroutine was called in style
of &{$subref}(
.
"SELF
,
PREVIOUS_VALUE
)SELF
", meaning what it usually means in
Perl code, is a reference to the appropriate Tie::Watch
object. "PREVIOUS_VALUE
" is simply the previously
set value for a directive (usually its default). Those are the standard
two arguments we receive in a subroutine watch if the configuration directive
was of type SCALAR (defined to accept one string or text value).
![]() | Note |
---|---|
Subroutine watches must be defined after the configuration directives have been set to their values. Setting values after subroutine watches will simply destroy them (the watches) and have unpredictable effects. |
If the configuration directive being watched was a list (type ARRAY), the
subroutine would be called in pattern
&{$subref}(
.
("SELF
,
INDEX
,
PREVIOUS_VALUE
)INDEX
" would be an array index of the
item accessed). Setting watch points on arrays that you don't control
completely is not recommended. (Namely, most Interchange subroutines call arrays
in their list context, and no access method is provided for that).
Finally, if the configuration directive watched was a hash (type HASH),
the subroutine would be called in pattern
&{$subref}(
.
("SELF
,
KEY
,
PREVIOUS_VALUE
)KEY
" would be a name of the
hash value accessed).
In the following example, we tie the Variable
configuration directive.
This is not recommended for performance reasons — Variable
directive is called very often and should not bear any extra overhead). But
it illustrates the power of this operation:
Variable TESTIT Unwatch worked. Variable <&EOV sub { my ($self, $key, $orig) = @_; if($key eq 'TESTIT') { # only the first time if($Scratch->{$key}++) { $self->Unwatch(); return $orig->{TESTIT}; } else { return "Tie::Watch works! -- name=$Values->{fname}"; } } else { return $orig->{$key}; } } EOV
The first time __TESTIT__
is called for an individual user,
it would return the string "Tie::Watch works! -- name=
"
along with their first name (if they provided one at some point).
On a second access (again, for an individual user),
the watch would be dynamically dropped and the default value of the
variable NAME
TESTIT
returned.
All other variables would operate as usual.
Cookies are typically short
parts supported by the HTTP protocol. Their importance is in the fact that
the server can send them to clients, and read and modify their value.
In addition, cookies have their expiry time, which can be set, also by the
server, to any intended value.
Whether Interchange will try to land session cookies in clients' browsers is
determined by the key
/value
Cookies
directive, and their default expiry time
is set by SaveExpire
.
Clients can control whether they reject or accept cookies from all or some sites, and can enforce their expiry time.
Web page requests arriving from users are "anonymous" and basically unrelated to each other even if they are coming from the same user. This is because the HTTP protocol is "stateless" and server can't map requests to specific clients based on just the IP addresses it sees. Therefore, cookies are a crucial mechanism for preserving state information in programs with web-based interfaces. By reading the session ID value (stored in a cookie on client's computer), the server can now recognize associate users with their ongoing, active sessions.
![]() | Interchange and its non-dependence on client cookies |
---|---|
Many web-based solutions require that the clients accept storage and retrieval of cookies. When cookies are not enabled on client side, the usage of the site is limited, or clients are even denied access completely.
Interchange, on the other side, does not require client support for cookies.
If the storage of cookies is denied or unsupported by the client,
Interchange appends session
information in generated URLs and uses them to continue keeping track of
user sessions. (An example session ID "embedded" in an URL looks like
Session IDs embedded in URLs should theoretically be equivalent to cookies,
and they almost are. The drawback is namely the fact
that once you visit a non-Interchange page, you lose the |
When a new client accesses its first page from the Interchange catalog, Interchange sends it both the requested page and a cookie. At that point, Interchange can't know whether the client accepted the cookie or not — it has to wait for the client to initiate the second page request. (Historically, many application servers always bounced the first request to provoke the second access from the client and to check for cookie support. Interchange does not do it.)
If the user sends a cookie back to Interchange (which, as you see, can happen no sooner than on second request), Interchange knows the client is cookie-capable and there's no need to embed session ID in URLs.
One possibly confusing thing is that, by default, Interchange always appends
session ID information to the URLs it generates — even if clients
have no cookie-handing problems.
If the scratch variable mv_no_session_id
is set in the user's
session, the session ID will not be appended to the URL. Furthermore,
on a somewhat related note, if the scratch
value mv_no_count
is set, then the page count
(mv_pc=
) will not be
appended either. "PC" is a page counter that serves to prevent client browsers
from caching pages.
random
> In any case, you can create the encrypted pwds with: > > 1) makepasswd --crypt > makepasswd --crypt-md5 > > 2) or help yourself with htpasswd/htpasswd2 and htdigest/htdigest2 > that come with apache (htpasswd for crypt(), htdigest for MD5): > > htpasswd2 -c /tmp/temp test > htdigest2 -c /tmp/temp test test It's done with simple Unix crypt by default. To have Apache's htpasswd output to stdout instead of a file, you can do: htpasswd -n test Jon
Data Source Name
or a DSN is an application's data source identifier. It provides all parameters for the connection to a database.
In its simplest form, it has the format of
protocol
:
subprotocol
:
host
:
port
:
database
It's also possible to explicitly name the parameters, like this:
protocol
:
subprotocol
:
database=DBNAME;port=PORT
In general, databases contain information, usually in tabular format, where columns define the names and types of contained data, and rows represent entries — database records.
Interchange is primarily using databases to just retrieve values from specific tables, and does not use any higher-level functions of RDBM databases (such as views, triggers, or stored procedures in PostgreSQL). Such things can, however, be implemented in the database independently of Interchange, as Interchange will properly pass any warning or error messages back and forth.
We should say right away that Interchange is completely database-independent. The choice of actual database types that can work with Interchange is large, and Interchange can use some database-like methods automatically when you're not explicitly interested in paying attention to databases working behind the scenes.
Common features are transparently available everywhere (with absolutely no code hacks or special cases required), regardless of the underlying database type used. In addition, almost no field names are hard-coded, allowing for unlimited flexibility.
Keep in mind that the terms database
and
database table
actually mean the same thing in Interchange
parlance - a database table
.
Interchange works with GNU DBM, DB_File, SQL, LDAP and in-memory types of databases.
Regardless of type or other characteristics, each database must be registered
on a catalog level before it's ready to be used, and this is achieved
using the Database
configuration directive. It's useful to remember
at this point that multiple catalogs can share the same database.
Three parameters need to be present in a basic Database
definition:
an arbitrary database name, text source file with initial content, and the
type of the database.
Text source files are not databases themselves, of course (for performance and other reasons); they are only used to provide initial data for the corresponding database tables.
By default, all database source files are kept in the
products/
subdirectory of your
CATROOT. The ProductDir
directive controls the exact location.
The ASCII files can contain carriage return
(^M
) characters even in data fields, but must have a
newline character (^N
) at the end of line to properly
separate records.
Mac users uploading files must use ascii mode, not binary mode.
Interchange's default ASCII delimiter is TAB. Keep in mind that the items must be separated by a single delimiter (that is, by a single TAB only). Due to the nature of TABs, TAB-delimited files look messy and unaligned when viewed in a text editor. Do not try to fix these; better use the te utility that comes as part of the Interchange distribution to edit such files more conveniently.
Interchange can manage an unlimited number of arbitrary database tables and database table types. Several flexible delimiter schemes are available "out of the box":TAB-delimited file (Type 1, the default): Fields are separated by TAB characters. No whitespace is allowable at the beginning of the line.
code description price image SH543 Men's fine cotton shirt 14.95 shirts.jpg
(You might notice that the field names and values above are not properly aligned. This is the nature of tab delimited files.)
Using the default TAB delimiter is recommended if you plan on searching the ASCII source file of the database.
LINE (Type 2):
Fields are specified each on its own line, separated by the newline
(\n
) character. One blank line separates a record
from another record.
code description price image SH543 Men's fine cotton shirt 14.95 shirts.jpg
%% (Type 3):
Fields are separated by the literal combination of
"\n%%\n
", while the records are separated
by "\n%%%\n
". Users fond of the Unix "fortune"
program may find this format familiar.
code %% description %% price %% image %%% SH543 %% Men's fine cotton shirt %% 14.95 %% shirts.jpg
CSV-delimited file (Type 4): Fields are enclosed in quotes and separated by commas. Again, no whitespace should be at the beginning of the line.
"code","description","price","image" "SH543","Men's fine cotton shirt","14.95","shirts.jpg"
CSV-delimiter schemes might cause problems with ASCII text searching routines.
PIPE-delimited file (Type 5):
Fields are separated by the pipe ("|
") characters which
resemble vertical lines. No whitespace is allowable at the beginning of the
line.
code|description|price|image SH543|Men's fine cotton shirt|14.95|shirts.jpg
PIPE-delimited files perform fairly well with ASCII text searching routines.
TAB-delimited file (Type 6):
<reserved> (Type 7):
SQL (Type 8):
LDAP (Type 9):
![]() | Note |
---|---|
Field names are usually case-sensitive (in fact, that depends on the underlying database type). Always be consistent when naming or referencing fields and you'll avoid the trouble. All lower or all upper case names are recommended. |
If a database is specified to be one of the first six types, then the database will automatically be converted to a more efficient internal structure. Those include DB_FILE, GDBM, or MEMORY. The order of preference and the selection is:
As hinted above, you do not need to use an external SQL database. If you only have a small data set, you could use Interchange's internal databases. This is a tremendous gain for small and quick setups, or ad-hoc Interchange evaluation. However, some functions (order management, for example) will be slower and not as robust without an SQL database. SQL is strongly recommended for at least the state, country, orderline, transactions and userdb tables. Any other tables that will have programmed updates, such as inventory, are also best placed in SQL.
![]() | Database performance |
---|---|
Do not, however, try to optimize too soon and for no measurable difference. Do not fall in the jaws of premature optimization, your worst enemy. |
Generally, you should make an additional effort of configuring and using SQL databases to achieve Interchange's full potential. Using SQL also makes your data sets easily available for integration with other applications.
In any case, database import and conversion routines are already available in Interchange and you can use them at any point.
Speaking of the source files' behavior, if a file named
is present
in the same directory as
table
.sql
, then database
table will never be imported from the ASCII text source file.
If there is no table
.txt
,
the DBI/SQL import will happen once, at
Interchange startup or catalog reconfiguration time (and the
table
.sql
file will be
created);
Interchange will connect to the SQL database using the specified DSN
(DSN is a standard DBI parameter meaning "Database Source Name").
The table will be dropped (if it already exists in the database) using a
line similar to
table
.sqlDROP TABLE
.
This will occur without warning, but table
NoImport
can be used to
prevent it or otherwise change the default behavior.
The table will then be created again and populated with text source
file data.
If there are any
COLUMN_DEF
specifications present in interchange.cfg
, catalog.cfg
or
products/
,
they will be used to create SQL table specification
(which is recommended for clean and correct database
layout). If there aren't any, however, then the key (first field in the text
file, by default) will be created with the type table
.sqlchar(16)
,
and all other fields will be created as char(128)
. This is
very unfortunate, but the best Interchange can do without your help.
Table creation statements will be written to the error.log
along with, of course, any errors. From our experience, the most common
mistake at this point is choosing column names that sound perfectly reasonable,
but also happen to be reserved keywords in MySQL. (The error messages
appear to be misleading here, so you better take a look at the
list
of reserved MySQL keywords before losing patience with the problem).
Once the database (database table actually, remember?)
is created, the text source file will be imported into it.
For this step to succeed, data typing must be user-configured. In other words,
if say, word "none
" is placed in a field while the field
in question
is defined to be of numeric type, database import will not succeed;
consequently, the problematic catalog won't configure successfully
(it will be skipped) and it won't be available when Interchange starts up.
For a complete discussion, please see the Database
configuration
directive.
By file-based databases we primarily assume GNU DBM and DB_File. We also call those database types "internal", since in the absence of say, an SQL definition, all inferior formats (such as text source files) are automatically converted to some kind of a file-based database.
Those database types usually work in a way that, on every client access, the appropriate database text source file is checked for being newer than the actual DB file itself. When it happens that it is, the database table is re-imported from the text source file on the fly, and the routine then proceeds as usual.
![]() | Database updates |
---|---|
It is important to note that, when using Interchange internal database methods, all changes in the text source files cause the databases to be re-created. This can have unwanted effects if the database was modified from within Interchange and the contents have not been written back to the text source files. Another common problem are larger data sets that take noticeable time to get imported to (or exported from) the internal database.
The exact behavior can be controlled via the |
To check if you have GNU DBM and GDBM Perl support available, run
perl -le'require GDBM_File and print "I have GDBM."'.
To check if you have Berkeley DB and DBM Perl support available, run
perl -le'require DB_File and print "I have Berkeley DB."'.
Sometimes you want to use Berkeley DB even if GNU DBM is installed and would
naturally take precedence; in such cases, set the
MINIVEND_DBFILE
environment variable to a true value
(setenv MINIVEND_DBFILE 1 in csh,
MINIVEND_DBFILE=1 ; export MINIVEND_DBFILE in
sh,
b(a)sh or
ksh).
It is also possible to use Berkeley DB for just specific databases.
For a complete discussion, please see the Database
configuration
directive.
Memory Interchange databases use Perl hashes to store the data directly in memory. Every time the Interchange server is restarted, it will re-import all in-memory databases for every catalog.
Memory databases are used by default only if no database type is explicitly specified, and there is no DB_File or GNU DBM found on the system. Otherwise they can be used for small but high-traffic tables. Keep in mind, however, that since their contents are not saved back to the text files, you'll want to either take care of the data export yourself, or keep the tables stuffed with read-only data.
if you want to force memory databases despite of GDBM_File or DB_File
being present, set the MINIVEND_NODBM
environment variable
to a true value (look previous chapter for hints on setting it).
It is also possible to use memory type for just specific databases.
Memory databases import will be performed once at every Interchange startup or catalog reconfiguration time.
For a complete discussion, please see the Database
configuration
directive.
We are trying not to impose any database structure that would require our own tools to maintain the data. We always want to keep it such that Interchange data can be maintained via a spreadsheet processor or foreign database tools.
This section describes naming and file usage conventions used with Interchange. This is very important for both understanding Interchange and developing your own custom solutions which build upon officially recommended practices.
Term definitions:
key or code
The words reference the database key. In Interchange, the key is usually the product code or SKU, which is the product part number. Otherwise, key values may be used to generate relationships to other database tables.
It is recommended that the key be the first column of the database text source file, since Interchange's import, export, and search facilities rely on this practice.
field or column
The vertical row of a database. One of the columns is always the key and it is usually the first one, as explained above.
table or database
A table in the database. Because Interchange has evolved from a single-table database to an access method for an unlimited number of tables (and databases, for that matter), a table will occasionally be referred to as a database. The only time the term database refers to something different is when describing the concept as it relates to SQL, where a database contains a series of tables. While Interchange cannot create SQL databases, it can drop and create tables within databases if given the proper permissions.
Interchange uses one mandatory database, which is referred to as the
products database. In the supplied demo catalog
(and in the most of real-world solutions as well), the primary
database is directly called products
and the ASCII source is kept in the products/products.txt
file.
This is also the default file for searching contents with
the search engine, such as Glimpse, HTDig or Swish.
![]() | Note |
---|---|
Interchange also has two optional databases that are specified in special, fixed formats:
The two above tables cannot be stored in any user-specified format. |
No debugging options are enabled by default in Interchange. See DebugFile
for
a quick introduction to enabling debug messages.
Before DebugHost
can be used to restrict diagnostics to specfic
set of hosts, the debug mode itself has to be enabled using DebugFile
.
Dereferencing is strictly a computer-programming issue, but we will try to explain it in very brief and comprehensible terms, so that you understand the idea of dereferencing and its practical effect when data structures are copied.
Let's say we want to compose a list of a few automobiles. Each entry in the list will contain the fields model, year and mileage.
Theoretically speaking, to solve this real-world problem with the help of a computer, we would create a template (containing the three fields), and produce one instance of the template for each car we add to our list. (How this list is created, how the elements are added and how they relate to each other is irrelevant here).
One imaginary list with three instances could be visually represented in the following way:
Model Year Mileage list[0] { 'Fiat', 1996, 177940 } list[1] { 'Citroen', 2001, 66000 } list[2] { 'Citroen', 2002, 23000 }
There is only one copy of this list in computer memory, and we read or modify the elements by obtaining references (or, pointers) to appropriate entries.
If we take list
above to contain the list of references
to the entries, we can
use list[0].Model
to retrieve the value "Fiat", or
list[2].Year
to retrieve "2002". For both of those fields,
a reference was first dereferenced (or,
followed) to reach the actual data fields.
When list elements need to be copied to another location (usually as part of some bigger plan which, again, we are not interested in), they can be copied by value (with dereferencing) or by reference (obviously, without dereferencing). With copy by value, you would end up with 2 references and 2 different lists (initially they would be the same but afterwards you could modify each with no connection to the other). In case of copy by reference, you would again have 2 references, but both pointing to the same list. Modifying data through any of the two references would have impact on both.
So, when a data structure (or its element) is said to be copied without dereferencing, then in case it was a reference, it is still copied in itself, but all copies point to the same location. In other words, the data is not duplicated, only the access points are.
Product discounts in Interchange can be set at any time. The discounts apply only to the customer receiving them, so you can set discounts based on membership in a club or other arbitrary means.
Discounts are defined using the discount
tag,
and are of the following types:
Discount on a specific item - a discount for one particular item.
Key to use with the discount
tag is the product's SKU
Discount on all items - a discount applying to all items.
Key to use with the discount
tag is ALL_ITEMS
Discount on a particular item at particular time -
a discount for an individual line item, applied if the mv_discount
attribute is set (usually with embedded Perl)
Order discount - a discount applied not to individual products, but to the
total order amount.
Key to use with the discount
tag is ENTIRE_ORDER
Discounts within the discount
tag are specified using a formula.
The formula is scanned for
the $q
and $s
variables which are
substituted for the item quantity and subtotal respectively.
The variable $s
is saved between
iterations, so that the discounts can be cumulative.
In case of individual item discounts, the formula must be constructed to
handle all instances of a particular SKU found in the user's basket.
There are many ways how same SKU might occur
multiple times in the user's basket (for example, with SeparateItems
enabled) — the same formula will be invoked on every occurrence and
it should always give out the correct individual subtotal.
In case of an entire order discount, the formula is usually simpler and defines a flat discount amount or percentage.
Discounts are applied to the effective price of the product, that is — the price obtained after applying price adjustments.
For examples, see the discount
reference page.
In April 2005, Interchange added support for "discount spaces" (using CGI
variable mv_discount_space
),
in a manner akin to values space (mv_values_space
) or
named shopping cart (mv_cartname
). See DiscountSpacesOn
and DiscountSpaceVar
for usage examples.
This entry describes how Interchange processes a request after assiging it to a catalog.
Running macro(s) defined by Preload
directive.
Checking authorization.
Session, cookie and robot handling.
Redirect GET requests with affiliate code when BounceReferrals
directive is enabled. Effectively terminates processing of the request.
...
Unix Epoch resource at Wikipedia.
User sessions in Interchange are usually kept as files in the
session/
directory (or inside
a DBM database) for each catalog. Since session
data is not deleted after sessions end (or timeout), periodic expiring
needs to be set up to keep the session database or session files from growing
too large, wasting disk space and slowing down directory lookups.
There's no worry that expiring will do any harm, because all our scripts only clean up unused sessions. Active users will not notice any change.
The simplest way to expire catalog's session files is to run
expire -c CATALOG_NAME
.
For convenience, there is also expireall script which
reads all catalog entries in interchange.cfg
and runs expire on them.
The expire script accepts a -r
option
which, when DBM sessions are used, tells the script to reorganize database
files and recover lost disk space.
On a UNIX server, it's most useful to run expireall
from crontab
. As the Interchange user, run
crontab -e to edit crontab data, and enter something like:
# once a day at 4:40 am 40 4 * * * /PATH/TO/perl /PATH/TO/INTERCHANGE/bin/expireall -r
![]() | Note |
---|---|
If a session saved search paging files in |
When file-based sessions are used (no DBM), then you can use a custom script like this:
#!perl # expire_sessions.pl -- delete files 2 days old or older # invoke as: /PATH/TO/perl expire_sessions.pl /PATH/TO/CATALOG/session/ ... my @files; my $dir; foreach $dir (@ARGV) { # just push files on the list if (-f $dir) { push @files, $_; next; } next unless -d $dir; # get all the file names in the directory opendir DIR, $dir or die "opendir $dir: $!\n"; push @files, ( map { "$dir/$_" } grep(! /^\.\.?$/, readdir DIR)); } for (@files) { unless (-f $_) { warn "skipping $_, not a file.\n"; next; } next unless -M $_ >= 2; unlink $_ or die "unlink $_: $!\n"; }
This script can be adjusted as necessary. Refinements might include reading the file to "eval" the session reference and expire only customers who are not registered members.
If your files get chown-ed to root every day, then you probably used root's instead of Interchange user's crontab file. Either move the crontab to the Interchange user, or use su to swith users from the root account:
44 4 * * * su -c "/PATH/TO/INTERCHANGE/bin/expireall -r" IC_USERNAME
The above does not, however, clean temporary files from the ScratchDir
directory. We don't often use the expire scripts any more. We just use
a small standalone script clean_session_tmp
:
#!/bin/sh for DIR in $*; do for i in session tmp; do if test -d "$DIR/$i"; then find $DIR/$i -type f -mmin +480 | xargs --no-run-if-empty rm find $DIR/$i -depth -type d -empty -mtime +2 | xargs --no-run-if-empty rmdir else echo "$0: $DIR/$i doesn't exist."; fi done done
using a cron entry similar to:
44 0,4,8,12,16,20 * * *DIR/bin/
clean_session_tmp/path/to/catdir1
/path/to/catdir2
The set of configuration directives prefixed "External" allows export of Interchange variables for use by other programs and languages, such as PHP, Ruby or Python.
The three directives are External
, ExternalFile
and
ExternalExport
.
The current functionality is just a proof of concept and is not for serious use. The interface needs serious work.
A working example for accessing data from PHP is provided in the
External
reference page.
In general programming terms, a false value is one
that is either 0
(zero) or ""
(an empty string).
Since the Perl programming language has a notion of undefined values, they too are considered false.
With Interchange 5.3.0, Interchange supports so-called "features". The whole purpose of the new "Feature" facility is to allow easy installation of new capabilities to Interchange.
Interchange already has the convention of "extensions" which allow you to put together features to add to Interchange. But the installation is manual, and requires good docs to make it easily installable for end-users. Also, many features require access to the global configuration. There's also another problem at sight, namely that of feature creep, since everything was just being added to the "standard" catalog.
The basic mechanism is simple:
Inside "feature" modules, there are three special kinds of files, called by
extensions .global
, .init
and
.uninstall
.
(In the included quickpoll
feature, these are named
quickpoll.global
and quickpoll.init
).
If a file has the extension .global
, it is added to the global
configuration. The included quickpoll
feature, for example,
adds the quickpoll
ActionMap, and two usertags:
poll-answer
and ascii-graph
.
If a file has the extension .init
, it is run once —
the first time
the target catalog is accessed. Again, in the quickpoll
example, it is used to add
mv_metadata entries and a couple of sample polls.
All other files in the directory are catalog configuration
(quickpoll.catalog.cfg
for a concrete example).
It could have also been
broken up into say, files quickpoll.sql
and
quickpoll_answer.sql
.
All subdirectories contain files which are copied to the
catalog directory with the same relative path. In this
case,
ICROOT/features/quickpoll/templates/components/quickpoll
would go to
CATROOT/templates/components/quickpoll
.
The .init
file, when run, sends its output to
(and that would be ConfDir
/init/FEATURE
/FEATURE
.initetc/init/quickpoll/quickpoll.init
for
the concrete example.)
Once it is run, the existence of the file prevents it being run again.
Uninstall files, those with the .uninstall
extension,
are ITL files that can perform any uninstall
functions, and they run with temporary AllowGlobal
access to
allow dropping of tables, unlinking of files, etc.
Automated uninstall features include removing any files installed
as a part of the feature -- providing they have not changed. If
the file was edited and is not identical to the originally installed
file, then it is left there and a warning issued.
Uninstall creates file
to note the uninstall, and which prevents the Init process from happening
again (assuming Interchange has not been restarted since the
feature installation).
ConfDir
/init/FEATURE
/uninstall
The uninstall routine is called with the uninstall_feature
tag.
![]() | Caution |
---|---|
The catalog user must remove the
Also, there is a short window where a SQL table, if dropped
as a part of the uninstall procedure, could be re-instantiated
based on the existence of the configuration in memory. It
is recommended that if |
Interchange filters are usually small routines that perform various (arbitrary) transformations of user input.
There are filters that trim the text to a specified maximum length,
substitute characters in strings, make user input display- or storage-safe,
or even serve as value
equivalents.
Using existing filters and creating new ones is very simple.
Interchange "form actions" are basically Perl subroutine definitions that you can
execute upon form submission. Which form action to trigger is specified by the
mv_action
form variable.
Successfully executed form action should return a true value, upon
which Interchange would display the page specified in mv_nextpage
.
If the form action returns false, Interchange will not display any page.
Using FormAction
, you can both define new and override existing
form actions.
"GET" resource at Yukka Korpela's website and faqs.org.
In Interchange parlance, "gating" is the process of controlling access to certain pages. See Q: HOW-TO entry for further information and examples.
![]() | Note |
---|---|
In Interchange versions prior to 5.7.0, |
The first step is to fulfil the prerequisites of the payment module
(listed in the individual module documentation below) and enable the module
in the global configuration file with the Require
directive:
require Vend::Payment::NetBilling
If we are using only one gateway in a catalog, setting up
MV_PAYMENT
variables is sufficient.
Variable | Purpose | Notes |
---|---|---|
MV_PAYMENT_MODE | gateway mode name | |
MV_PAYMENT_HOST | gateway host name | optional (predefined in module) |
MV_PAYMENT_ID | merchant identifier | |
MV_PAYMENT_SECRET | secret part of credentials | password or certificate |
MV_PAYMENT_REFERER | referring URL | |
| currency | three-letter currency code according to ISO 4217 |
MV_PAYMENT_TEST | test mode |
An example configuration looks like:
Variable MV_PAYMENT_MODE signio Variable MV_PAYMENT_PARTNER verisign Variable MV_PAYMENT_ID nevairbe Variable MV_PAYMENT_SECRET foobar
With the Route
directive it is possible to specify payment gateways
for special purposes. The payment route should set all relevant payment
parameters for the gateway, otherwise the settings from the MV_PAYMENT_* may
leak into the route.
Route signio partner verisign Route signio id nevairbe Route signio secret foobar
The following payment gateway transactions are known by Interchange:
Transaction | Description |
---|---|
avs | Address verification (AVS) |
auth | Payment authorization (Charge) |
return | Credit |
reverse | Reverse former transaction |
sale | Charge and capture |
settle | Capture of an authorized charge |
void | Cancel or refund payment |
abort | Abort pending capture. |
Modules for the following payment gateways are included in the Interchange source code:
Module | Name | Mode | Description |
---|---|---|---|
AuthorizeNet | AuthorizeNet | authorizenet | |
BoA | Bank of America | boa | |
BusinessOnlinePayment | Business::OnlinePayment | onlinepayment | wrapper for Business::OnlinePayment |
Cybercash | Cybercash | cybercash | |
ECHO | Electronic Clearing House, Inc. | echo | |
EFSNet | Concord EFSNet | echo | |
Ezic | EziC | ezic | |
Getitcard | Getitcard | getitcard | Prepaid cards from Getitcard. |
ICS | Cybersource ICS | ICS | |
iTransact | iTransact | itransact | |
Linkpoint | LinkPoint | linkpoint | |
MCVE | Mainstreet Credit Verification Engine | mcve | |
NetBilling | NetBilling | netbilling | |
PRI | Payment Resources International | PRI | |
PSiGate | PSiGate | psigate | |
Sage | Sage Payment | sage | |
Signio | Payflow Pro | signio | |
Skipjack | Skipjack | skipjack | |
TCLink | TrustCommerce | trustcommerce | |
TestPayment | Payment Test | testpayment | for test purposes |
The AuthorizeNet module implements the ADC Direct Response method for AuthorizeNet version 3.
OpenECHO module from http://www.openecho.com/.
The Getitcard payment module is used for purchases with prepaid cards issued by Getitcard® (http://www.getitcard.com/).
Required parameters are id
and keyfile
.
The domain name of the LinkPoint Secure Payment Gateway (LSPG). Default is
secure.linkpt.net
for production and
staging.linkpt.net
for testing.
File name of the merchant security certificate. This file should contain the RSA private key and the certificate, otherwise you get an error like "Unable to open/parse client certificate file."
The Netbilling module implements the Netbilling Direct Mode 2.1.
This is your account and sitetag separated by a colon (ACCOUNT:SITETAG). ACCOUNT is the number of your Netbilling merchant or agent account, as a 12-character string. Required for ALL transactions. SITETAG is the site tag of your website configured in the Netbilling system. Required for membership transactions, optional for others.
Net::TCLink module from http://www.trustcommerce.com/tclink.html or CPAN.
This module can be used for test purposes.
Hello, World! resource at Wikipedia.
See ISBN Wikipedia entry.
Interchange functions are accessed via the Interchange Tag Language (ITL). The pages in a catalog may be mostly HTML, but they will use ITL tags to provide dynamic content and access Interchange functions in general. ITL is a superset of MML, or Minivend Markup Language.
ITL tags perform all kinds of operations.
There's more than 200 standard predefined tags, and the
UserTag
facility allows you to create your own custom tags,
indistinguishable from the ones "built-in". To get started
with custom tags, see usertag glossary entry.
ITL tags are similar to HTML, in that they accept attributes and that there are both standalone (non-container) and container tags.
A standalone tag has no ending element, such as value
:
[value name]
The above example will insert user's name.
A container tag has both beginning and ending elements, such as tag if
:
[if value name] You have provided us with your name. It is [value name]. [/if]
In the above example, you see that container tags are in general useful only when content is provided in their body (the place between the opening and corresponding ending tag).
There must be no whitespace
between the left bracket ([) and the tag name; [forum]
is
valid, [ forum]
is not!
We've covered the most basic syntax above. If you need to pass attributes to the tag, you do that in the opening section. If the tag is a container, then body text can additionally be specified between the opening and closing marker:
[tagname
parameters_or_attributes
]Body Text
[/tagname
]
Note that some Interchange macros (drop-in text replacements)
may look like tags or end tags. For example, [/page]
used to
be a macro that expanded to </a>
, but page
itself was not a container tag and [/page]
wasn't its
closing element. Likewise, some tags perform special subtags processing
on their own, and the subtags are not considered real tags —
they only exist in the context of the surrounding tag and usually support
only limited options. One such example is tag row
with its subtag
.
It is important to mention that
if a tag or argument name
includes an underscore or dash (such as in item-list
), then you can
always choose between the dash and
underscore! The two forms are interchangeable, except that you
must be consistent with tag markers; an ending tag must match the opening tag.
Both [item-list]...[/item-list]
or
[item_list]...[/item_list]
are fine, but
[item-list]...[/item_list]
is
not.
This is a very convenient feature. It is a common standard to use dashes
(-
) in ITL, and underscores (_
)
in Perl code (because in Perl, dashes would be interpreted as minus operators,
which is not what you'd expect).
There are two styles of supplying attributes to a tag: named and positional.
In the named style, you supply attributes using
pairs, just as with HTML:
key
=value
[value name=variable_name
]
The positional-style tag that accomplishes the same thing, but is more compact:
[value variable_name
]
Positional syntax, however, requires support by each tag individually. That
is, set of arguments that can be specified in positional notation (as
well as their position in the set) are fixed and determined in advance. You can
see a list of accepted positional arguments (and their implicit order) in
each tag's SYNOPSIS reference section; try value
for example.
You cannot mix named and positional attributes in the same tag; use either all named, or all positional.
Positional syntax is appropriate for simpler tags and Interchange interprets positional arguments slightly faster, but don't let premature optimization kick in — as Edsger W. Dykstra once said, "Premature optimization is the root of all programming evil".
In most cases, positional attributes (called parameters) work the same as named attributes. However, there are a few situations where you must use named attributes:
When you want to specify a parameter that, positionally, comes after a parameter that you want to omit, e.g. omit the first parameter but specify the second. The parser would have no way of knowing which is which, so you must revert to named syntax. This rarely happens, though, because the first positional parameter is usually required for a given tag anyway, and can't be omitted.
When there is some ambiguity as to which parameter is which, usually due to whitespace. If the argument value contains any whitespace (even if it is enclosed in quotes!), you must use named syntax.
When you need to interpolate the value of an attribute, such
as of default=
in
[value name=time default="[time]"]
.
When you are using looping subtags (Loop tags and subtags are explained below).
ITL also supports a special notation where you use HTML comment markers with ITL inside, and where the comments are removed by Interchange so that the tag results show up in the generated HTML page.
That means [
and tagname
...
]<!--[
are equivalent.
tagname
...
]-->
This allows you to make your raw ITL tags appear as comments in HTML
browsers, and especially in HTML editors which might not like non-HTML markup
at random positions.
You might want to use this if your editor has trouble with
ITL tags when editing Interchange page templates, or alternatively, if
you want to use one .html
file both as an Interchange
page and as a static page delivered directly by your web server where no
Interchange processing is performed.
When you really wish to treat HTML comments as plain comments and not remove them, then you must include a whitespace between the HTML comment delimiters and the ITL square brackets:
<!--[value name]--> becomes Bill <!-- [value name] --> becomes <!-- Bill -->
ITL is interpolated in both cases however. To prevent ITL
interpolation within HTML comments, you
either need to see the <pragma>no_html_comment_embed</pragma> pragma, or enclose
the block in a special ITL comment
tag instead of in
HTML comment:
[comment] [value name] [/comment]
Besides not being interpolated at all, comment
blocks do not
appear in the final HTML sent to the client either, so you can be completely
safe regarding any unintentional code or information leakage.
While <!--[
and [
are completely
interchangeable, the Interchange parser does not replace ]-->
with ]
unless it sees <!--[
at least once somewhere on the page.
This is a small parsing speed optimization and shouldn't cause you any
trouble as you're supposed to be consistent in your syntax anyway.
When ITL tags are expanded, for example when
[value name]
yields the actual user's name, we say the tags
are interpolated.
If you want to use one tag's output as another tag's input, you must use named syntax and you must double-quote the argument. For example, this WILL NOT work:
[page scan se=[scratch variable_name
]]
The above code is in really bad shape. To make it work, we need to do two things: switch to named syntax, and properly quote arguments containing whitespace:
[page href=scan arg="se=[scratch variable_name
]"]
Note that the href argument did not need to be
quoted; arg did, because it
contained a whitespace and it contained a tag intended for interpolation
([scratch variable_name]
).
![]() | Note |
---|---|
As you can implicitly see from the above example, attribute values
are normally interpolated (in other words,
However, when the attributes contain a dot (such as
|
Interchange parse content like pages, components and profiles in a few consecutive passes instead of once from the top to the bottom:
pragmas are set from pragma
in the content.
Variable
s are replaced (__NAME__
, @@NAME@@
,
@_TITLE_@
) with their respective values.
Comments enclosed by comment
are removed.
Tags are expanded.
The question is — exactly when can you omit the quotes around argument values? First answer is trivial; never omit the quotes and you'll never run into trouble.
The other answer says, "omitting quotes earns you a bonus for style and achieves
small parser speed improvement". However, if the value contains a whitespace,
you must quote it.
To quote values, you can use double quotes ("
),
single quotes ('
) and
pipes (|
) interchangeably.
Pipes have the additional functionality of
removing leading and trailing whitespace, but generally you can use
all types in combination to do three levels of quoting with ITL; for
more deeply nested constructs, use direct Perl code.
Additionally, container tags can be closed using "XML-style" syntax. The following two lines are identical in effect:
[forum top="[item-code]" display_page=forum_display ][/forum] [forum top="[item-code]" display_page=forum_display /]
Note, however, that you can use "/]
"
only with tags for which you provide no attributes, or the attributes are named
and quoted.
Positional attributes (parameters)
"absorb" everything (including the "/
") up to the closing
bracket, and therefore using "/]" is not possible.
When using "/]" notation, it is still possible to provide body for the tag
by specifying body=
attribute.
The above covers a good deal of quoting, but it's still not all there is to it. Loop subtags, which we explain some lines below, are an exception to the rule and do not need quotes (even though they sometimes contain whitespace), because they are parsed earlier in a separate pass, before the general parser takes on.
Here's an example with loop subtags that would work properly even
with quotes omitted. (Pay attention
to [item-field url]
which, at first sight, looks like it is
invalid because it contains a space and is not quoted nor named.)
[item-list] [page [item-field url]]detailed info[/page] on [item-description] [/item-list]
You might wonder why unquoted tags are allowed. The answer is performance. If you have large lists of tags you can achieve significant speed-ups by using positional attributes. Parsing and disassembling named attributes takes some more CPU cycles.
Among ITL tags, perl
, calc
, calcn
and mvasp
are used obtain direct access to the Perl language in Interchange pages.
In fact, all the ITL tags are only extensions (and convenience features)
built on top of Interchange's Perl core, so all ITL tags boil down to Perl
code anyway. Using Perl directly could have the benefits of:
Increasing page parsing speed (Perl blocks are evaluated directly)
Making complicated ITL constructs much easier, or even trivial
Enabling direct access to Interchange data structures (and code, and everything)
Unifying coding style
Let's say we wanted to display user's real name in a page. Here are three pieces of code, all achieving this effect, but using different approaches to produce the result:
Displaying user's real name using an ITL layer:
[if value name] Your name is [value name], in case you forgot. [else] I don't know your name. [/else] [/if]
Displaying user's real name by calling ITL tags from Perl:
[calc] my $out; my $name = $Tag->value('name'); if ($name) { $out = "Your name is $name, in case you forgot."; } else { $out = "I don't know your name."; } return $out; [/calc]
Displaying user's real name by accessing Interchange's data structures:
[calc] my $out; my $name = $Values->{name}; if ($name) { $out = "Your name is $name, in case you forgot."; } else { $out = "I don't know your name."; } return $out; [/calc]
ITL also supports a special type of quoting, using backticks
(`
), where the content is evaluated by Perl
and then passed as the usual argument value.
Using this backtick notation, you can conveniently perform quick, in-place Perl evaluation of an argument, or pass complex data structures as attribute values.
Performing quick, in-place argument interpolation:
[cgi name=Magic_Number hide=1 set=`2 + 3 + $CGI->{magic}`] The magic number is: [cgi Magic_Number]
Passing complex data structures as argument values:
[cgi name=Magic_Structure hide=1 set=`{ key1 => 'val1', key2 => 'val2' }`]
As you see, this notation (and implied functionality) is completely valid and possible. It is then just up to the called tag to handle attribute value appropriately.
![]() | Note |
---|---|
It is only possible to use the backticks notation with named parameters! |
Universal attributes apply to all tags, although tags might specify their own defaults for the attribute. The code implementing universal attributes is independent of the routines that implement specific tags.
We will explain interpolate and reparse attributes here. It is important to remember that the behavior of the interpolate attribute (unfortunately) differs, based on whether a tag is stand-alone or a container. In addition, the reparse attribute is only used with container tags.
With standalone tags, the interpolate attribute
specifies whether the output of the tag will be
interpolated.
Output of most of the stand-alone tags is not interpolated by default.
Exceptions to this rule would include, for example, the include
tag.
With container tags, the interpolate attribute
specifies whether the tag body will be
interpolated before being passed to the tag.
The reparse attribute specifies whether
output will be interpolated.
For an interpolation example with container tags, let's assume the user's name is Kilroy:
[log interpolate=1][value name] was here[/log] [log interpolate=0][value name] was here[/log]
The first line would log "Kilroy was here" to
,
while the second line would log pretty useless "[value name] was here".
CATROOT
/etc/log
For an interpolation example with stand-alone tags, consider the following:
[set name=now interpolate=0][time]%A, %B %d, %Y[/time][/set]
We've set the scratch variable new
to not contain the
actual date, but the ITL code to calculate and display the date each time
it's called. Let's assume today is Monday, January 1, 2001, and we have the
following code:
[scratch name=now interpolate=1] [scratch name=now interpolate=0]
The first line would then produce the expected
Monday, January 1, 2001
, while the second would
leave us with unusable [time]%A, %B %d, %Y[/time]
.
The example above serves to show interpolate used with a stand-alone tag. This behavior can only be achieved by using reparse attribute if you're having a container instead of the stand-alone tag.
The reparse attribute is only used with container
tags, and specifies whether the tag output will be
interpolated. This is basically the same as the
interpolate attribute for stand-alone tags, but a
new name had to be invented because interpolate
already performed a different function with container tags, when
this functionality was to be added.
Most container tags will have their output re-parsed for more
Interchange tags by default. If you wish to prevent this behavior, you
must explicitly set the reparse
attribute to a false value. Note, however, that you will
almost always want the default action. Probably the only ITL tag that
doesn't reparse by default is mvasp
.
Here's an example. Again, assuming the user's name is Kilroy,
[perl reparse=1] my $tagname = 'value'; return "[$tagname name] was here\n" [/perl] [perl reparse=0] my $tagname = 'value'; return "[$tagname name] was here\n" [/perl]
expands to
Kilroy was here [value name] was here
In Interchange 5.3.1, the <pragma>no_default_reparse</pragma> pragma was added, which
disables the default reparsing of container tags' output. It is therefore
advisable to always specify the reparse=1
attribute at
places where you want reparsing to really occur.
The third attribute, send , was deprecated long ago
and will only be described for historical reference.
Each tag may accept additional arguments which vary from tag to tag. For each tag individually, you need to consult the appropriate reference page to learn tag-specific parameters and attributes. Note, however, that the tag arguments do not necessarily have to be announced in the tag definition header; sometimes they're used pretty much ad-hoc, but we are making sure to keep the documentation up to date. We kindly ask you to inform us of any errors or omissions in the documentation.
Instead of just plain values, for some tags it would be particularly convenient if they accepted arrays or hashes of argument values, much like you could do if you used Perl directly. Fortunately, ITL supports that too! For an ordinary tag, the syntax is as follows:
attribute
.ARRAY_INDEX
=value
attribute
.HASH_KEY
=value
ARRAY_INDEX
is an integer array index starting
from 0
(this is due to standard programming practice and
Perl behavior). Note that you cannot have both an array and a hash
attribute of the same name (even though that would be possible in pure
Perl).
Here is an example of an array-based attribute:
search.0="se=hammer fi=products sf=description" search.1="se=plutonium fi=products sf=comment"
It is up to each individual tag to handle an
array or hash value properly.
See the documentation for the specific tag before passing
it an attribute array or hash value.
The page
tag, for example, would treat a search specification array
(the structure we've shown above) as a joined search (one of different
search types we support).
On the Perl level, before passing attributes to a tag, Interchange parser would convert attributes to an anonymous array reference.
If you were passing the above example directly to a tag named
ROUTINE
within a perl
block or your custom tag, you would actually pass an
anonymous array as the value of the attribute:
my $arrayref = [ "se=hammer/fi=products/sf=description", "se=plutonium/fi=products/sf=description", ]; $Tag->ROUTINE( { search => $arrayref } );
Similarly, to use a hash reference in some other occasion:
my $hashref = { name => "required", date => 'default="%B %d, %Y"', }; $Tag->ROUTINE( { entry => $hashref } );
Pay attention to the fact that with array- or hash-based attributes,
their values are not interpolated.
It means that, at places where scalar options
are interpolated (such as
[tag option='[time]']
, resulting in time
expanding to
the current time), reference-based attributes are not (such as
[tag my.option='[time]']
, which would result in literal
"[time]
" being passed to the attribute "my.option".
Interpolation of reference-based attributes can be enabled by
setting the <pragma>interpolate_itl_references</pragma> pragma.
Certain tags can only appear in a special context, usually nested within
other, parent tags. Those include, for example, the ones interpreted as part
of a surrounding loop tags, such as
loop
, item-list
, query
or search-region
.
Some of those subtags are
PREFIX
-accessories,
PREFIX
-alternate,
PREFIX
-calc,
PREFIX
-change,
PREFIX
-code,
PREFIX
-data,
PREFIX
-description,
PREFIX
-discount,
PREFIX
-discount-subtotal,
PREFIX
-field,
PREFIX
-increment,
PREFIX
-last,
PREFIX
-line,
PREFIX
-modifier,
PREFIX
-next,
PREFIX
-param,
PREFIX
-pos,
PREFIX
-price,
PREFIX
-quantity,
PREFIX
-subtotal,
if-PREFIX
-data,
if-PREFIX
-field,
if-PREFIX
-param,
if-PREFIX
-pos,
PREFIX
-modifier-name and
PREFIX
-quantity-name.
In each of the above, PREFIX
represents an
arbitrary prefix that is used in that looping tag; this is needed to be able
to support nesting in arbitrary order and to arbitrary level.
All of the
subtags are only interpreted within their container tags (they can only
appear inside their parent tags' bodies) and they only accept
positional parameters. The default prefixes follow:
Parent tag | Default prefix | Example sub-tag |
---|---|---|
loop
| loop
|
[loop-code] [loop-field price] [loop-increment] |
item-list
| item
|
[item-code] [item-field price] [item-increment] |
search-region
| item
|
[item-code] [item-field price] [item-increment] |
query
| sql
|
[sql-code] [sql-field price] [sql-increment] |
tree
| item
|
[item-code] [item-field price] [item-increment] |
Sub-tag behavior is consistent among the looping tags.
![]() | Important |
---|---|
As we've said already, subtags are parsed before the standard parsing routine takes on; that means before any regular tags within the loop. This has a subtle effect; it can, for example, prevent the usual tags from accepting looping tags' values as attributes. In such cases, look for "subtag" (prefixed) variants of the usual tags. |
Speaking of loops and Perl, there are two types of records that Interchange can return with looping lists: ARRAY and HASH.
An array list is the normal output of the query
and loop
tags,
or of a search operation. In those cases, fields specified in
mv_return_fields
(or those specified in SQL) are returned for
each selected row.
The two queries below are essentially identical:
[query sql="select foo, bar from products" /] [loop search=" ra=yes fi=products rf=foo,bar "]
Both will return an array of arrays consisting of the
foo and
bar columns.
The corresponding Perl data structure would look like this
(familiarity with the output from Data::Dumper
Perl
module helps here):
[ ['foo0', 'bar0'], ['foo1', 'bar1'], ['foo2', 'bar2'], ['fooN', 'barN'], ]
A hash list is the normal output of the item-list
tag. It returns
the value of all return fields in an array of hashes. A normal return
might look like this:
[ { code => '99-102', quantity => 1, size => 'XL', color => 'blue', mv_ib => 'products', }, { code => '00-341', quantity => 2, size => undef, color => undef, mv_ib => 'products', }, ]
However, you can explicitly request hash lists to be returned from queries:
[query sql="select foo, bar from products" type=hashref /]
The data structure returned would then be:
[ { foo => 'foo0', bar => 'bar0' }, { foo => 'foo1', bar => 'bar1' }, { foo => 'foo2', bar => 'bar2' }, { foo => 'fooN', bar => 'barN' }, ]
low, high, rpc, prefork
Standard PreFork
uses the typical double-fork method in UNIX for
generating daemons so as to avoid zombies (or more specifically needing
to deal with them). However, forking Interchange daemons is relatively
expensive.
For most (if not all) sane, modern Unixen, you can avoid the relative expense of double forking by setting SIGCHLD to IGNORE in the master, which accomplishes roughly the same goal. It states, essentially, that the parent is not interested in the result of the child's process, so don't zombify my kids waiting for me to check on them.
Internationalization (or "I18N") features are those being in relation to program's ability to support localized messages (see locale), currencies, and other region-specific settings.
Interchange has a rich set of I18N features that allow conditional message display,
differing price formats, different currency definitions, price factoring,
sorting, and other settings. Currently, those features are implemented as
Interchange-specific, and do not rely on Perl's I18N/L10N features, except for
the built-in POSIX support (use locale
and setlocale()
).
The definitions are maintained in the catalog.cfg
file, through the use of
Interchange's Locale
directive. All settings are
independent for each catalog and each user
visiting that catalog; in other words, customers can access the same catalog
in an unlimited number of languages, currencies and regional settings.
See locale glossary entry for more specific information.
Interchange batch jobs can be triggered from the commandline or an Unix cronjob.
Dispatch a job from the commandline:
interchange --runjobs=training=db
Jobs are run asynchronously. The command will return before the job is completed or even started.
Every job is tied to a directory containing job files. The selection
of the files is controlled by the HTMLsuffix
and the
suffix
Jobs
attribute. By default all files not
matching HTMLsuffix
are included.
The following configuration example directs Interchange to execute
all files matching
jobs/
NAME
/*.job
for the job named NAME
.
Jobs base_directory jobs Jobs suffix .job
Interchange gathers the output of all job files and passes through
the filter(s) specified with filter
key of the Jobs
directive.
The remaining output is optionally written to the job logfile and/or
emailed as specified by the log
and email
keys of the Jobs
directive.
Jobs email racke@linuxia.de Jobs log logs/job
Tracking jobs into a database table can be enabled with the
trackdb
key of the Jobs
directive.
Jobs trackdb jobs
Required fields for this table are code
,
name
, pid
,
begin_run
, end_run
.
![]() | Note |
---|---|
|
Levies in Interchange are a way of defining multiple additional product
charging such as taxes, handling, fees, and shipping.
If you use Levies
, the normal
salestax, shipping, and handling tags are ignored
(even when you use assign
).
The Levy
configuration directive defines individual levy
sections (levy keys). The Levies
configuration directive
defines which levy sections will actually be honored.
Levy sections (key names) are unlimited and arbitrary; we use
salestax
, shipping
etc. to
cover most common cases, but you can put anything in there and have
it work as long as the key names in Levies
have a corresponding
Levy
definition.
Levies are a bit processor-intensive, and as such are not recalculated
every time they are accessed. They can, however, be manually recalculated
by using the check_status
Levy
parameter,
as described in the Levy
reference page.
First of all, Interchange requires a web server that is already installed on a system. Since Interchange is always running in the background as a daemon — parallel to your web server — there must be some means of communication established between the two.
For that reason, at startup, Interchange initializes the UNIX or INET domain socket (or both), depending on specific configuration. Few variants of a small CGI "link program" ship with Interchange and are intended to connect to one of those sockets. Then, they provide the data link to the web server (and consequently, to the users' browser).
![]() | Note |
---|---|
Since Apache is the most popular web server, further instructions will focus on Apache setup. If using another type of web server, some translation of terms may be necessary. |
A ScriptAlias
or other CGI execution capability is
needed on the web server side, to allow use of the link program. The default
ScriptAlias
for many web servers is just /cgi-bin/
. Otherwise if the ExecCGI
Apache directive is set, then any program ending in a particular file suffix
(usually .cgi
) can be invoked as a CGI program.
By convention, Interchange names link programs after catalog IDs (catalog
names as defined in interchange.cfg
), though this is not required.
Link program name is used to derive SCRIPT_PATH
, which is then
used to determine which Interchange catalog is referenced.
UNIX domain sockets are not reachable from the Internet directly; they can only be accessed from the local host. This improves security and is the right thing to do when the web and Interchange server are running on the same host.
The link program vlink
is the provided facility for such
communication with Interchange. As said, this is the most secure way to run a catalog,
as there is no way for systems on the Internet to interact with Interchange
except through the offered link program.
The most important issue with UNIX-domain sockets and Interchange are file permissions
(Unix sockets appear as files on the local disk). Interchange normally runs
with the socket file having permissions 0600
(which
translates to rw-------
), which mandates that the CGI
program and the Interchange server run as the same user ID. This means that the
vlink
program must be SUID to the same user ID as the Interchange
server executes under. (Or that CGIWRAP is used on a single catalog system,
but CGIWRAP is largely obsolete these days).
Even though this is against good security practice, let's say you can put
SocketPerms 0666
in your interchange.cfg
(and restart Interchange) to
allow wide-open access to the socket file. In fact, you should do this when
you have connection problems to quickly rule out whether it's just permissions
or something else.
INET sockets are reachable from the Internet directly. The link program
tlink
is the provided facility for such communication with
Interchange. Other browsers can talk to the socket directly if mapped to a
catalog with the global TcpMap
directive. To improve security,
Interchange usually checks that requests come from one of a limited number of
"trusted" systems defined with the global TcpHost
directive.
vlink
and tlink
, compiled
from vlink.c
and tlink.c
source files,
are very small, fast and low-overhead C programs that contact the running
Interchange daemon. The vlink
executable is normally made
setuid (SUID) to the user account which runs Interchange, so that the UNIX-domain
socket file can be set to secure permissions (user read-write only, as mentioned
above). It is normally not necessary for the user to do anything — the
link programs will be compiled by the configuration program. If the Interchange
daemon is not running, either of the programs will display a message indicating
that the server is not available.
Link program source files, found in dist/src/
directory with Interchange source tree, share the common C include file
config.h
which sets required variables:
LINK_FILE
-
name of the socket file (so, UNIX domain socket) that will be opened,
usually /usr/local/lib/interchange/etc/socket
(or
anyhow, etc/socket
under your ICROOT directory).
LINK_HOST
-
IP number of the host (so, INET domain socket) which should be contacted.
The default of 127.0.0.1
(the local machine) is probably
best for many installations.
LINK_PORT
-
TCP port number (so, INET domain socket) to connect to. The default is
7786
(the decimal ASCII codes for 'M' and 'V') and does
not normally need to be changed.
LINK_TIMEOUT
-
number of seconds vlink
or tlink
should
wait before announcing that the Interchange server is not running. The default
of 45
is probably a reasonable value.
There is a compile_link program found within Interchange
source tree that will assist you with link program compilation.
Run perldoc PATH/TO
/compile_link
to read its documentation. (To find where the compile_link
or compile_link.PL commands are, use
locate compile_link or find / -name 'compile_link*').
Anyhow, you can also compile the link programs more "manually" if you position yourself to the Interchange source tree:
$ cd dist/src $ ./configure $ # (open and inspect config.h) $ mkdir ../bin $ perl compile.pl
After which the compiled vlink
and tlink
programs will be in your dist/bin/
directory.
For yet another iteration, if you want the control over the whole process, instead of perl compile.pl invoke simply:
$ gcc vlink.c -o ../bin/vlink $ gcc tlink.c -o ../bin/tlink
The problem with the above approach, however, is that you might be missing critical compiler options, or that the compiler isn't gcc at all. To ensure that the C compiler will be invoked properly, use the following little ditty instead of the two gcc lines:
perl -e 'do "syscfg"; system("$CC $LIBS $CFLAGS $DEFS vlink.c -o ../bin/vlink");' perl -e 'do "syscfg"; system("$CC $LIBS $CFLAGS $DEFS tlink.c -o ../bin/tlink");'
And finally, you can make the binaries smaller by going to
../bin/
and typing
strip *, if available. (strip removes
all debugging symbols from the binaries).
Have in mind that link programs are the same for all catalogs running on the same server — it's only their name that makes them refer to different catalogs. In other words, once you get one link program compiled, you can simply keep copying it to new names as you create new catalogs.
First of all, if Interchange is to run under a different user account than
the individual configuring the program, change vlink
ownership to the Interchange user. Do not make it owned by root, because making
vlink SETUID root is an huge and unnecessary security risk.
It should also not normally run as the default Web user
(often nobody
or http>
).
Here's a prospective session transcript (starting in
dist/bin/
):
# Set up ownership and suid bit chownINTERCHANGE_USER
vlink # Move the vlink executable to the cgi-bin directory: mv vlink /usr/lib/cgi-bin/CATALOG_NAME
# Most systems unset the SUID bit when moving the file, # that's why we add suid here and not above: chmod u+s /usr/lib/cg-bin/CATALOG_NAME
The latest versions of the link programs have been compiled on the following systems:
AIX 4.1
BSD2.0 (Pentium/x86)
Debian GNU/Linux
Digital Unix (OSF/Alpha)
FreeBSD 2.x, 3.x, 4.x
IRIX 5.3, IRIX 6.1, IRIX 6.5 (both SGI MIPSPro and GCC; remove -lnsl -lsocket
from syscfg
file on IRIX 6.x)
OpenBSD 2.7
Red Hat Linux 6.2, 7.0, 7.1, 7.2, 7.3, 8.0
SCO OpenServer 5.x
Solaris 2.x (both Sun compiler and GCC)
Solaris 7 (both Sun compiler and GCC)
SunOS 4.1.4
If nothing works and you have a problem, you can try using the
tlink.pl
program found in the same location
as link
and tlink
.
tlink.pl
is a tlink
implementation
written in Perl and should work on a really broad range of systems.
In any case, unless you are using tlink.pl
for a
specific reason, and you do have a working compiler, tuning the C source
files to overcome compilation problems is preferred over using
Perl implementation of the link program.
See internationalization (I18N) glossary entry for a general introduction.
Localization (or "I10N") is a process of making an I18N-enabled application to use specific locale definition (messages, currency format, etc.).
The important thing to have in mind is that Interchange allows customers to access the same catalog in an unlimited number of languages, currencies and regional settings. What's more, you can switch locales at will, at any time!
The simplest and recommended way to set the default
catalog locale is to define a starting value for mv_locale
in
catalog.cfg
:
ScratchDefault mv_locale de_DE
The per-user locale change can be made permanent
(for the duration of the user
session, of course), or temporary (if you're, say, displaying pricing
information in multiple currencies). See setlocale
for more discussion
and examples.
Besides using actual "programmatic" methods to set locales, you can achieve
the same effect by using one terrific feature of the process
ActionMap; to display page named "pricelist
" in
locale fr_FR
and currency en_US
, simply
call ITL tag
[page process/locale/fr_FR/currency/en_US/page/pricelist]
.
The localized messages you want to display must previously be defined, of
course.
The simplest way to define localized messages is to use
the Locale
directive in any of the ways shown:
Locale de_DE "Value setting" Werteinstellung Locale de_DE "Search" Suchen Locale fr_FR <<EOF { "Value setting", "Configuration de valeur", "Search", "Recherche" } EOF
This method, however, is not practical when you have a lot of messages;
for robust setups use LocaleDatabase
.
With the above, to display an appropriate translation of "Value setting",
you would call "[L]Value setting[/L]
", which would display as
"Configuration de valeur
" in
locale fr_FR
,
"Werteinstellung
" in locale de_DE
, and as
a fallback, it would be displayed unmodified as "Value setting" — if the
locale is neither
fr_FR
not de_DE
, or no localized string
for the message was found at all.
Keep in mind that the "[L]
" tags must be capitalized —
this is done for both processing speed and easy visual distinction within
pages.
![]() | A word on localization tag [L] |
---|---|
Interchange tries to substitute text for its localized variant very early in the page serving process — even before variables are expanded or tags interpolated. On one hand, this means you can use variables and tags in localized strings as they will be handled properly.
On the other hand, it means code constructs like
|
Another way to display localized messages is to supply localization text
directly within Interchange pages, inside LC
tag; have a look at this
intuitive example:
[LC] This is the default text. [fr_FR] Text for the fr_FR locale. [/fr_FR] [de_DE] Text for the de_DE locale. [/de_DE] [/LC]
It's also possible to display completely different pages, based on the
locale in effect. You probably know the default HTMLsuffix
in
Interchange is ".html
", but you probably do not know it is
locale-sensitive. With a request for page named "index"
([page index]
), fr_FR
locale in effect, and a
catalog.cfg
directive of
Locale fr_FR HTMLsuffix .fr_FR
Interchange would first look for pages/index.fr_FR
, and only if
it's not found go to the usual pages/index.html
.
![]() | A general note on locale-sensitivity of config directives |
---|---|
We've said |
Interchange sorting features (such as — but not only — the
sort
tag)
will honor a setting of LC_COLLATE
, provided that
the operating system and C compiler support locales for POSIX,
have the locale definitions set, and the locale chosen matches one of
locales available.
Suppose we have a letters database, containing some of the letters of the alphabet. A code of:
[loop 19-202 00-0011 99-102] [sort letters:letter] [loop-data letters letter] [loop-code] <br/> [/loop]
would provide different order for locale C
than
fr_FR
.
MIME resource.
Resources at Debian Tutorial - Permissions, Debian Tutorial - Advanced Topics and O'Reilly & Associates' Essential System Administration book.
See attribute.
Name-space is an abstract group of elements. Within the group, element names must be unique, but elements of the same name can appear in different namespaces (so, in different groups). They are completely separate and in no relation to each other.
Generally speaking,
the namespace indicator can be prefixed to the element name (such as
xsl:stylesheet
), given as a separate parameter
(such as name="stylesheet" space="xsl"
), or set
in a separate call and later considered implicit.
Interchange sports a completely flexible item ordering scheme. One practical
implementation can always be seen in the latest demo catalog shipping with Interchange; the latest
demo catalog is called Standard
.
Just as in many other Interchange demo catalogs, order-related files are kept in the
CATROOT/pages/ord/
directory.
The most important file would probably be
CATROOT/pages/ord/basket.html
which shows
order basket contents, and is displayed to the user every time an item is
added to it, or the user explicitly requests it.
![]() | Note |
---|---|
It is not strictly necessary to display the basket page every time an item is ordered, but most customers will be confused if you don't give them a clear visual consequence of their ordering action. An alternative to a separate basket page could be a side-bar menu that would display cart contents in a summarized form, and be visible on all pages. However, it would still be useful to display the complete basket page to the users when they are about to stop ordering and proceed with the check-out routine. |
Besides just listing electronic cart contents, the basket page usually also allows for item removal (or quantity selection in general) and total price recalculation.
In general, the body of the basket page is a HTML form. Strictly speaking, it could just be a normal list of contents (without the <form> element), but then you couldn't request any actions on the items (such as their removal from the cart).
At minimum, the basket page will contain the item-list
tag that will
loop over all items in the cart and display them. Any Interchange tags can be used
on the page, and you can freely have multiple item lists (the basket page
is in no way special and does not place any restrictions
on content).
Interchange supports a number of ways to add an item to the basket. You can order an item by using both form-based (button-clicking) and link-based (link-visiting) methods.
Link-based ordering is implemented through the order
tag. You should
check the order
reference page for complete documentation, but let's
give a summary of its functionality here. In general, ITL code
[order code=SKU
quantity=NUMBER
href=URL
cart=NAME
]Link text</a>
expands into an appropriately-constructed HTML link. Clicking
a link places the item of specified SKU number in the cart and displays
the basket page. The code parameter should be a
valid product SKU (listed in one of the products tables),
and is the only required parameter. href allows
some page other than the default basket to be displayed once the item has
been added to the cart. cart
selects the shopping
cart the item will be placed in (you see, the default cart is called
main
, but Interchange is always a step ahead).
The order
tag supports a number of attributes (more than we've shown
above), but the same effect as above can be achieved with the page
or area
tags as well. Here are a few lines, all equal in effect:
Order a [order TK112]Toaster</a> today! Order a [page order TK112]Toaster</a> today! Order a <a href="[area order TK112]">Toaster</a> today! Order a <a href="[area href=order arg=TK112]">Toaster</a> today! Order a <a href="[area href=order form='mv_order_item=TK112']">Toaster</a> today!
"This is all nice and well", you might say, "but how do I create the link when item data fields (such as SKU, description or price) are not known in advance?". Well, this will be the case if you're creating a flypage or displaying results based on a user search operation. The procedure is, of course, exactly the same; you could just be uncertain about where to stick in the additional ITL tags. Here's a clarifying example:
Order a [order [item-code]][item-field name]</a> today!
Form-based ordering comes handy when you want to implement more complex
ordering schemes. Consider the same ordering example as above (a toaster of
product SKU TK112
) implemented using forms:
<form action="[process href=order]" method="post"> [form-session-id] <input name="mv_order_item" type="hidden" value="TK112" /> <input name="mv_order_quantity" type="text" value="1" size="3" /> toasters <input value="Order!" type="submit" /> </form>
Although the readability above suffers due to HTML elements and quoting,
it's easy to dissect it to components. <form> and <input> are
standard HTML elements. We care to supply type="text"
with text fields for clarity, but that's the default type and can be
safely omitted. What's left to note is that the <input>
element names are of predetermined values, and that's only reasonable —
you need to set those variables that you know Interchange will look for (otherwise
this would be a waste of time). In our situation this includes setting
mv_order_item
(item SKU number),
mv_order_quantity
(item quantity) and
mv_todo
(desired form action).
For a complete list of user mv variables, see
.
You might notice we added the quantity field in the example above. From its implementation, you see that making special mv variables contain user input is extremely easy. What's more, input placeholders do not have to be a text fields as in our example; it can be a list, checkbox or a complex set of HTML widgets!
Interchange supports "stacking" of variables. This allows us to order different items ("batches") at once:
<form action="[process href=order]" method="post"> [form-session-id] <input name="mv_order_item" type="hidden" value="TK112" /> <input name="mv_order_quantity" type="text" value="1" size="3" /> Standard Toaster <br/> <input name="mv_order_item" type="hidden" value="TK200" /> <input name="mv_order_quantity" type="text" value="1" size="3" /> Super Toaster <input value="Order!" type="submit" /> </form>
Items that have a quantity of zero (or blank) will be skipped, and only items with a positive quantity will be placed in the basket.
Note that "on the fly" items are those that do not exist in any of the products databases, and are literally "put together" on the fly. This is unrelated to Interchange flypage functionality.
If you setup the OnFly
config directive, Interchange will be able to add
items to user's basket even if they're not listed in any of the
products databases.
A basic on-fly item can then be generated like this:
<a href="[area form=" mv_order_item=000101 mv_order_quantity=1 mv_order_fly=description=An on-the-fly item|price=100.01 "]">Order item 000101</a>
Or, through a proper Submit button:
<form action="[process href=order]" method="post"> [form-session-id] <input type="hidden" name="mv_order_item" value="000101"> Qty: <input size="2" name="mv_order_quantity" value="1"> <input type="hidden" name="mv_order_fly" value="description=An on-the-fly item|price=100.01"> <input type="submit" value="Order button"> </form>
A lot of things might look weird in the above example, but it is all valid ITL code.
The form parameter mv_order_fly
can contain any number of fields
which define attributes for our on-fly item. Fields are separated by the
pipe (|
) character and contain value-parameter
pairs separated by an equal (=
) sign.
![]() | Note |
---|---|
To show description for the on-fly item, you must use the
Similarly, an attempt to generate a flypage for an on-fly item will fail,
resulting in display of the |
Multiple items can be ordered at once by stacking the variables, as we've
shown already. However, if there is only one mv_order_item
instance
defined, then you can stack mv_order_fly
variables which will result
in their concatenation just as if you separated them with pipes. Here's
an example, equal in effect to the previous one:
<a href="[area form=" mv_todo=refresh mv_order_item=000101 mv_order_fly=description=An on-the-fly item mv_order_fly=price=100.00 "]">Order item 000101</a>
We've seen variable stacking at work above, but there's more to it.
Imagine you had a master item and accompanying accessories which only make
sense together with the main item. You could order them at once using
ordinary stacking - no problem there, but what if you also wanted
accessories or options removed at the same time the master item is
removed from the cart?
In the simplest form, this is achieved by ordering items as usual, while
defining mv_order_group
. The first item in the list is then
treated as the master item, and all other are sub-items.
<form action="[process href=order]" method="post"> [form-session-id] <input name="mv_order_group" type="hidden" value="1" /> <input name="mv_order_item" type="hidden" value="00-0011" /> <input name="mv_order_item" type="hidden" value="00-0011a" /> <input value="Order Mona Lisa with frame" type="submit" /> </form>
Incredible, but you can also define multiple master items! All you need to
do is define a mv_order_group
for each individual item.
Master item "owns" all subsequent sub-items until the next master
is defined.
<form action="[process href=order]" method="post"> [form-session-id] <input name="mv_order_group" type="hidden" value="1" /> <input name="mv_order_item" type="hidden" value="00-0011" /> <input name="mv_order_group" type="hidden" value="0" /> <input name="mv_order_item" type="hidden" value="00-0011a" /> <input name="mv_order_group" type="hidden" value="1" /> <input name="mv_order_item" type="hidden" value="19-202" /> <input name="mv_order_group" type="hidden" value="0" /> <input name="mv_order_item" type="hidden" value="99-102" /> <input value="Order Mona Lisa and more!" type="submit" /> </form>
The example shows a 4-item order form. Items 1 and 3 are master items,
items 2 and 4 are sub-items.
If a master item of SKU 00-0011
is deleted from the
basket, 00-0011a
will be deleted too. If
19-202
is deleted, then 99-102
will be
deleted along.
![]() | Note |
---|---|
Use of HTML checkboxes for this type of thing could be inappropriate
because they do not pass a value when unchecked. The value then remains
defaulting to last used value. It is preferable to use radio groups or
select/drop-down widgets instead. If you use checkboxes, make sure to
explicitly clear [value name=mv_order_group set=''] [value name=mv_order_item set='']
|
mv_mi
and mv_si
attributes are set to the group and
sub-item status of each item. The group, contained in the attribute
mv_mi
, is a meaningless yet unique integer. All items
in a group will have the same mv_mi
value. The attribute
mv_si
is 0
if the item is
a master item, and 1
if it is a sub-item.
Basket pages list electronic cart contents and usually allow order contents to be adjusted; items can be removed, ordered in different quantities etc.
It is possible to have an multiple (different) basket pages, because they're in no way special. It is also possible to have multiple shopping carts, for example in cases where the same user maintains a buy and sell list. Multiple basket pages could also be used where items have many accessories and, depending on the item ordered, you send customers to different, item-specific pages to choose accessories. The possibilities are endless, it's just about evaluating your actual needs.
The name of the basket page to be displayed upon item order can be defined in a few ways:
You can set order
value of the SpecialPage
directive to the page you want displayed on item order
You could use the order
tag in form of
[order code=
to define the basket page
SKU
page=NAME
]Order it!</a>
If you already are on the order page and want to
"jump" next, use the
standard form, setting some of mv_orderpage
, mv_nextpage
,
mv_successpage
and mv_failpage
variables.
Interchange allows you to define and maintain multiple shopping carts
for each user.
The default shopping cart named main
is created when
the user session starts. If the user orders an item of SKU
M1212
through the following link:
[order code=M1212 cart=layaway]Order this item!</a>
then the item will be placed in the cart named layaway
, and
not main
. Consequently, the use won't see the just-ordered
item on the basket page because the default shopping basket displays contents
of the main
cart only. The easiest way to support multiple
carts is to copy the default basket page
(
)
to a new file, insert CATROOT
/pages/ord/basket.html[cart layaway]
code snippet at the top,
and specify it as the target page for the order
tag:
[order code=M1212 cart=layaway page=ord/layaway_basket]Order this item!</a>
Now the contents of the layaway
cart would be displayed
on the appropriate page. Most of the fundamental cart management ITL
tags support the nickname option, allowing
cart name to be chosen. Using nickname is handy,
and has no permanent effect, the default cart remains set at value of
main
.
A sticky attribute could be set to change the
default cart name until further notice. This is convenient,
but you must remember to use [cart main]
to get back to
the primary cart!
And for the sake of completeness, here's the above example using form-based ordering:
<form action="[process href=order]" method="post"> [form-session-id] <input name="mv_order_item" type="checkbox" value="M1212" /> Item M1212 <input name="mv_order_quantity" type="text" value="1" /> Quantity <input name="mv_cartname" type="hidden" value="layaway" /> <input value="order this item!" type="submit"> </form>
Pragmas are used to control various aspects of page and data parsing and display. They are processed before an Interchange page goes to the normal processing routine.
Pragma values can be defined at any level; catalog-wide, page-wide or ITL-block wide.
![]() | Note |
---|---|
Catalog-wide pragmas are equivalent to inserting |
To define a pragma catalog-wide, use the Pragma
directive in catalog.cfg
:
PragmaNAME
[=value
]
To define a pragma for a particular page, use the pragma
tag anywhere on a page. For readability, however, we suggest to always
define pragmas at the top.
The following two lines, of which you would
usually use only one at a time, present pragma activation and deactivation:
[pragmaNAME
] [pragmaNAME
0]
To define a pragma for an ITL block inside the page, enclose the
block in [tag pragma ...]
:
[tag pragmaNAME
]1[/tag] ... [tag pragmaNAME
]0[/tag]
Starting with Interchange 5.0, the $::Pragma->{
syntax
is used in the Interchange source, instead of the old
NAME
}$Vend::Cfg->{Pragma}{
.
NAME
}
See the list of available pragmas in Interchange Reference Pages: Pragmas.
Base price of each item is kept in the products
database. In fact, the three required fields for all items are
code,
price and
description.
Note that the default names are well suited for about all purposes,
but you can change the actual names of the price and
description columns using PriceField
and DescriptionField
directives.
The simplest method, as we've just hinted above, is flat pricing based on a fixed value in the products database. All you have to do is put item price in the price field and you're all set. If you want to change pricing based on quantity, size, color or arbitrary other factors, then read on.
Although the CommonAdjust
directive provides a lot of options to
describe pricing schemes, it might not match yours or you are just feeling
more comfortable in writing code instead of delving through the following
section. In this case you can simply write your own usertag calculating the
price with the following settings for CommonAdjust
and PriceField
:
PriceField 0 CommonAdjust [calc-price]
The following example can be used to base your own usertag on. Product code and
quantity for the price calculation can be found in the
$item
variable from the
Vend::Interpolate
namespace.
UserTag calc_price Routine <<EOR sub { my ($code, $quantity, $set); $code = $item->{code}; $quantity = $item->{quantity}; $Tag->perl({tables => 'vendors_pricing'}); $set = $Db{vendors_pricing}->query(q{select price from vendors_pricing where sku = '%s' order by price asc limit 1}, $code); if (@$set) { return $set->[0]->[0]; } else { return 0; } } EOR
A flexible, chained pricing scheme is available when CommonAdjust
directive is set up.
CommonAdjust string, a term we'll use in this section, will be explained below.
Here are CommonAdjust
usage rules, all assuming PriceField
having the default value of price
.
If CommonAdjust
is set to any value (so, be it a
valid CommonAdjust string or not), extended price
adjustments are enabled
If the price field in the
products database contains a
CommonAdjust string (or a non-empty, non-zero value), it takes
precedence over the default set with CommonAdjust
If the value of the CommonAdjust
directive is set to a valid CommonAdjust string,
and the price field is empty or
specifically 0
, then the CommonAdjust string
will be used to set the price of the item. An effective way to make
the price field empty is to use
the PriceField
directive to set field name to an invalid value, such as
0
, none
or noprice
.
If no CommonAdjust strings are found, then the price will be 0
, and
subject to any later application of discounts
If, as a result of CommonAdjust string application, another CommonAdjust string is found, it will be re-parsed and the result applied. Chaining is retained; a fallback may be passed and will take effect
Using valid CommonAdjust strings, prices may be adjusted in several ways, but we first need to define "CommonAdjust string" as we've promised in the introduction. CommonAdjust strings are price definition strings which consist of individual actions called atoms. Atoms in turn consist of settors. Roughly, we could say atoms convey "control information" — they define when are contained settors applied, and settors define what is applied.
Price atoms can be of type final, chained and fallback.
Final price atom is applied if it does not evaluate to zero
Chained price atom is subject to further adjustment
Fallback price atom is skipped if a previous chained price was not zero
Atom types are recognizable from the syntax used to define them.
Chained atom ends with a comma
Fallback atom has a leading semi-colon
Final atoms have no comma appended or semi-colon prepended
Atoms themselves are separated by whitespace and must be quoted if they themselves contain whitespace.
A settor is a mean by which the price is set. In other
words, it is an expression contained in an atom which, when evaluated,
affects the item price.
Just as there are different types of atoms, there are different types of settors
as well. Be aware that all settor types can yield new CommonAdjust strings, and it's
quite possible to create endless loops this way. Therefore, the maximum
number of initial CommonAdjust strings is 16
,
and there may be a maximum of 32
repeated CommonAdjust string iterations
or loops before the price will return zero on an error. Those limits are only
defaults and can be adjusted using general-purpose Limit
config
directive.
![]() | Note |
---|---|
Common needs are easily shown but not so easily explained. Follow our pointers to examples if your vision starts to blur as you dive into the following section. |
Let's take a look at available settor types. Settor types do not have distinguishable symbolic names, but their type is recognizable from the syntax used. Note that bold elements are required, while the rest are optional.
-
decimal
A number which is applied to the current
price value. For instance,
a price of 10
and settor of 2
result
in new current price of 12
.
May be a positive or negative decimal.
-
decimal
%
A number which is applied as a percentage of the current
price value. May be a positive or negative number. For instance,
a price of 10.00
and settor of -8%
result in new current price of 9.20
.
table
:
column
:
key
Causes a direct lookup in a database table. Table defaults to the
products database entry for the item (subject,
of course, to multiple products databases). Column
argument must always be present, but you'd usually set it to
price
. (That scheme is often used in combination with
setting PriceField
to something different of
price, because the
price field would take precedence
over the CommonAdjust string).
The key defaults to the item SKU (except in a special
case of attribute-based lookup).
table
:
col1..col5
,
col10
:
key
Causes a quantity lookup in database table which, again, defaults to products databases. Column specification is required and given as a set set of comma-separated fields (with ranges supported), looked up by the optional key. Key defaults to the item code as usual.
Pay attention to the convenient range support; the following two settors are identical:
pricing:p1,p2,p3,p4,p5,p10: pricing:p1..p5,p10:
With this quantity-based lookup, item quantity is compared to the numerical portion of the column name (leading non-digits are stripped). The price is then set to a value from the appropriate database column. The "appropriateness" is determined logically — since the numeric portion of the column name determines minimum quantity for the class, Interchange picks the "highest" class for which minimum quantity requirement is fullfiled (so that the item price is set according to quantity range).
![]() | Warning |
---|---|
If the column value at the appropriate quantity level is blank,
a zero cost will be returned from the atom.
It is important to have all columns populated, because |
One excellent extra effect you can achive with this is "mix-and-match". It allows you to order different items which you put into the same "product group" to have their quantities added together to determine the appropriate price range.
==
attribute
:
table
:
column
:
key
Performs attribute-based lookup. Table specifies the table for lookup. Column defaults to the name of the attribute being looked for.
& PERL_CODE
The leading &
character is stripped and the PERL_CODE
block is passed to the equivalent of a calc
tag. No Interchange tags
can be used in there, but the tag_data()
function
(data
tag, basically)
is available, the current price is
available in variable $s
, and
the current item details (code, quantity, price, and any attributes) are
available inside the $item
hash reference.
All are found in Perl package Vend::Interpolate
.
That said,
$Vend::Interpolate::item is the current item hash reference $Vend::Interpolate::item->{code} gives key for current item $Vend::Interpolate::item->{mv_ib} gives database ordered from $Vend::Interpolate::item->{...} gives specified attribute's value
[
ITL_CODE
]
or __
VARIABLE
__
Causes ITL_CODE
to be parsed for Interchange tags with
variable substitution, but
without locale substitution. You may also define a price in a
Variable
.
$
Causes Interchange to look in the mv_price
attribute of the
shopping cart, and apply that price as the final price, if it exists.
The attribute must be of numerical value.
>>
word
Causes Interchange to literally return
as price. This is not
useful in pricing, as it will evaluate to zero. But when word
CommonAdjust
is also used for shipping, it is a way of re-directing shipping modes.
word
The value
(which must
be unique — not match any other settors) is available as a key only for
the next lookup.
If that next settor is a database lookup and it contains
a dollar sign (word
$
),
will be substituted
for it.
word
(
settor
)
The value returned by settor
will be available
as key for the next lookup.
If that next settor is a database lookup and it contains
a dollar sign ($
),
will be substituted
for it.
word
For actual examples, please see the CommonAdjust
reference page.
For product discount settings, see the discount glossary entry.
There are different types of profiles in Interchange:
Profile
)UserDB
)
Interchange form profiles are used to validate form inputs and eventually trigger additional actions. The validation usually consists in requiring that some of the fields are non-empty or match a specific regular expression pattern.
Profiles are not specific to order checkout or any specific step; they are an integral part of all form processing in Interchange. You will also see actions such as Interchange account creation, login, logout or password change being at least partly handled using profiles.
Profiles can be defined in external files (and activated using
OrderProfile
) or in scratch variables. External files are,
by convention, kept in CATROOT/etc/
and
begin with profiles.
. Multiple profiles
can be defined in each file.
A very simple hello world-like profile example follows:
__NAME__ test_profile fname=required lname=required __END__
The above profile requires customers' first and last names to be entered.
![]() | Note |
---|---|
The |
If users leave some of the required fields empty (or there's some other reason why you'd want them to correct their input), you'll probably want to show them the form page again and display some kind of an error message. To override the default messages, simply specify your own strings after the format check specification. Here's an example:
__NAME__ test_profile fname=required First name is required! lname=required Last name is required! __END__
From the examples above, you can see that the format check specifications are
specified as
. Form variable names are obvious; in our
example they were FORM_VARIABLE_NAME
=CHECK_TYPE
fname
and lname
.
Check types, however, can take on one of the following values:
required
- form field must end up with non-empty content.
If no direct CGI variable exists, variable is searched in the
the values space (form fields submitted at some past time during
the session) and UserDB
mandatory
- form field must contain a non-blank value,
and it must
exist directly on the form being checked. In other words, it must be
a CGI variable and not a value variable coming from some
previous form input or UserDB
phone
- form field must look like a phone number
(by a very loose specification), allowing numbers from all countries
phone_us
- form field must have US phone number
formatting with area code included
state
- form field must contain an US state,
including DC and Puerto Rico
province
- form field must contain a canadian
province or pre-1997 territory
state_province
- form field must contain an US state
or canadian province
zip
- form field must contain
an US postal code formatting, with optional ZIP+4.
This is also called by the alias us_postcode
ca_postcode
- form field must contain a canadian
postal code formatting (check for a valid first letter is performed)
postcode
- form field must contain a valid
US or Canada postal code formatting
true
- form field content must begin with
y
, 1
or t
(case-insensitive)
false
- form field content must begin with
n
, 0
or f
(case-insensitive)
always_pass
- form field always passes.
This can be used to update the field regardless of the value
email
- form field content must pass rudimentary
e-mail address check; it must contain "AT"
(@
), a name, and a minimal domain
regex
-
form field content must match regular
expression patterns. Multiple patterns can be specified, separated by
whitespace
REGEX_PATTERNs
length
- form field content must satisfy the allowed length range
RANGE
match
- form field content must be equal to the content of another field,
CGI_VARNAME
CGI_VARNAME
unique
- form field content must not exist in a given DATABASE
DATABASE
filter
- form field content should be equal to the original value after
being ran through the specified FILTER
FILTER
Checks can be combined with the &and
and
&or
profile operators. The following example
uses the <check>regex</check> and <check>length</check>
checks to ensure that the input is suitable for system usernames
and is within certain length bounds.
username=regex ^[a-z][-a-z0-9]*$ "Invalid characters in username." &and username=length 6-32 Size limits exceeded (6-32 characters)
Most of the time, the changes you make to the HTML files or
databases are directly visible on the next access to your site.
However, that is not the case with more "serious" files such as configuration
files (catalog.cfg
) or profiles (etc/profiles*
).
To apply changes from all those "non-instant" files, you must restart Interchange server or — quicker and more elegant — reconfigure just the specific catalog.
Changes to global configuration file (interchange.cfg
) can only be applied
by restarting Interchange completely.
For practical instructions, see .
In Interchange, the term "Rotated Banner" refers to the process of selecting or displaying one of the banners from the banner field in the banner database, when that field contains multiple banners (separated by appropriate delimiters) and the rotate field contains any positive integer value.
The available banners are displayed in sequential order, with an independent pointer for each client.
See the banner
tag and for further
discussion and examples.
SDBM is an in-file, non-SQL database (similar to GDBM). Interchange will, however, add an SQL layer on top of every supported non-SQL database so SQL calls can be used, as long as they are made through Interchange.
Interchange must work with some kind of a database. When no SQL database is specified, database source files (text) are still stored in a database, even though it's a file-based one (GDBM or similar) and requires no special setup from the user.
For in-file database backend, Interchange will either use GNU DBM or Berkeley DB, depending on what it finds on the system.
Yet another option was SDBM, but it is severely handicapped — it does not support values bigger than 2048 bytes and at least two of our demo catalog databases fail to load for that reason.
Since early 2006, Interchange no longer considers SDBM an option, unless it is explicitly told so. You can explicitly instruct Interchange to use SDBM backend for particular databases, but that is not encouraged and SBDM should not be used.
To enable the Interchange SOAP server, set SOAP
to yes in the
global configuration file. It is also important to allow SOAP actions
with SOAP_Control
both in the global configuration and in
the catalog configuration file in order to provide web services.
SOAP_Control Action always
After restart, the Interchange SOAP server will listen on port 7780 for requests.
For all catalogs providing SOAP services, do the following steps:
Enable SOAP
and allow SOAP actions in the catalog configuration file:
SOAP Yes SOAP_Control Action always
Add SOAP URL to Catalog
directive in the global configuration file:
Catalog wellwell /home/interchange/catalogs/wellwell /cgi-bin/wellwell /soap
Define SOAP actions with SOAP_Action
.
Finally, restart your Interchange server.
If Interchange cannot determine the catalog for the SOAP request,
it will trigger the soap:Client.NotFound
fault.
If the catalog hasn't enabled SOAP
, Interchange will trigger
the soap:Client.NotAvailable
fault.
If the called SOAP action dies, Interchange triggers a
soap:Server
fault with the message
Application error
.
Secure Sockets Layer resource at Wikipedia.
Interchange has several features that enable secure ordering via SSL.
Despite their mystique, SSL web servers are actually
quite easy to operate. The difference between the standard HTTP server
and the SSL HTTPS server, from the standpoint of the user, is only in
the encryption that happens kind-of transparently, and the specification
of the URL -- https:
is used for the
URL protocol specification instead of the usual http:
designation.
![]() | Note |
---|---|
Interchange attempts to perform operations securely,
but no guarantees or warranties of any kind are made! Since Interchange
comes with Perl source, it is possible to modify the program to create
bad security problems. One way to minimize this possibility is to record
digital signatures, using MD5 or PGP, of
|
Interchange uses the SecureURL
directive to set the base URL for secure
transactions, and the VendURL
directive for normal non-secure
transactions.
Secure URLs can be enabled for individual forms through a form action of
[process secure=1]
. An individual page can be displayed
via SSL with [page href=
.
A certain page
can be set to always be secure with the PAGE_URL
secure=1]PAGE_NAME
</a>AlwaysSecure
directive.
Interchange incorporates additional security for credit card numbers. The
field mv_credit_card_number
will not ever be written to disk.
To enable automated encryption of the credit card information, you need
to enable CreditCardAuto
. EncryptProgram
also needs to be set to a command which will, with hope, encrypt the number
when invoked. PGP is now recommended above all other encryption
program. The entries should look something like:
CreditCardAuto Yes
EncryptProgram /usr/bin/pgpe -fat -r sales@company.com
See CreditCardAuto
, PGP
, GPG_PATH
,
EncryptKey
and EncryptProgram
configuration directives
for more information and examples.
In password protection, salt is a random string of data (two characters, in traditional crypt implementations) used to modify a password hash.
Salt is added to make it impossible (or at least more difficult) for an attacker to break into a system by pre-compiling large dictionaries of password strings and then comparing them to the crypted strings from the password files. In effect, adding a 2-character salt to a password hash makes more than 600 possible combinations of the same actual password.
One of the side effects of salt is that it causes an actual password to generate two different encrypted strings (when two different salts are used), so multiple users can accidentally choose the same password without making that fact obvious to any user with read access to the crypted strings.
When Interchange creates a user session, it also initializes the scratch space (also referred to as the scratchpad) to hold various variables which are valid throughout the session.
By default, the scratch space is created empty. You can define default
scratch variables and their values using ScratchDefault
.
Once defined, a scratch variable will exist either until it is explicitly
deleted (most probably using scratchd
tag) or the session ends.
The exception are so-called "temp" variables, which are normal scratch
variables (they live in the scratch space), but are automatically deleted
when page processing ends. You can use them for saving intermediate results
that you calculate and display, and then want Interchange to automatically forget.
The catalog programmer has complete control over the scratch variables.
The following tags manipulate the scratch space:
set
,
seti
,
tmp
,
tmpn
,
scratch
and
scratchd
.
Here's a complete list of ways to set scratch variables.
In ITL:
Set syntax | Attributes |
---|---|
[set VARNAME ]VALUE [/set] | ITL code in value body is not interpolated |
[seti VARNAME ]VALUE [/seti] | ITL code in value body is interpolated |
[tmp VARNAME ]VALUE [/tmp] | ITL code in value body is interpolated, variable is temporary |
[tmpn VARNAME ]VALUE [/tmpn] | ITL code in value body is not interpolated, variable is temporary |
In embedded Perl:
Set syntax | Attributes |
---|---|
$Scratch->{VARNAME } = 'VALUE '; | Set value |
$Tag->tmp('VARNAME '); | Mark scratch variable as temporary, must set a value afterwards |
$Tag->tmp('VARNAME ', 'VALUE '); | Mark scratch variable as temporary and set value at the same time |
In GlobalSub code or usertags:
Set syntax | Attributes |
---|---|
$::Scratch->{VARNAME } = 'VALUE '; | Set value |
Here's a complete list of ways to get scratch variables.
In ITL:
Get syntax | Attributes |
---|---|
[scratch VARNAME ] | Display value |
[scratchd VARNAME ] | Display value, delete scratch variable |
In embedded Perl:
Get syntax | Attributes |
---|---|
$Scratch->{VARNAME }; | Get value |
$Session->{scratch}{VARNAME }; | Equivalent |
In GlobalSub code or usertags:
Get syntax | Attributes |
---|---|
$::Scratch->{VARNAME }; | Get value |
$::Session->{scratch}{VARNAME }; | Equivalent |
One other predefined use for scratch variables is to hold form processing
code (code that is executed on users' form submission).
See the button
tag for examples.
The session contains:
The length of the session identifier is determined by the session_id_length
Limit
setting.
See database.
Interchange allows taxing in a number of ways.
Sales tax calculation in this simple scheme is performed on a straight
percentage basis,
with certain items allowed to be tax-exempt. Simply initialize the
SalesTax
directive to the name of lookup fields. Those lookup fields
are the ones that are available on the final order form and indicate
geographical locality.
SalesTax
configuration directive is
set to form fields whose values will be used as keys to look up
the tax rate in products/salestax.asc
, based on localities.
salestax is a fixed-format database, and consists
of identifiers that match localities defined using SalesTax
.
Locality names are always forced to UPPER CASE, and are usually identified by zip or state codes:
SalesTax zip,state
Each line of the mentioned products/salestax.asc
file should contain a code (again, usually 5-digit zip or 2-letter state
ID), followed by a TAB character and a desired percentage.
default 0.0 45056 .0525 61821 .0725 61801 .075 IL .0625 OH .0525 VAT .15 WA .08
Based on user information given on the order form (and given our
sample SalesTax
setting), Interchange will attempt a tax lookup by
zip and state (in that order), and apply the percentage found to the
subtotal of the order.
The subtotal will include item prices, taxes and shipping costs (if
TaxShipping
was set up).
It will add the percentage, then make that available with the salestax
tag for display on the order form.
If no match is found in CATROOT/products/salestax.asc
,
the entry DEFAULT
(case-insensitive)
will be applied — which is usually zero.
If some items are not taxable, then you must set up a field in your
database which indicates that. Announce this field name by using the
NonTaxableField
directive. If the specified field for that item
evaluates to a true value, sales tax will not be applied to the item.
If you want to set a fixed tax for all orders, as might occur for VAT
in some countries, then SalesTax
set to the zip or state codes is
not optimal for the purpose of sales tax calculation. The solution is to
introduce an arbitrary variable that, unlike
zip or
state, does not change from user to user.
Then, you would set that variable in user session to point to a fixed entry
in the
CATROOT/products/salestax.asc
file.
Exactly how you're going to set a session variable is not important;
you could use the ValuesDefault
directive (
ValuesDefault tax_code VAT
), a hidden form variable
(<input type="hidden" name="tax_code" value="VAT" />
),
or a simple Perl code block
([perl] $Values->{tax_code} = 'VAT'; return [/perl]
).
This is one of the simpler taxing methods as well. A series of Interchange
Variable
settings are read to develop a salestax rate for one or more
geographical localities.
With this method, you implement the simple SalesTax method explained above,
but only put one entry in
CATROOT/products/salestax.asc
. Then
, TAXRATE
and TAXSHIPPING
catalog variables would be used to build
the tax rates.
The single entry in CATROOT/products/salestax.asc
should be default with a value of fly-tax
:
DEFAULT [fly-tax]
With this method, country and state databases are used to develop complex VAT or salestax rate calculations, based on country and state, possibly with different rates based on product type.
The SalesTax
directive must be set to multi
, and
then the type of tax to apply will be read from the
country database. Since this is a standard database, to
display taxing for say, Croatia (code HR
), you'd
simply call:
[data table=country col=tax key=HR]
We've mentioned some hard-coded values above (the country table, column names
etc.), but this is all configurable using
MV_COUNTRY_TABLE
,
MV_COUNTRY_FIELD
,
MV_COUNTRY_TAX_FIELD
,
MV_STATE_TABLE
,
,
MV_STATE_TAX_FIELD
,
MV_TAX_TYPE_FIELD
and
MV_TAX_CATEGORY_FIELD
variables.
So, with this multi approach, Interchange first performs a lookup
in the country database. The default key for the lookup
is, of course, [value country]
(value of the
country
form variable), and the column retrieved is
tax. At that point, the following rules
are applied:
If no string is found, tax returns 0
If string simple:
is found, COUNTRY_CODE
fly-tax
is used for the appropriate country.
If string state
is found, then another lookup
in the country database is done; it's something along
the lines of
select tax from state where country = '[value country]' and state = '[value state]'
The value is then applied as explained in the following steps
If a pure (integer or decimal) number is found (such as
0.05
), the rate is applied directly
If a percentage is found, such as 22.2%
, the rate is,
obviously, applied as a percentage
If a string category =
is found, the
tax_category field in the
products database is used to determine tax rate.
(The default option is there to be applied if the
tax_category for a product is zero
or unspecified.
The special field D
%, default = D
%mv_shipping
is used to set tax rate
for shipping, if shipping is indeed taxed. If shipping is taxed only when
taxable items are in the cart, then use
mv_shipping_when_taxable
instead.)
Using Levies
and Levy
structure, any or all of
the above methods are used in combination to implement the a
taxing scheme of truly arbitrary complexity.
To prevent tax calculation routines from ever applying a negative tax amount, see pragma <pragma>no_negative_tax</pragma>.
Time specification follows:
Table 1. Recognized format strings for time functions
Format String | Description | Range | Example |
---|---|---|---|
%c | date and time | Mon Apr 12 14:17:46 2004 | |
%x | date | 01/18/2004 | |
%X | time | 14:17:46 | |
%Y %y | year | 2004 04 | |
%j | day of the year | 0-366 | 112 |
%m | month | 01-12 | 01 |
%B %b | month name | January Jan | |
%d %e | day of the month | 01-31 | 08 8 |
%U %W | week number, week starts Sunday (%U) or Monday (%W) | 00-53 | 48 47 |
%w %u | weekday | 0-6 | 03 3 |
%A %a | weekday name | Sunday Sun | |
%H %I | hour | 00-23 00-12 | 23 11 |
%p | AM/PM | PM | |
%M | minute | 00-59 | 57 |
%s | seconds since epoch | 1233908038 | |
%S | second | 00-59 | 57 |
%Z | time zone name | CEST |
Several actions performed by users are recorded by Interchange's usertracking facility, if one is enabled. Usertrack data can actually be obtained from two different locations.
The first location is Interchange itself, where you tune the TrackFile
directive to your needs (such as logs/usertrack
).
This is the approach used in the standard demo.
The second location are the HTTP headers. You can configure the Web server to write this header into access logs; here's an example for the Apache web server:
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" \ %T %v \"%{X-Track}o\"" track CustomLog /var/log/apache/track.log track
In general programming terms, a true value is anything that is not false.
Note that a string containing only whitespace (say, one TAB or space) is also considered 'true'.
The name "UserDB" stands for a set of built-in Interchange features related to the user database. That includes the database table that will hold your visitors data, and the set of functions operating either on that database or on people's session information.
Interchange's set of user database functions is elaborate, but it still manages not to place application-specific requirements on your practical database. While it is possible to use Interchange's UserDB functions with your custom type of database, it is strongly recommended to follow simple UserDB naming and usage standards. That alone will instantly give you a huge amount of built-in userdb functionality with no overhead programming.
The whole UserDB set consists of:
The user database. It is the database table that will actually contain all persistent information for visitors who create an account at your website. This table has only a few required fields.
Set of predefined Interchange variables (such as mv_username
,
mv_password
and mv_verify
.)
As you will see, most of the UserDB functions in
Interchange will be simplified to placing data in predefined variables
and then invoking required actions; no matter how complex the
actions might be internally.
The userdb
tag. This tag is the convenient method of invoking
UserDB functions. As long as you will adhere to naming and usage
standards, UserDB invocations will be as simple as
[userdb login]
, [userdb save]
or
[userdb logout]
.
To create the database, you could follow a very simple structure. The only two required fields are username and password. Your sample test database could look like this:
code password
(Note that the name of the first column — the primary key — is arbitrary because Interchange's default functions are not accessing it by name. But it is a good standard to name the first field code in all your database tables).
It is, however, recommended to use an SQL database for any more complex functions. Let's take a look at a more elaborate user database that, among other things, has support for visitors' first and last name, inactivity flag and billing and shipping addresses:
Database userdb LENGTH_EXCEPTION_DEFAULT truncate_log Database userdb DEFAULT_TYPE varchar(255) Database userdb COLUMN_DEF "username=varchar(64) NOT NULL PRIMARY KEY" Database userdb COLUMN_DEF "password=varchar(64) NOT NULL" Database userdb COLUMN_DEF "accounts=text" Database userdb COLUMN_DEF "acl=text" Database userdb COLUMN_DEF "address1=varchar(64)" Database userdb COLUMN_DEF "address2=varchar(64)" Database userdb COLUMN_DEF "address3=varchar(64)" Database userdb COLUMN_DEF "address_book=text" Database userdb COLUMN_DEF "b_address1=varchar(64)" Database userdb COLUMN_DEF "b_address2=varchar(64)" Database userdb COLUMN_DEF "b_address3=varchar(64)" Database userdb COLUMN_DEF "b_city=varchar(30)" Database userdb COLUMN_DEF "b_company=varchar(64)" Database userdb COLUMN_DEF "b_country=varchar(10)" Database userdb COLUMN_DEF "b_fname=varchar(30)" Database userdb COLUMN_DEF "b_lname=varchar(30)" Database userdb COLUMN_DEF "b_nickname=text" Database userdb COLUMN_DEF "b_phone=varchar(30)" Database userdb COLUMN_DEF "b_state=varchar(10)" Database userdb COLUMN_DEF "b_zip=varchar(10)" Database userdb COLUMN_DEF "carts=text" Database userdb COLUMN_DEF "city=varchar(30)" Database userdb COLUMN_DEF "company=varchar(64)" Database userdb COLUMN_DEF "country=varchar(10)" Database userdb COLUMN_DEF "credit_limit=varchar(16)" Database userdb COLUMN_DEF "db_acl=text" Database userdb COLUMN_DEF "dealer=varchar(32)" Database userdb COLUMN_DEF "email=varchar(42)" Database userdb COLUMN_DEF "fax=varchar(30)" Database userdb COLUMN_DEF "file_acl=text" Database userdb COLUMN_DEF "fname=varchar(30)" Database userdb COLUMN_DEF "inactive=varchar(8)" Database userdb COLUMN_DEF "lname=varchar(30)" Database userdb COLUMN_DEF "mail_list=text" Database userdb COLUMN_DEF "mod_time=varchar(20)" Database userdb COLUMN_DEF "mv_shipmode=varchar(255)" Database userdb COLUMN_DEF "owner=varchar(20)" Database userdb COLUMN_DEF "p_nickname=text" Database userdb COLUMN_DEF "phone_day=varchar(30)" Database userdb COLUMN_DEF "phone_night=varchar(30)" Database userdb COLUMN_DEF "price_level=varchar(30)" Database userdb COLUMN_DEF "preferences=text" Database userdb COLUMN_DEF "s_nickname=text" Database userdb COLUMN_DEF "state=varchar(20)" Database userdb COLUMN_DEF "zip=varchar(10)" Database userdb DEFAULT "inactive=''"
The above is Interchange's database representation format that allows Interchange to be aware of the database structure and automagically create it if it's missing. See database glossary entry for complete information.
The above is PostgreSQL-compatible definition. We have the equivalent
MySQL version available
(in short, you only need to replace
type =text
with MySQL's =BLOB
).
To make the new database table accessible to Interchange, and to define
a couple more UserDB-specific options, you could add the following
to your catalog.cfg
:
# For SQL databases: Database userdb userdb.txt __SQLDSN__ # Or for file-based database (DBM): Database userdb userdb.txt TAB # Encrypt passwords? UserDB default crypt 0 # Ignore uppercase/lowercase in usernames? UserDB default ignore_case 1 # Enable this in combination with the above, so that # username is always 'normalized': Filter mv_username lc # To disable field containing date of last change: UserDB default time_field none # To enable field containing date of last change #UserDB default time_field mod_time UserDB default logfile var/log/userdb.log # To allow people login using their email, and not their username # (in that case, username does not have to be meaningful and # can be automatically assigned, like "U00001"): #UserDB default indirect_login email
The login page could again be very simple, like this:
[set Login] [userdb login] [/set] <form action="[process secure=1]" method="POST"> <input type="hidden" name="mv_todo" value="return"> <input type="hidden" name="mv_nextpage" value="index"> <input type="hidden" name="mv_click" value="Login"> [form-session-id] <input name="mv_username" type="text"> <input name="mv_password" type="password"> <input value="Submit" type="submit"> </form>
More complex login page with support for cookies and "remembering" users, and that displays a logout option if the user is already logged in could look like this:
[set Login] [userdb login] [/set] [set Logout_choice] [if type=explicit compare="[userdb function=logout clear='[cgi clear_values]']"] [set mv_no_count]1[/set] [set mv_no_session_id]1[/set] [if cgi clear_cart] [calc] @$Items = (); return; [/calc] [/if] [/if] [/set] [tmp cookie_username][read-cookie MV_USERNAME][/tmp] <form action="[process secure=1]" method="POST"> <input type="hidden" name="mv_todo" value="return"> <input type="hidden" name="mv_nextpage" value="index"> [form-session-id] [if !session logged_in] <input type="hidden" name="mv_click" value="Login"> <input name="mv_username" type="text"> <input name="mv_password" type="password"> [if config CookieLogin] <input type="hidden" name="mv_cookie_password" value="0"> <input type="checkbox" name="mv_cookie_password" value="1" [if scratch cookie_username]CHECKED[/if]> [/if] [else] <input type="hidden" name="mv_click" value="Logout_choice"> Logged in as [data session username]. <input class="input" type="hidden" name="clear_values" value="1"> <input class="input" type="checkbox" name="clear_values" checked value="1"> Erase values. [/else] [/if] <input type="submit" value="Submit"> </form>
When the user logs in, user database values are automatically
copied to their values space and can be retrieved at
any time using the value
tag. (Values which are not present
in the database might take on a default value defined with
ValuesDefault
).
Often times, you would like to save users' data back to the user database, be it during or at the end of user session. You can automatically do this with a combination of form processing directives, but if you want to do it manually, use the following simple yet powerful code:
[update values] [userdb save]
The above code saves all users' values back to the database. Values which do not have a corresponding database field are ignored (as there's no place to save them). This is a fault-tolerant behavior and something you almost always want to happen anyway.
For all advanced examples and more technical discussion, see
userdb
tag documentation.
Tag is a basic functional unit in ITL — Interchange Tag Language. It is to Interchange what HTML tags are to a HTML page, or binary executables to an Unix shell.
As tags and their usage are explained under the ITL glossary entry, we are going to explain usertag inclusion in the Interchange server and usertag programming here.
Tags have traditionally been defined in
lib/Vend/Interpolate.pm
file in the Interchange source tree.
While some of the crucial tags are still defined there (search for
^sub tag_
in the file) to solve the chicken-or-egg
problem, new tags should be created as standalone files within the
code/
directory in the Interchange
source. This makes them more manageable and allows you to easily
"deactivate" unused tags and decrease IC;'s memory footprint (as
explained above).
Even though all Interchange tags are generally called tags or usertags, there are actually three types of tags: system tags, user tags and UI tags. There's no functional difference between them, but we've decided to introduce a rough distinction.
System tags (or core tags) are defined in
lib/Vend/Interpolate.pm
and
code/SystemTag/
in
Interchange source. They are used by core Interchange modules
(lib/Vend/*.pm
files) and are required for a
functional installation.
Some files have the extension .coretag
, and some
have the usual .tag
, but there's no difference.
(.tag
is preferred for your custom tags).
User tags are defined in
code/UserTag/
directory and
form a collection of commonly used Interchange tags. This is the most
common type and directly intended for custom catalog programming.
UI (User Interface) tags are defined in
code/UI_Tag/
directory and
form a collection of extra tags used by our Admin UI interface.
A catalog that is not running the Admin UI should, theoretically, be able
to do without the whole code/UI_Tag/
directory. However, as very useful tags are found within all three types,
the AccumulateCode
approach is preferred over this crude
directory-based selection.
Global usertags (defined at the Interchange server level) run directly under Interchange server permissions, without restrictions.
Catalog usertags (defined at the catalog level), however, run under safe restrictions to maximize security.
You should run all your custom tags at catalog-level and eventually
let some of the restrictions loose using SafeUntrap
configuration
directive. Run global usertags only when there is no other option, and
make sure your code is as resilient to arbitrary user input as possible.
Usertags are defined using the UserTag
config directive so,
obviously, they have to be defined in interchange.cfg
or catalog.cfg
, or files included
by those basic configuration files.
While you could add your own tags to the default Interchange directories
(within the code/
directory, as
explained) or even define them in interchange.cfg
or catalog.cfg
directly, it's generally
best if you create a
usertag/
directory (at a catalog
or global level), put your custom tags there, and include them in the
running configuration with the include usertag/*.tag
configuration directive.
Here's a classic hello world usertag example, containing all the relevant structural elements:
# Copyright YEAR COPYRIGHT-HOLDER-NAME EMAIL-OR-WEB-ADDRESS # Licensed under the GNU GPL v2. See file LICENSE for details. # $Id: usertag,v 1.4 2007-11-14 12:38:08 racke Exp $ UserTag hello-world Order name UserTag hello-world addAttr UserTag hello-world Version $Revision: 1.4 $ UserTag hello-world Routine <<EOR sub { my ($name, $opt) = @_; my $ret; $name ||= "world"; $name = ucfirst $name; if ( $_ = $opt->{surname} ) { $_ = ucfirst; $name .= " $_"; } $ret = "Hello, $name!"; return $ret; } EOR
After you install the usertag (as explained above), you can test it by using this sample HTML code:
<pre> The default name: [hello-world] Name "John": [hello-world john] Name "John", surname "Doe": [hello-world name=john surname=doe] </pre>
As you can see, each usertag is defined through a series of
UserTag
lines. All possible UserTag
options are
explained in the following section.
Recognized usertag options are defined as a Perl hash named
%tagCanon
in file lib/Vend/Config.pm
:
Group
ActionMap
ArrayCode
HashCode
CoreTag
SearchOp
Filter
FormAction
OrderCheck
UserTag
SystemTag
Widget
Alias — another name, an alias, for the tag.
UserTagALIASED-NAME
AliasNAME
UserTag time Version $Revision: 1.4 $ UserTag date Alias time
addAttr — pass a hash reference with all user-supplied tag attributes as last argument to the tag handling subroutine.
UserTagNAME
addAttr[VALUE]
UserTag benchmark addAttr (implies Yes) UserTag benchmark addAttr 1 UserTag benchmark addAttr 0
attrAlias — another name, an alias, for a tag's attribute.
UserTagNAME
attrAliasALIASED-ATTR-NAME
REAL-ATTR-NAME
UserTag meta-info Order table column key UserTag meta-info attrAlias col column
attrDefault
canNest
Description — embedded, one-line tag description.
UserTagNAME
DescriptionTEXT
UserTag uninstall_feature Description Uninstall feature installed with 'Feature' config directive. UserTag uninstall_feature Description <<EOD Uninstall feature installed with 'Feature' config directive. EOD
Override
Visibility
Help
Documentation — embedded tag documentation. This can be any free-form text, but sometimes it's handy to write the documentation in Perl POD syntax, as it allows the use of convenient pod2text and related commands to read the documentation.
UserTagNAME
DocumentationTEXT
UserTag tabbed-display Documentation <<EOD tabbed-display -- DHTML tabbed display ...... EOD
ExtraMeta
Gobble
hasEndTag — the tag has an end tag. In other words, the tag is a container.
UserTagNAME
hasEndTag[VALUE]
UserTag widget hasEndTag (implies Yes) UserTag widget hasEndTag 1 UserTag widget hasEndTag 0
Implicit
Interpolate — interpolate tag data. Due to a poor naming choice, this option behaves differently for non-container and container tags.
For non-container tags, it specifies whether tag output should be reparsed for more Interchange tags.
For container tags, it specifies whether tag
body should be interpolated before being passed
to the tag. Another option, NoReparse
then controls
whether final tag output should
be reparsed for more Interchange tags.
Interpolation is turned off by default.
UserTagNAME
Interpolate[VALUE]
UserTag table-organize Interpolate (implies Yes) UserTag table-organize Interpolate 1 UserTag table-organize Interpolate 0
InvalidateCache
isEndAnchor
noRearrange
Order
PosNumber — number of positional tag parameters. This option is not required as the number is automatically calculated from the Order
option.
UserTagNAME
PosNumberCOUNT
UserTag test Order opt1 opt2 opt3 UserTag test PosNumber 3
PosRoutine
MapRoutine
NoReparse — do not reparse output from container tags for more Interchange tags. This option has no effect on non-container tags.
Reparsing is turned on by default (NoReparse 0
).
UserTagNAME
NoReparse[VALUE]
UserTag either NoReparse (implies Yes) UserTag either NoReparse 1 UserTag either NoReparse 0
JavaScriptCheck
Required
Routine
Version
In general terms, a variable is "that which is variable; that which varies, or is subject to change".
In computer terms, variables have a name most of the time, so that you can
refer to them. For example, variable $amount
might have
a value of 10
, and that value may change over time.
Basically, all programs work with (a lot of) variables, be it to define their behavior or store intermediate results.
In Interchange, there are many kinds of variables. We have:
Read the respective glossary entries. This entry only deals with global and catalog variables.
Global and catalog variables are basic ways of storing variable information in Interchange. They are basically the same, but some of the definitions only make sense at global (Interchange server) level, and some only make sense at "local" (individual catalog) level. There's also a fallback mechanism available that can query the global setting if its instance at catalog level is not set.
Of those variables, we further (informally) distinguish between "core", "distribution" and "standard" variables. Core variables are being honored by common, underlying Interchange code; distribution variables are honored in our out of the box configurations, and standard variables are being honored in our standard demo catalog that we ship along with Interchange.
Look up the Variable
configuration directive for instructions on
setting variable values. Look up the var
tag for basic instructions on getting variable values.
Global and catalog variables are not normally modified dynamically (they
keep their value as set in interchange.cfg
or catalog.cfg
). However, they can be
manipulated at runtime, in which case you most probably want to do it before
Interchange puts a requested page into processing. This is best done in an
Autoload
routine.
![]() | Note |
---|---|
By the way, an |
When accessing variables, we distinguish between three access types:
from ITL code, from embedded Perl code, and from GlobalSub
s or
tags.
Here's a complete list of ways to access global or catalog variables:
In ITL:
Access syntax | Place of definition |
---|---|
__VARNAME __ | catalog.cfg |
@_VARNAME _@ | catalog.cfg , with fallback to interchange.cfg |
@@VARNAME @@ | interchange.cfg |
[var VARNAME ] | catalog.cfg |
[var VARNAME 1] | interchange.cfg |
[var VARNAME 2] | catalog.cfg , with fallback to interchange.cfg |
In embedded Perl:
Access syntax | Place of definition |
---|---|
$Variable->{VARNAME } | catalog.cfg |
$Tag->var('VARNAME ') | catalog.cfg |
$Tag->var('VARNAME ', 1) | interchange.cfg |
$Tag->var('VARNAME ', 2) | catalog.cfg , with fallback to interchange.cfg |
In GlobalSub code or usertags:
Access syntax | Place of definition |
---|---|
$::Variable->{VARNAME } | catalog.cfg |
$Tag->var('VARNAME ') | catalog.cfg |
$Tag->var('VARNAME ', 1) | interchange.cfg |
$Tag->var('VARNAME ', 2) | catalog.cfg , with fallback to interchange.cfg |
$Global::Variable->{VARNAME } | interchange.cfg , and only within GlobalSub code |