root/modules/branches/2.7/music/page.music.php

Revision 8237, 20.4 kB (checked in by p_lindheimer, 4 years ago)

fixes #3436 create reserved directory with blank music file, use of /dev/null no longer works

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 <?php /* $Id$ */
2 //Copyright (C) 2004 Coalescent Systems Inc. (info@coalescentsystems.ca)
3 //
4 //This program is free software; you can redistribute it and/or
5 //modify it under the terms of the GNU General Public License
6 //as published by the Free Software Foundation; either version 2
7 //of the License, or (at your option) any later version.
8 //
9 //This program is distributed in the hope that it will be useful,
10 //but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //GNU General Public License for more details.
13 ?>
14
15 <?php
16 $tabindex = 0;
17 $action = isset($_REQUEST['action'])?$_REQUEST['action']:'';
18 $randon = isset($_REQUEST['randon'])?$_REQUEST['randon']:'';
19 $randoff = isset($_REQUEST['randoff'])?$_REQUEST['randoff']:'';
20 $category = strtr(isset($_REQUEST['category'])?$_REQUEST['category']:''," ./\"\'\`", "------");
21 if ($category == null) $category = 'default';
22 $display='music';
23
24 global $amp_conf;
25
26 if ($category == "default") {
27     $path_to_dir = $amp_conf['ASTVARLIBDIR']."/mohmp3"; //path to directory u want to read.
28 } else {
29     $path_to_dir = $amp_conf['ASTVARLIBDIR']."/mohmp3/$category"; //path to directory u want to read.
30 }
31
32
33 if (strlen($randon)) {
34     touch($path_to_dir."/.random");
35     createmusicconf();
36     needreload();
37 }
38 if (strlen($randoff)) {
39     unlink($path_to_dir."/.random");
40     createmusicconf();
41     needreload();
42 }
43 switch ($action) {
44     case "addednewstream":
45     case "editednewstream":
46         $stream = isset($_REQUEST['stream'])?$_REQUEST['stream']:'';
47         $format = isset($_REQUEST['format'])?trim($_REQUEST['format']):'';
48         if ($format != "") {
49             $stream .= "\nformat=$format";
50         }
51         makestreamcatergory($path_to_dir,$stream);
52         createmusicconf();
53         needreload();
54         redirect_standard();
55     case "addednew":
56         makemusiccategory($path_to_dir);
57         createmusicconf();
58         needreload();
59         redirect_standard();
60     break;
61     case "addedfile":
62         createmusicconf();
63         needreload();
64 //        redirect_standard();
65     break;
66     case "delete":
67         //$fh = fopen("/tmp/music.log","a");
68         //fwrite($fh,print_r($_REQUEST,true));
69         music_rmdirr("$path_to_dir");
70         $path_to_dir = $amp_conf['ASTVARLIBDIR']."/mohmp3"; //path to directory u want to read.
71         $category='default';
72         createmusicconf();
73         needreload();
74         redirect_standard();
75     break;
76 }
77
78
79 ?>
80 </div>
81 <div class="rnav"><ul>
82     <li><a href="config.php?display=<?php echo urlencode($display)?>&action=add"><?php echo _("Add Music Category")?></a></li>
83     <li><a href="config.php?display=<?php echo urlencode($display)?>&action=addstream"><?php echo _("Add Streaming Category")?></a></li>
84
85 <?php
86 //get existing trunk info
87 $tresults = music_list($amp_conf['ASTVARLIBDIR']."/mohmp3");
88 if (isset($tresults)) {
89     foreach ($tresults as $tresult) {
90         if ($tresult != "none") {
91             ( $tresult == 'default' ? $ttext = _("default") : $ttext = $tresult );
92             echo "<li><a id=\"".($category==$tresult ? 'current':'')."\" href=\"config.php?display=".urlencode($display)."&category=".urlencode($tresult)."&action=edit\">{$ttext}</a></li>";
93         }
94     }
95 }
96 ?>
97 </ul></div>
98
99
100 <?php
101 function createmusicconf() {
102     global $amp_conf;
103
104     $File_Write="";
105     $tresults = music_list($amp_conf['ASTVARLIBDIR']."/mohmp3");
106     if (isset($tresults)) {
107         foreach ($tresults as $tresult)  {
108             // hack - but his is all a hack until redone, in functions, etc.
109             // this puts a none category to allow no music to be chosen
110             //
111             if ($tresult == "none") {
112           $dir = $amp_conf['ASTVARLIBDIR']."/mohmp3/.nomusic_reserved";
113           if (!is_dir($dir)) {
114           makemusiccategory($dir);
115         }
116         touch($dir."/silence.wav");
117             } elseif ($tresult != "default" ) {
118                 $dir = $amp_conf['ASTVARLIBDIR']."/mohmp3/{$tresult}/";
119             } else {
120                 $dir = $amp_conf['ASTVARLIBDIR']."/mohmp3/";
121             }
122             if (file_exists("{$dir}.custom")) {
123                 $application = file_get_contents("{$dir}.custom");
124                 $File_Write.="[{$tresult}]\nmode=custom\napplication=$application\n";
125             } else if (file_exists("{$dir}.random")) {
126                 $File_Write.="[{$tresult}]\nmode=files\ndirectory={$dir}\nrandom=yes\n";
127             } else {
128                 $File_Write.="[{$tresult}]\nmode=files\ndirectory={$dir}\n";
129             }
130         }
131     }
132
133
134     $handle = fopen($amp_conf['ASTETCDIR']."/musiconhold_additional.conf", "w");
135
136     if (fwrite($handle, $File_Write) === FALSE) {
137         echo _("Cannot write to file")." ($tmpfname)";
138         exit;
139     }
140
141     fclose($handle);
142 }
143
144 function makestreamcatergory($path_to_dir,$stream) {
145     if (!is_dir($path_to_dir)) {
146         makemusiccategory($path_to_dir);
147     }
148     $fh=fopen("$path_to_dir/.custom","w");
149     fwrite($fh,$stream);
150     fclose($fh);
151 }
152
153 function makemusiccategory($path_to_dir) {
154     mkdir("$path_to_dir", 0755);
155 }
156  
157 function build_list() {
158     global $path_to_dir;
159     $pattern = '';
160     $handle=opendir($path_to_dir) ;
161     $extensions = array('mp3','MP3','wav','WAV'); // list of extensions to match
162     
163     //generate the pattern to look for.
164     $pattern = '/(\.'.implode('|\.',$extensions).')$/i';
165     
166     //store file names that match pattern in an array
167     $i = 0;
168     while (($file = readdir($handle))!==false) {
169         if ($file != "." && $file != "..") {
170             if(preg_match($pattern,$file)) {
171                 $file_array[$i] = $file; //pattern is matched store it in file_array.
172                 $i++;       
173             }
174         }
175     }
176     closedir($handle);
177     
178     return (isset($file_array))?$file_array:null//return the size of the array
179 }
180
181 function draw_list($file_array, $path_to_dir, $category) {
182     global $display;
183     //list existing mp3s and provide delete buttons
184     if ($file_array) {
185         foreach ($file_array as $thisfile) {
186             print "<div style=\"text-align:right;width:550px;border: 1px solid;padding:2px;\">";
187             print "<b style=\"float:left;margin-left:5px;\" >".$thisfile."</b>";
188
189             $delURL = $_SERVER['SCRIPT_NAME']."?display=".(isset($display)?$display:'')."&del=".urlencode($thisfile)."&category=".$category;
190             $tlabel = _("Delete");
191             $label = '<span><img width="16" height="16" border="0" title="'.$tlabel.'" alt="'.$tlabel.'" src="images/core_delete.png"/>&nbsp;</span>';
192             echo "<a style=\"margin-right:5px;\" href=".$delURL.">".$label."</a></div><br />";
193         }
194     }
195 }
196
197 function process_mohfile($mohfile,$onlywav=false,$volume=false) {
198     global $path_to_dir;
199     global $amp_conf;
200
201     $output = 0;
202     $returncode = 0;
203     $origmohfile=$path_to_dir."/orig_".$mohfile;
204     if ($amp_conf['AMPMPG123']) {
205         if($onlywav) {
206             $newname = substr($mohfile,0,strrpos($mohfile,"."));
207
208             // If we are dealing with an MP3, we need to decode it to a wav file. mpg123 -w writes the converted output to $origmohfile.wav
209             if (strtoupper(substr($origmohfile,-4)) == '.MP3') {
210                 $mpg123cmd = "mpg123 -w \"".substr($origmohfile,0,strrpos($origmohfile,".")).".wav\" \"".$origmohfile."\" 2>&1 ";
211                 exec($mpg123cmd, $output, $returncode);
212             }
213             $newmohfile = $path_to_dir."/wav_".$newname.".wav";
214             //We need to take the output of mpg123 to use in the sox conversion. If we used $origmohfile directly then we would be bypassing mpg123. The mpg123 might not be needed on some systems if we had the sox version with mp3 compiled in. The standard rpmforge sox rpm does not have mp3 included.
215             //$soxcmd = "sox \"".$origmohfile."\"";
216             $soxcmd = "sox \"".substr($origmohfile,0,strrpos($origmohfile,".")).".wav\"";
217             $soxcmd .= " -r 8000 -c 1 \"".$newmohfile."\"";
218             if($volume){
219                 $soxcmd .= " vol ".$volume;
220             }
221             $soxresample = " resample -ql ";
222             exec($soxcmd.$soxresample."2>&1", $output, $returncode);
223             if ($returncode != 0) {
224                 // try it again without the resample in case the input sample rate was the same
225                 //
226                 exec("rm -rf \"".$newmohfile."\"");
227                 exec($soxcmd."2>&1", $output, $returncode);
228             }
229         }
230     } else { // AMPMPG123
231         $newname = strtr($mohfile,"&", "_");
232         if(strstr($newname,".mp3")) {
233             $onlywav = false;
234         }
235
236         if(!$onlywav) {
237             $newmohfile=$path_to_dir."/". ((strpos($newname,'.mp3') === false) ? $newname.".mp3" : $newname);
238             $lamecmd="lame --cbr -m m -t -F \"".$origmohfile."\" \"".$newmohfile."\" 2>&1 ";
239             if (strpos($newmohfile,'.mp3') !== false) {
240                 exec($lamecmd, $output, $returncode);
241             }
242         } else {
243             $newmohfile = $path_to_dir."/wav_".$newname;
244             $soxcmd = "sox \"".$origmohfile."\" -r 8000 -c 1 \"".$newmohfile."\" ";
245             $soxresample = "resample -ql ";
246             exec($soxcmd.$soxresample."2>&1", $output, $returncode);                                                     
247             if ($returncode != 0) {                                                                                       
248                 // try it again without the resample in case the input sample rate was the same                             
249                 //                                                                                                         
250                 exec("rm -rf \"".$newmohfile."\"");                                                                         
251                 exec($soxcmd."2>&1", $output, $returncode);                                                                 
252             }
253         }
254     } // AMPMPG123
255
256     if ($returncode != 0) {
257         return join("<br>\n", $output);
258     }
259     $rmcmd="rm -f \"". $origmohfile."\"";
260     exec($rmcmd);
261     if ($amp_conf['AMPMPG123']) {
262         // If this started as an mp3, we converted it to a wav and then transcoded it from there,
263         // so we have two "original" files to delete
264         //
265         if (strpos($origmohfile,'.mp3') | strpos($origmohfile,'.MP3') !== false)  {
266             $rmcmd="rm -f \"". substr($origmohfile,0,strrpos($origmohfile,".")).".wav\"";
267             exec($rmcmd);
268         }
269     } // AMPMPG123
270     return null;
271 }
272
273 ?>
274
275 <div class="content">
276 <h2><?php echo _("On Hold Music")?></h2>
277
278 <?php
279 if ($action == 'add') {
280     ?>
281     <form name="addcategory" action="<?php $_SERVER['PHP_SELF'] ?>" method="post" onsubmit="return addcategory_onsubmit();">
282     <input type="hidden" name="display" value="<?php echo $display?>">
283     <input type="hidden" name="action" value="addednew">
284     <table>
285     <tr><td colspan="2"><h5><?php echo _("Add Music Category")?><hr></h5></td></tr>
286     <tr>
287         <td><a href="#" class="info"><?php echo _("Category Name:")?><span><?php echo _("Allows you to Set up Different Categories for music on hold.  This is useful if you would like to specify different Hold Music or Commercials for various ACD Queues.")?> </span></a></td>
288         <td><input type="text" name="category" value=""></td>
289     </tr>
290     <tr>
291         <td colspan="2"><br><h6><input name="Submit" type="submit" value='<?php echo _("Submit Changes")?>' ></h6></td>       
292     </tr>
293     </table>
294 <script language="javascript">
295 <!--
296
297 var theForm = document.addcategory;
298 theForm.category.focus();
299
300 function addcategory_onsubmit() {
301     var msgInvalidCategoryName = "<?php echo _('Please enter a valid Category Name'); ?>";
302     var msgReservedCategoryName = "<?php echo _('Categories: \"none\" and \"default\" are reserved names. Please enter a different name'); ?>";
303
304     defaultEmptyOK = false;
305     if (!isAlphanumeric(theForm.category.value))
306         return warnInvalid(theForm.category, msgInvalidCategoryName);
307     if (theForm.category.value == "default" || theForm.category.value == "none" || theForm.category.value == ".nomusic_reserved")
308         return warnInvalid(theForm.category, msgReservedCategoryName);
309    
310     return true;
311 }
312
313 //-->
314 </script>
315
316     </form>
317     <br><br><br><br><br>
318
319 <?php
320     } else if ($action == 'addstream') {
321     ?>
322     <form name="addstream" action="<?php $_SERVER['PHP_SELF'] ?>" method="post" onsubmit="return addstream_onsubmit();">
323     <input type="hidden" name="display" value="<?php echo $display?>">
324     <input type="hidden" name="action" value="addednewstream">
325     <table>
326     <tr><td colspan="2"><h5><?php echo _("Add Streaming Category")?><hr></h5></td></tr>
327     <tr>
328         <td><a href="#" class="info"><?php echo _("Category Name:")?><span><?php echo _("Allows you to Set up Different Categories for music on hold.  This is useful if you would like to specify different Hold Music or Commercials for various ACD Queues.")?> </span></a></td>
329         <td><input type="text" name="category" value=""></td>
330     </tr>
331     <tr>
332         <td><a href="#" class="info"><?php echo _("Application:")?><span><?php echo _("This is the \"application=\" line used to provide the streaming details to Asterisk. See information on musiconhold.conf configuration for different audio and internet streaming source options.")?> </span></a></td>
333         <td><input type="text" name="stream" size="80" value=""></td>
334     </tr>
335     <tr>
336     <tr>
337         <td><a href="#" class="info"><?php echo _("Optional Format:")?><span><?php echo _("Optional value for \"format=\" line used to provide the format to Asterisk. This should be a format understood by Asterisk such as ulaw, and is specific to the streaming application you are using. See information on musiconhold.conf configuration for different audio and internet streaming source options.")?> </span></a></td>
338         <td><input type="text" name="format" size="6" value=""></td>
339     </tr>
340     <tr>
341         <td colspan="2"><br><h6><input name="Submit" type="submit" value='<?php echo _("Submit Changes")?>' ></h6></td>       
342     </tr>
343     </table>
344 <script language="javascript">
345 <!--
346
347 var theForm = document.addstream;
348 theForm.category.focus();
349
350 function addstream_onsubmit() {
351     var msgInvalidCategoryName = "<?php echo _('Please enter a valid Category Name'); ?>";
352     var msgInvalidStreamName = "<?php echo _('Please enter a streaming application command and arguments'); ?>";
353     var msgReservedCategoryName = "<?php echo _('Categories: \"none\" and \"default\" are reserved names. Please enter a different name'); ?>";
354
355     defaultEmptyOK = false;
356     if (!isAlphanumeric(theForm.category.value))
357         return warnInvalid(theForm.category, msgInvalidCategoryName);
358     if (theForm.category.value == "default" || theForm.category.value == "none" || theForm.category.value == ".nomusic_reserved")
359         return warnInvalid(theForm.category, msgReservedCategoryName);
360     if (isEmpty(theForm.stream.value))
361         return warnInvalid(theForm.stream, msgInvalidStreamName);
362    
363     return true;
364 }
365
366 //-->
367 </script>
368
369     </form>
370     <br><br><br><br><br>
371
372 <?php
373 } else {
374 ?>
375
376     <h5><?php echo _("Category:")?> <?php echo $category=="default"?_("default"):$category;?></h5>
377 <?php 
378     if (file_exists("{$path_to_dir}/.custom")) {
379         $application = file_get_contents("{$path_to_dir}/.custom");
380         $application = explode("\n",$application);
381         if (isset($application[1])) {
382             $format = explode('=',$application[1],2);
383             $format = $format[1];
384         } else {
385             $format = "";
386         }
387     } else {
388         $application = false;
389     }
390     if ($category!="default") {
391         $delURL = $_SERVER['PHP_SELF'].'?display='.urlencode($display).'&action=delete&category='.urlencode($category);
392         $tlabel = sprintf(($application === false)?_("Delete Music Category %s"):_("Delete Streaming Category"),$category);
393         $label = '<span><img width="16" height="16" border="0" title="'.$tlabel.'" alt="" src="images/core_delete.png"/>&nbsp;'.$tlabel.'</span>';
394 ?>
395         <p><a href="<?php echo $delURL ?>"><?php echo $label; ?></a></p>
396 <?php 
397     }
398     if ($application !== false) {
399     ?>
400         <form name="editstream" action="<?php $_SERVER['PHP_SELF'] ?>" method="post" onsubmit="return editstream_onsubmit();">
401         <input type="hidden" name="display" value="<?php echo $display?>">
402         <input type="hidden" name="action" value="editednewstream">
403         <table>
404         <tr><td colspan="2"><h5><?php echo _("Edit Streaming Category").": $category"?><hr></h5></td></tr>
405         <tr>
406             <td><a href="#" class="info"><?php echo _("Application:")?><span><?php echo _("This is the \"application=\" line used to provide the streaming details to Asterisk. See information on musiconhold.conf configuration for different audio and internet streaming source options.")?> </span></a></td>
407             <td><input type="text" name="stream" size="80" value="<?php echo $application[0]?>"></td>
408         </tr>
409         <tr>
410             <td><a href="#" class="info"><?php echo _("Optional Format:")?><span><?php echo _("Optional value for \"format=\" line used to provide the format to Asterisk. This should be a format understood by Asterisk such as ulaw, and is specific to the streaming application you are using. See information on musiconhold.conf configuration for different audio and internet streaming source options.")?> </span></a></td>
411             <td><input type="text" name="format" size="6" value="<?php echo $format?>"></td>
412         </tr>
413         <tr>
414             <td colspan="2"><br><h6><input name="Submit" type="submit" value='<?php echo _("Submit Changes")?>' ></h6></td>       
415         </tr>
416         </table>
417 <script language="javascript">
418 <!--
419
420 var theForm = document.editstream;
421 theForm.stream.focus();
422
423 function editstream_onsubmit() {
424     var msgInvalidStreamName = "<?php echo _('Please enter a streaming application command and arguments'); ?>";
425
426     defaultEmptyOK = false;
427     if (isEmpty(theForm.stream.value))
428         return warnInvalid(theForm.stream, msgInvalidStreamName);
429    
430     return true;
431 }
432 //-->
433 </script>
434
435         </form>
436         <br><br><br><br><br>
437
438 <?php
439     } else { // normal moh dir
440 ?>
441
442     <form enctype="multipart/form-data" name="upload" action="<?php echo $_SERVER['PHP_SELF'] ?>" method="POST"/>
443         <?php echo _("Upload a .wav or .mp3 file:")?><br>
444         <input type="hidden" name="display" value="<?php echo $display?>">
445         <input type="hidden" name="category" value="<?php echo "$category" ?>">
446         <input type="hidden" name="action" value="addedfile">
447         <input type="file" name="mohfile" tabindex="<?php echo ++$tabindex;?>"/>
448         <input type="button" value="<?php echo _("Upload")?>" onclick="document.upload.submit(upload);alert('<?php echo addslashes(_("Please wait until the page loads. Your file is being processed."))?>');" tabindex="<?php echo ++$tabindex;?>"/>
449         <br />
450 <?php
451     if ($amp_conf['AMPMPG123']) {
452 ?>
453         <select name="volume" tabindex="<?php echo ++$tabindex;?>">
454             <option value="1.50">Volume 150%</option>
455             <option value="1.25">Volume 125%</option>
456             <option value="" selected>Volume 100%</option>
457             <option value=".75">Volume 75%</option>
458             <option value=".5">Volume 50%</option>
459             <option value=".25">Volume 25%</option>
460             <option value=".1">Volume 10%</option>
461         </select>
462         <a href="#" class="info"><?php echo "&nbsp;"._("Volume Adjustment")?><span> <?php echo _("The volume adjustment is a linear value. Since loudness is logarithmic, the linear level will be less of an adjustment. You should test out the installed music to assure it is at the correct volume. This feature will convert MP3 files to WAV files. If you do not have mpg123 installed, you can set the parameter: <strong>AMPMPG123=false</strong> in your amportal.conf file") ?></span></a>
463 <?php
464     } else { // AMPMPG123
465 ?>
466         <input type="checkbox" name="onlywav" checked="checked"><small><?php echo _("Do not encode wav to mp3"); ?></small>
467 <?php
468     } // AMPMPG123
469 ?>
470     </form>
471     <br />
472     <form name="randomon" action="<?php $_SERVER['PHP_SELF'] ?>" method="post">
473     <?php
474         if (file_exists("{$path_to_dir}/.random")) {
475             ?> <input type="submit" name="randoff" value="<?php echo _("Disable Random Play");?>"> <?php
476         } else {
477             ?> <input type="submit" name="randon" value="<?php echo _("Enable Random Play");?>"> <?php
478         }
479     ?>
480     </form>
481     <br />
482     <?php
483
484     // Check to see if the upload failed for some reason
485     if (isset($_FILES['mohfile']['name']) && !is_uploaded_file($_FILES['mohfile']['tmp_name'])) {
486         if (strlen($_FILES['mohfile']['name']) == 0) {
487             echo "<h5> PHP "._("Error Processing")."! "._("No file provided")." "._("Please select a file to upload")."</h5>";
488         } else {
489             echo "<h5> PHP "._("Error Processing")." ".$_FILES['mohfile']['name']."! "._("Check")." upload_max_filesize "._("in")." /etc/php.ini</h5>";
490         }
491     }
492     if (isset($_FILES['mohfile']['tmp_name']) && is_uploaded_file($_FILES['mohfile']['tmp_name'])) {
493         //echo $_FILES['mohfile']['name']." uploaded OK";
494         move_uploaded_file($_FILES['mohfile']['tmp_name'], $path_to_dir."/orig_".$_FILES['mohfile']['name']);
495
496         if ($amp_conf['AMPMPG123']) {
497             $process_err = process_mohfile($_FILES['mohfile']['name'],true,$_REQUEST['volume']);
498         } else {
499             $process_err = process_mohfile($_FILES['mohfile']['name'],($_REQUEST['onlywav'] != ''));
500         }
501
502         if (isset($process_err)) {
503             echo "<h5>"._("Error Processing").": \"$process_err\" for ".$_FILES['mohfile']['name']."!</h5>\n";
504             echo "<h5>"._("This is not a fatal error, your Music on Hold may still work.")."</h5>\n";
505         } else {
506             echo "<h5>"._("Completed processing")." ".$_FILES['mohfile']['name']."!</h5>";
507         }
508         needreload();
509     }
510
511     //build the array of files
512     $file_array = build_list();
513     $numf = count($file_array);
514     } // normal moh dir
515
516
517     if (isset($_REQUEST['del'])) {
518         $del = $_REQUEST['del'];
519         if (strpos($del, "\"") || strpos($del, "\'") || strpos($del, "\;")) {
520             print "You're trying to use an invalid character. Please don't.\n";
521             exit;
522         }
523         if (($numf == 1) && ($category == "default") ){
524             echo "<h5>"._("You must have at least one file for On Hold Music.  Please upload one before deleting this one.")."</h5>";
525         } else {
526             if (@unlink($path_to_dir."/".$del)) {
527                 echo "<h5>"._("Deleted")." ".$del."!</h5>";
528             } else {
529                 echo "<h5>".sprintf(_("Error Deleting %s"),$del)."!</h5>";
530             }
531             //kill_mpg123();
532             needreload();
533         }
534     }
535     if ($application === false) {
536         $file_array = build_list();
537         draw_list($file_array, $path_to_dir, $category);
538     }
539     ?>
540     <br><br><br><br><br><br>
541 <?php
542 }
543 ?>
544
545
Note: See TracBrowser for help on using the browser.