\label{Developer_Tutorial} \section{Adding a Handler to LON-CAPA} In this section, we will add a brand new {}``handler'' to LON-CAPA. A \textbf{handler\index{handler}} is code that gets loaded by the Apache web server that tells the web server how to handle a certain kind of request. For a complete discussion of this process, you will need to read the Apache documentation; we especially recommend the O'Reilly book {}``Writing Apache Modules with Perl and C''. In this chapter we will add a {}``Response'' handler; this is the most important handler in the system because it is directly responsible for what the user sees on the screen when they end up using the handler. As a side-effect of installing a new handler from scratch, we will also show how to modify the files LON-CAPA uses and run LON-CAPA with the modified files. After completing this tutorial you should be able to more easily understand the structure of the vast majority of LON-CAPA code. If you are brave and are not using Red Hat to run LON-CAPA, the \texttt{/etc/httpd} and \texttt{/var/log/httpd} directories may not exist, as your distribution may prefer to refer to them as \texttt{/etc/apache} and \texttt{/etc/log/apache}, such as Gentoo does. In that case, I recommend symlinking \texttt{/etc/httpd} to \texttt{/etc/apache} (LON-CAPA depends on \texttt{/etc/httpd} existing), and either make the appropriate substitutions in the following directions for the log files, or make a symlink there, too. (\texttt{ln -s /etc/apache /etc/httpd}) \subsection{Pre-requisites} \begin{itemize} \item LON-CAPA must be installed and running. \item \textbf{Perl}: You're going to need to know some or you will not understand the code. You don't need to be intimately familiar with the many hundreds of libraries Perl has, though, which is much harder than learning the language. \item Knowledge of the Apache server is not required for this tutorial, but {}``real work'' may require more information then is given here. \end{itemize} \subsection{Adding a New Handler} You need to create a \emph{new Perl module} to serve as the handler. A {}``Perl module''\index{Perl module} is a file that has a name ending in {}``.pm''. For the sake of concreteness, I will refer to this file as \texttt{lontutorial.pm}% \footnote{Currently, almost all LON-CAPA Perl modules are prefixed with {}``lon''. This is a weak convention used inconsistently, and theoretically it will be dropped from the project someday. % }. Create this new file where ever you'd like for now. \subsubsection{Apache Handler Shell} Put the following code into your Perl module: \begin{lyxcode} package~Apache::lontutorial; ~ use~strict; use~Apache::Constants~qw(:common); use~Apache::loncommon; ~ =pod ~ =head1~NAME ~ lontutorial~-~tutorial~code ~ =head1~SYNOPSIS ~ lontutorial~contains~the~code~from~the~tutorial,~and does~tutorial~stuff. ~ =cut ~ sub~handler~\{ ~~~~my~\$r~=~shift; ~ ~~~~\$r->print('Hello~world!'); ~~~~return~OK; \} ~ 1; \end{lyxcode} A line-by-line breakdown: \begin{itemize} \item \texttt{\textbf{package Apache::lontutorial;}}: This puts everything in the file in the \texttt{Apache::lontutorial} namespace. Note that the \texttt{lontutorial} part should match your filename before the \texttt{.pm}. (The file may contain other packages; see \texttt{lonnavmaps.pm} or \texttt{lonhelper.pm} for an example. But this package should show up in the file or Apache may have trouble finding your file.) \item \texttt{\textbf{use strict;}}: Turns on strict variable checking, which is a Very Good Idea. If you intend to submit the code back to the main LON-CAPA project, please make sure to use this. \item \texttt{\textbf{use Apache::Constants qw(:common);}}: This loads certain constants used to communicate with Apache into the current namespace. \item \texttt{\textbf{use Apache::loncommon;}}: This loads some commonly-used functions we will use later. See \texttt{man Apache::loncommon} for more information. \item \texttt{\textbf{=pod}} through \texttt{\textbf{=cut}}: This contains inline documentation in the POD format. Please see \texttt{man perlpod} for more information. I encourage you to document your code. Note many current LON-CAPA modules do not contain good examples. Please consult \texttt{perldoc lonhelp.pm} in the \texttt{loncom/interface} directory of the CVS repository, or see the \texttt{lonhelp.pm} information later in the developer manual, for style considerations. The documentation so produced can be easily added to this Developer's Manual if it would be useful to others, so you're accomplishing the dual purpose of documenting in the file and the manual at the same time. \item \texttt{\textbf{sub handler \{}}: Apache will call the \texttt{handler} function in the namespace it is given for the handler in the \texttt{loncapa\_apache.conf} file; see below. Thus, we need to supply it, or Apache will fail and complain about a missing subroutine. \item \texttt{\textbf{my \$r = shift;}}: The first and only parameter to the handler function is the Apache Response object. We will partially cover this object in this tutorial, for more information consult outside documentation. \item \texttt{\textbf{\$r->print('Hello world!');}}: \texttt{\$r->print} is how we sent text to the user. This is the \emph{only} method to send text to the user's web browser, and in the end, all output ends up going through this function. \item \texttt{\textbf{return OK;}}: {}``OK'' is a constant we imported in our namespace in the use \texttt{Apache::Constants} line. It tells the server the page was successfully displayed, as opposed to a 404 or other error. \item \texttt{\textbf{1;}}: This tells Perl the module was successfully parsed, and is a required part of Perl modules. \end{itemize} \subsubsection{Telling Apache About Your Handler} In order for Apache to use your file, you need to tell Apache where to find it, and what URL(s) to use for it. Add the following to your \texttt{/etc/httpd/conf/loncapa\_apache.conf}, after the \texttt{PerlTransHandler} declaration: \begin{lyxcode} PerlAccessHandler~~~~~~~Apache::lonacc SetHandler~~~~~~~~~~~~~~perl-script PerlHandler~~~~~~~~~~~~~Apache::lontutorial ErrorDocument~~~~~~500~~/adm/errorhandler \end{lyxcode} This will tell Apache to call Apache::lontutorial::handler when someone goes to /adm/tutorial to see what to do. Note: The \texttt{Apache::} is not technically necessary in mod\_perl, it's just that LON-CAPA uses it. We may remove it from the system someday. In the meantime, it would be consistent with the rest of the system to continue use it. (If you don't understand what this is saying, don't worry about it.) \subsubsection{Putting The File Where Apache Can Find It} Now you need to put that file where Apache can find it. Apache will look for it in \texttt{/home/httpd/lib/perl/Apache}/, so copy it there. \begin{lyxcode} cp~lontutorial~/home/httpd/lib/perl/Apache/ \end{lyxcode} \subsubsection{Running Apache} You can now (re-)start the Apache server. \begin{lyxcode} /etc/init.d/httpd~restart \end{lyxcode} Also, if you are logged into LON-CAPA, log out (exit) before continuing. (Note re-starting the server does \emph{not} automatically log you out, which is really helpful while debugging so you don't have to continuously log back in.) If everything went well, you can now go to your server and go to \texttt{http://your.server/adm/tutorial}, with \texttt{your.server} replaced by the domain name of your LON-CAPA server, of course. (I will use {}``your.server'' that way for the rest of this tutorial.) And you should see\ldots{} {}``Bad Request''? What happened? You don't know it yet, but we're making a handler for functionality that will require the user to be logged in for it to work, because it is going to depend on information about the user. Right now, nobody is logged in. The \texttt{PerlAccessHandler\index{PerlAccessHandler}} line in what we put in \texttt{loncapa\_apache.conf} tells Apache to use {}``Apache::lonacc::handler'' as an \textbf{access handler}\index{access handler}. An \textbf{access handler} determines whether the user has permission to perform the current action. lonacc is a handler written for LON-CAPA that among other things, ensures the user is logged in. This handler told Apache that this is a {}``Bad Request'', which is why you got the {}``Bad Request'' screen. Your handler code was actually \textbf{never even executed}; Apache aborted the request before getting to your code. Log into LON-CAPA, and choose a role with a course. (You may need to create a course; please see the Course Management manual for more information.) Any role with a course, Student, Instructor, Course Coordinator or otherwise is fine. We won't be making any changes to the course data so it doesn't matter if it's a {}``real'' course. Once you've gotten to the first page of the course (normal entry), change your URL to read \texttt{http://your.server/adm/tutorial}. You should now see {}``Hello world!'' on the screen. \paragraph{Apache can't find \texttt{/adm/tutorial}:} If Apache is claiming it can't find the file named {}``/adm/tutorial'', something is wrong with your \texttt{loncapa\_apache.conf} file. Please double check the entry to make sure it is in the right place, that it is typed correctly, and that all the lines are present. If it is still incorrect, please ask for help on the \texttt{lon-capa-dev@mail.lon-capa.org} list, including a copy of your \texttt{loncapa\_apache.conf} file in the email; either something is wrong that is beyond the scope of this document(other system configuration issues) or you really can't find the error. \paragraph{{}``Something went wrong, please help us find out what''} If you get an icon of the LON-CAPA logo with a cracked screen and the preceding message, something is wrong with your Perl code. This is the standard screen that asks the user to send us more information about what happened, so we can try to diagnose the problem. Since you're a developer, you can ignore that screen and actually collect the information and act on it directly. The Perl error landed in \texttt{/var/log/httpd/error\_log}, in the last few lines. Take a look at what was in there, and try to fix it. \begin{lyxcode} tail~/var/log/httpd/error\_log \end{lyxcode} Since I can't guess what error you encountered, I'm afraid I can't help you out here. But I do know the code I wrote above worked, so as a last resort try to figure out what is different. If you are absolutely stumped, please ask for help on the \texttt{lon-capa-dev@mail.lon-capa.org} list, including a copy of your \texttt{lontutorial.pm} file in the email; either something is wrong that is beyond the scope of this document (other system configuration issues) or you really can't find the error. Actually, if you have the time, I recommend adding a syntax error to your \texttt{lontutorial.pm}, even if it worked the first time, so you can see what happens. This will not be the last time there's an error in your code\ldots{} \subsubsection{Modifying The Module} When Apache is started, it reads all the Perl modules in LON-CAPA into memory, and after that, does not use the \texttt{.pm} files in \texttt{/home/httpd/lib/perl/Apache}% \footnote{Slight exaggeration here: Actually it only loads the ones mentioned in startup.pl, and since your module isn't in that file, it won't be loaded at startup. But this really doesn't matter much.% }. To get Apache to use your newly-edited files, you need to copy them to \texttt{/home/httpd/lib/perl/Apache} and restart the Apache server. It is generally not a good idea to directly modify the files in \texttt{/home/httpd/lib/perl/Apache} while developing, because of the difficulty you will have later propagating changes made there back to more useful places, such as in the LON-CAPA CVS repository. To alleviate the annoyance of constantly copying the file to that directory and restarting the server, compose a single command line that does all of it at once. I often use something like the following: \begin{lyxcode} pushd~.;~cd~YOUR\_DIRECTORY;~cp~lontutorial.pm ~/home/httpd/lib/perl/Apache;~/etc/init.d/httpd~restart; ~popd \end{lyxcode} where YOUR\_DIRECTORY is replaced by the directory containing the \texttt{lontutorial.pm} file, and that is all one line. The {}``pushd'' and {}``popd'' commands are built-in to the Bash shell; other shells may or may not contain such commands. Once you've created this command line, you can just use the Up arrow in your shell to get it back. You can add whatever you want to it as needed; that's why I wrap it in the {}``pushd'' and {}``popd'' commands, so you can {}``cd'' without affecting your current directory. If you had a syntax error in your Perl module, fix it now. If you didn't, I'd recommend make a small change to the printed string or something and making sure you have figured out how to change the code in the Apache server correctly before continuing. Once again, notice that even after re-starting the server, you are still logged in with the same settings you were using before the server re-start. \subsection{Making The Handler Do Something} \subsubsection{A Bit More Boilerplate} At this point, your handler isn't doing much. If you look carefully, you'll notice that the {}``Hello World'' on the screen is in a mono-spaced font, not the proportional font used by default in HTML. That's because \emph{all} you sent was {}``Hello World!''; the browser looks at the header of the HTTP response to see what kind of response it is getting back, but you didn't even \emph{send} a header. Technically, that's not a legal HTTP response, but the browser was accommodating and displayed what you got back anyhow as text. (Consult documentation on the HTTP protocol for more information about this; generally you will not need to worry about this in LON-CAPA beyond the boilerplate presented here.) To send back a proper HTTP response, add this code after the \texttt{my \$r = shift;}: \begin{lyxcode} ~~~~\#~Handle~header-only~request ~~~~if~(\$r->header\_only)~\{ ~~~~~~~~if~(\$env\{'browser.mathml'\})~\{ ~~~~~~~~~~~~\$r->content\_type('text/xml'); ~~~~~~~~\}~else~\{ ~~~~~~~~~~~~\$r->content\_type('text/html'); ~~~~~~~~\} ~~~~~~~~\$r->send\_http\_header; ~~~~~~~~return~OK; ~~~~\} ~~~~\#~Send~header,~don't~cache~this~page ~~~~if~(\$env\{'browser.mathml'\})~\{ ~~~~~~~~\$r->content\_type('text/xml'); ~~~~\}~else~\{ ~~~~~~~~\$r->content\_type('text/html'); ~~~~\} ~~~~\&Apache::loncommon::no\_cache(\$r); ~~~~\$r->send\_http\_header; \end{lyxcode} This is boilerplate that appears at the top of many LON-CAPA handlers. It handles the case where the user is requesting that only headers be sent; unusual, but worth handling. It sets the content type appropriately, and tells the client not to cache this page, which is appropriate since it will be dynamic. For efficiency, when you use \texttt{\$r->print()} to send something to the user, Apache assumes you know what you are doing and will immediately% \footnote{Actually, if you send a lot of little things, Apache is smart enough to bundle them together into one big network packet for efficiency. If you absolutely want Apache to send everything it has to the user \emph{right now}, use \texttt{\$r->rflush()}\ldots{} but sparingly, as repeated use can cause a \emph{huge} slowdown.% } send what you printed across the network. Since the HTTP header is supposed to precede all content, it is important that you send the header before you send any other content across the network, so we ask the Apache server to go ahead and send the header with \texttt{\$r->send\_http\_header()}. Since the \texttt{Apache::loncommon::no\_cache()} call affects the header, it must be called before we send it out, because once the header is sent there's no getting it back. All code that follows this code will send the output to the user directly. Finally, let's add two more lines to make it fit into the system better. Wrap the following three lines around the {}``Hello World'' line, as shown: \begin{lyxcode} \$r->print('Tutorial~Page'); \$r->print(\&Apache::loncommon::bodytag('Tutorial~Handler','','')); \$r->print('Hello~world!'); \$r->print(''); \end{lyxcode} Now use your command line to copy \texttt{lontutorial.pm} and restart the server. Go to \texttt{/adm/tutorial}, and you'll see a nice looking page saying {}``Hello world!''. Congratulations, you now have a handler ready to do some real work with! \subsubsection{Rending Navmaps} To start with, let's go ahead and display the navmap for the current course. Under \texttt{use strict;}, add this line: \begin{lyxcode} use~Apache::lonnavmaps; \end{lyxcode} Remove the {}``Hello world!'' line and replace it with this: \begin{lyxcode} \$env\{'form.condition'\}~=~1; my~\$renderArgs~=~\{~'cols'~=>~{[}Apache::lonnavmaps::resource{]}, ~~~~~~~~~~~~~~~~~~~'showParts'~=>~0, ~~~~~~~~~~~~~~~~~~~'r'~=>~\$r~\}; Apache::lonnavmaps::render(\$renderArgs); \end{lyxcode} Line by line: \begin{itemize} \item \texttt{\textbf{use Apache::lonnavmaps;}}: \texttt{Apache::lonnavmaps} contains routines that help render navmaps. For more information, see later in this manual or type \texttt{man Apache::lonnavmaps}. This ensures these routines are loaded into memory. \item \texttt{\textbf{\$env\{'form.condition'\} = 1;}}: This is an an argument being passed to the Apache::lonnavmaps::render routine in a rather unorthodox way. This will cause the navmap to render all of the resources, by default, except for what we explicitly exclude. Since we're not going to exclude anything (which we would do with \texttt{\$env\{'form.filter'\}}), all resources will be shown. \item \texttt{\textbf{my \$renderArgs\ldots{}\$r\};}}: Since the \texttt{render} routine takes a \emph{lot} of arguments, the \texttt{render} routine takes in a hash reference instead of a traditional list of arguments. For full information about what that function takes, consult the documentation. \texttt{'cols'} will tell the render function what to render in the navmap; {}``Apache::lonnavmaps::resource'' is a constant that indicates the standard listing of the resource with a link to the resource, which is the first column of the \textbf{NAV} display you are used to. \texttt{'showParts' => 0} tells the render function not to show individual parts, which will not be useful to us. \texttt{'r' => \$r} passes the Apache response object to the \texttt{render} function, which it uses to provide periodic output to the user by using the \texttt{rflush} method on the object. \item \texttt{\textbf{Apache::lonnavmaps::render(\$renderArgs);}}: Performs the actual rendering of the navmaps. Since we passed it \texttt{\$r}, it will take care of displaying the navmaps for us. If we didn't pass it \texttt{\$r}, it would return a string containing the rendered HTML, which we would be responsible for \texttt{\$r->print}ing. \end{itemize} Save this and use your command line to re-load the server. Make sure you've selected a specific course role before trying to visit \texttt{/adm/tutorial}. You may need to create a course with a few resources in it first, depending on your setup. \subsubsection{Goal} In order to exercise storing data on the LON-CAPA network, we're going to do something a little silly, but pedagogically useful. Let's add two columns to the navmap, one which will provide a checkbox which we can click, and another which will show how many times we've selected that particular resource and submitted a form. While this is somewhat silly, the general principle of doing something for some selection of resources is a fairly common operation in LON-CAPA, as is storing or loading data from the network. In doing this, we'll learn: \begin{itemize} \item How to store data on the LON-CAPA network correctly. \item How to process incoming form input in LON-CAPA. \item How to work with and identify resources in LON-CAPA. \end{itemize} \subsection{Adding A Form For Input} The first thing we need to do to accomplish this is to add an HTML form to the screen so we have something to submit. Just above the \texttt{\$env\{'form.condition'\}} line, add the following: \begin{lyxcode} \$r->print(\char`\"{}\char`\"{}); \end{lyxcode} This will print out a form element, and a hidden form element named {}``submission'' with a value of {}``1''. Strictly speaking, this is not necessary, but this serves as a {}``sentinel'', which makes it easy to check later whether or not we need to process input with a single \texttt{if} statement. Just before \texttt{\$r->print({}``'');}, add this: \begin{lyxcode} \$r->print(''); \end{lyxcode} If you now use your command line to re-load the server, and you examine the source code of the page on /adm/tutorial, you should now see a form, with a hidden value and a submit button, though pressing the submit button won't do much. \subsubsection{Seeing The Incoming Data} LON-CAPA automatically processing incoming POST data and exposes it to you in the \texttt{\%env} hash. The data will appear in \texttt{\$env\{'form.\$varname'\}}, where \texttt{\$varname} is the variable name of the HTML form element. In this case, since we have an element named {}``submission'', a {}``1'' will appear in \texttt{\$env\{'form.submission'\}} when we hit the {}``Increment'' button. To see this, add the following after the \texttt{bodytag} call: \begin{lyxcode} if~(\$env\{'form.submission'\})~\{ ~~~~\$r->print('

Form~submitted.

'); \}~else~\{ ~~~~\$r->print('

No~form~information~submitted.

'); \} \end{lyxcode} Reload the tutorial code into the server. Now, when you type in \texttt{/adm/tutorial}, {}``No form information submitted.'' will appear on the screen. When you hit {}``Increment'', {}``Form submitted.'' will appear. Note this only applies to POST'ed data. If you use GET, the data will appear on the query string. For your code, this will show up in \texttt{\$env\{QUERY\_STRING\}}. If you want to invoke LON-CAPA's processing on that string, so you see the variables in \texttt{\%env}, use \texttt{Apache::loncommon::get\_unprocessed\_cgi(\$env\{QUERY\_STRING\});}. This is particularly useful for cases where input may be coming in via either POST or GET. \subsubsection{Adding Checkboxes for Input} Now we want to add a checkbox column into the navmap rendering so we have a place to input our selections. In order to do that, we need to provide the rendering code with a subroutine that will take an \texttt{Apache::lonnavmaps::resource} object and return the string we want to print, including the \texttt{td} and \texttt{/td} tags. (For more information, consult the \texttt{Apache::lonnavmaps::render} documentation.) Add the following after the second and last \texttt{send\_http\_header} line: \begin{lyxcode} my~\$checkboxCode~=~sub~\{~my~\$resource~=~shift; ~~~~return~'symb())~.~ ~~~~'\char`\"{}~/>'; \}; \end{lyxcode} A \textbf{symb} is a string which uniquely identifies a resource within a course, since the same resource can show up in multiple sequences, or multiple times in the same sequence. Thus, that makes a good ID for the checkboxes. Given an \texttt{Apache::lonnavmaps::resource} object, the {}``symb'' method will return the symb. \texttt{Apache::lonnet::escape} is a function that does URL-escaping on the resulting string, so it's safe to pass into an HTML attribute. (XML-type escaping would work too.) Go ahead and load this into the server, so you can see the HTML output by viewing the source. \subsubsection{Getting The Checked Resources} Let's make sure we can retrieve the checked resources before continuing. Add the following code before the \texttt{\$r->print({}``print(\char`\"{}\$symb\char`\"{}); ~~~~\} \} \end{lyxcode} That will print each symb that we select the checkbox for on the screen. You may remove this later if you find it unsightly. Re-load the code into the server and make sure it's still working. Try checking a few boxes and hitting {}``Increment'' to make sure the symbs appear at the top of the screen. \subsection{Storing Data on the LON-CAPA Network} Let's suppose you want to record how often you submit a given checkbox. You can't write to the current server's hard drive, because there's no guarantee that next time you log on, that you'll get the same server. (Of course, if you're on a single development system, you can know that, but if you want your code to be generally useful for LON-CAPA, you can't depend on being on the same system.) Let's go ahead and retrieve the hash we're going to store. If it doesn't already exist, it will end up getting created implicitly. Before the \texttt{foreach (keys \%env)}, add \begin{lyxcode} my~\%countHash~=~Apache::lonnet::restore('tutorial'); my~\%newHash~=~(); \end{lyxcode} \texttt{\%countHash} will contain what we've stored. \texttt{\%newHash} will be used to store new data into the stored hash; please see the documentation on lonnet to see why this is necessary. \texttt{'tutorial'} identifies the hash we wish to store; if we make a later \texttt{restore} call with that parameter we'll get this hash back. Normally you'll want to choose the name of the hash more carefully, for instance including the course ID into it, unless you really mean to do something for all courses. For simplicity, we are leaving that out. Replace the \texttt{foreach} loop that is printing out the symbs with this: \begin{lyxcode} foreach~(keys~\%env)~\{ ~~~~if~(substr(\$\_,~0,~10)~eq~'form.symb.')~\{ ~~~~~~~~my~\$symb~=~substr(\$\_,~10); ~~~~~~~~\$symb~=~Apache::lonnet::unescape(\$symb); ~~~~~~~~\$countHash\{\$symb\}++; ~~~~~~~~\$newHash\{\$symb\}~=~\$countHash\{\$symb\}; ~~~~\} \} \end{lyxcode} That will increment the count for each symb that we checked a box for. We need to store the data the user changed, so just before \texttt{return OK;}, add the following line of code: \begin{lyxcode} Apache::lonnet::store(\textbackslash{}\%newHash,~'tutorial'); \end{lyxcode} \subsubsection{Displaying The Count} Finally, we need a column that will display the count of times we've checked the box. Right after the \texttt{foreach} loop we just modified, add \begin{lyxcode} my~\$countCode~=~sub~\{~my~\$resource~=~shift; ~~return~''~. ~~~~\$countHash\{\$resource->symb()\}~. ~~~~\char`\"{}\char`\"{}; ~~\}; \end{lyxcode} Add \texttt{\$countCode} to the 'cols' list in the \texttt{\$renderArgs}: \begin{lyxcode} my~\$renderArgs~=~\{~'cols'~=>~{[}Apache::lonnavmaps::resource, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\$checkboxCode,~\$countCode{]}, ~~~~~~~~~~~~~~~~~~~'showParts'~=>~0, ~~~~~~~~~~~~~~~~~~~'r'~=>~\$r~\}; \end{lyxcode} Viola! That should do it. \subsection{Internationalization} In order for your handler to be able to submitted to LON-CAPA, we'd really appreciate it if your handler was already internationalized. ``Internationalization'' refers to the process of adding hooks to code to make it easy to ``localize''. ``Localizing'' a program means taking advantage of those hooks and create a file that describes to the LON-CAPA web system how to display LON-CAPA in a language other than English. For more complete information about Internationalization and Localization, please refer to the Internationalization chapter. For now, suffice it to say we need to wrap the text strings we are using in the program in calls to the hook subroutine, which will be named \texttt{\&mt}. First, at the top of your handler, near the other \texttt{use} commands, add \texttt{use Apache::lonlocal;}. Second, find where all the strings are in your program and wrap them in calls to \texttt{\&mt}. I see the following: \begin{enumerate} \item In the \texttt{bodytag} call, the title: \begin{lyxcode} \$r->print(\&Apache::loncommon::bodytag(\&mt('Tutorial~Handler'),'','')); \end{lyxcode} \item In the two messages printed out showing the form submissions: (Of course in this case these are really just debugging messages we'd remove before actually using this handler. But let's localize them for practice) \begin{lyxcode} if~(\$env{'form.submission'})~{ ~~~~\$r->print('

'.\&mt('Form submitted').'

'); }~else~{ ~~~~\$r->print('

'.\&mt('No form information submitted.').'

'); } \end{lyxcode} Note we do \emph{not} generally want to wrap HTML tags unless we are absolutely forced to; those are constant across human languages and would only burder the translators with stuff they should not need to deal with. \item The label of the button we've created: \begin{lyxcode} \$r->print(''); \end{lyxcode} \end{enumerate} Note we only need to wrap things the human user will see; we don't need to wrap the \texttt{tutorial} parameter to the \texttt{Apache::lonnet::restore} call, for instance, and in fact wierd things could happen if we did! Also note that resource names and such are already as internationalized as they are going to get, so we don't need to worry about them. Since the internationalization system will return the value passed to \texttt{\&mt} by default if it can't find a translation, it's safe to internationalize code before translations exist, and in fact it's a necessary step. Also note that punctuation should be wrapped in the \texttt{\&mt} calls, including things like trailing periods, since not all languages have the same punctuation standards as English. This only covers simple internationalization. This can take you a long way, but if you encounter a more difficult problem, please send a note to the \texttt{lon-capa-dev@mail.lon-capa.org} mailing list. \subsection{Complete Code Listing} For your reference, I include the complete code listing for the tutorial here: \begin{lyxcode} package~Apache::lontutorial; ~ use~strict; use~Apache::lonnavmaps; use~Apache::Constants~qw(:common); use~Apache::lonlocal; ~ =pod ~ =head1~NAME ~ lontutorial~-~tutorial~code ~ =head1~SYNOPSIS ~ lontutorial~contains~the~code~from~the~tutorial,~and does~tutorial~stuff. ~ =cut ~ sub~handler~\{ ~~~~my~\$r~=~shift; ~~~~\#~Handle~header-only~request ~~~~if~(\$r->header\_only)~\{ ~~~~~~~~if~(\$env\{'browser.mathml'\})~\{ ~~~~~~~~~~~~\$r->content\_type('text/xml'); ~~~~~~~~\}~else~\{ ~~~~~~~~~~~~\$r->content\_type('text/html'); ~~~~~~~~\} ~~~~~~~~\$r->send\_http\_header; ~~~~~~~~return~OK; ~~~~\} ~~~~\#~Send~header,~don't~cache~this~page ~~~~if~(\$env\{'browser.mathml'\})~\{ ~~~~~~~~\$r->content\_type('text/xml'); ~~~~\}~else~\{ ~~~~~~~~\$r->content\_type('text/html'); ~~~~\} ~~~~\&Apache::loncommon::no\_cache(\$r); ~~~~\$r->send\_http\_header; ~~~~my~\$checkboxCode~=~sub~\{~my~\$resource~=~shift;~ ~~~~~~return~'symb())~.~'\char`\"{}~/>'; ~~~~~~~~~~~~~~~~~~~~~~~~~\}; ~~~~\$r->print('Tutorial~Page'); ~~~~\$r->print(\&Apache::loncommon::bodytag(\&mt('Tutorial~Handler'),'','')); ~~~~if~(\$env\{'form.submission'\})~\{ ~~~~~~~~\$r->print('

'~.~\&mt('Form~submitted.')~.'

'); ~~~~\}~else~\{ ~~~~~~~~\$r->print('

'~.~\&mt('No~form~information~submitted.')~.~'

'); ~~~~\} ~~~~my~\%countHash~=~Apache::lonnet::restore('tutorial'); ~~~~my~\%newHash~=~(); ~~~~foreach~(keys~\%env)~\{ ~~~~~~~~if~(substr(\$\_,~0,~10)~eq~'form.symb.')~\{ ~~~~~~~~~~~~my~\$symb~=~substr(\$\_,~10); ~~~~~~~~~~~~\$symb~=~Apache::lonnet::unescape(\$symb); ~~~~~~~~~~~~\$countHash\{\$symb\}++; ~~~~~~~~~~~~\$newHash\{\$symb\}~=~\$countHash\{\$symb\}; ~~~~~~~~\} ~~~~\} ~~~~my~\$countCode~=~sub~\{~my~\$resource~=~shift; ~~~~~~return~''~. ~~~~~~~~\$countHash\{\$resource->symb()\}~. ~~~~~~~~\char`\"{}\char`\"{}; ~~~~~~\}; ~~~~\$r->print(\char`\"{}\char`\"{}); ~~~~\$env\{'form.condition'\}~=~1; ~~~~my~\$renderArgs~=~\{~'cols'~=>~{[}Apache::lonnavmaps::resource, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\$checkboxCode,~\$countCode{]}, ~~~~~~~~~~~~~~~~~~~~~~~'showParts'~=>~0, ~~~~~~~~~~~~~~~~~~~~~~~'r'~=>~\$r~\}; ~~~~Apache::lonnavmaps::render(\$renderArgs); ~~~~\$r->print(''); ~~~~\$r->print(''); ~~~~Apache::lonnet::store(\textbackslash{}\%newHash,~'tutorial'); ~~~~return~OK; \} 1; \end{lyxcode}