3 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
4 * Copyright (C) 2002-2006 The Nucleus Group
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 * (see nucleus/documentation/index.html#license for more info)
13 * @license http://nucleuscms.org/license.txt GNU General Public License
14 * @copyright Copyright (C) 2002-2006 The Nucleus Group
15 * @version $Id: functions.inc.php 1624 2012-01-09 11:36:20Z sakamocchi $
18 // try to set a long timeout time
19 @set_time_limit(1200);
22 * Generic class that can import XML files with either blog items or comments
23 * to be imported into a Nucleus blog
29 * Creates a new BlogImport object
33 * Nucleus blogid to which the content of the XML file must be added
36 * $aOptions['PreserveIds'] = 1 (NOT IMPLEMENTED)
37 * try to use the same ID for the nucleus item as the ID listed
39 * $aOptions['ReadNamesOnly']
40 * Reads all category names and author names (items
41 * only) into $aAuthorNames and $aCategoryNames
42 * @param aMapUserToNucleusId
43 * Array with mapping from user names (as listed in the XML file) to
44 * Nucleus member Ids. '_default' lists the default user.
45 * example: array('karma' => 1, 'xiffy' => 2, 'roel' => 3, '_default' => 1)
46 * @param aMapCategoryToNucleusId
47 * Similar to $aMapUserToNucleusId, but this array maps category names to
48 * category ids. Please note that the category IDs need to come from the
49 * same blog as $iBlogId
50 * example: array('general' => 11, 'funny' => 33)
52 * name of a callback function to be called on each item. Such a callback
53 * function should have a format like:
54 * function myCallback(&$data)
55 * where $data is an associative array with all item data ('title','body',
58 function BlogImport($iBlogId = -1, $aOptions = array('ReadNamesOnly' => 0), $aMapUserToNucleusId = array(), $aMapCategoryToNucleusId = array(), $strCallback = '') {
61 $this->iBlog = $iBlogId;
63 $this->oBlog =& $manager->getBlog($iBlogId);
66 $this->aOptions = $aOptions;
67 $this->aMapUserToNucleusId = $aMapUserToNucleusId;
68 $this->aMapCategoryToNucleusId = $aMapCategoryToNucleusId;
69 $this->strCallback = $strCallback;
70 $this->aMapIdToNucleusId = array();
72 $this->bReadNamesOnly = $this->aOptions['ReadNamesOnly'] == 1;
73 $this->aCategoryNames = array();
74 $this->aAuthorNames = array();
77 // disable magic_quotes_runtime if it's turned on
78 set_magic_quotes_runtime(0);
86 // to maintain track of where we are inside the XML file
91 $this->aCurrentItem = $this->_blankItem();
92 $this->aCurrentComment = $this->_blankComment();
94 // character data pile
98 $this->parser = xml_parser_create();
99 xml_set_object($this->parser, $this);
100 xml_set_element_handler($this->parser, 'startElement', 'endElement');
101 xml_set_character_data_handler($this->parser, 'characterData');
102 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
104 // TODO: add data checking
108 $this->strErrorMessage = '';
113 * Gets the import library version
115 function getVersion() {
120 * Returns an array with all the author names used in the file (only
121 * the authors of items are included)
123 * @require importXmlFile should be called prior to calling this
125 function getAuthorNames() {
126 return $this->aAuthorNames;
130 * Returns an array with all the category names used in the file
132 * @require importXmlFile should be called prior to calling this
134 function getCategoryNames() {
135 return $this->aCategoryNames;
139 * Imports an XML file into a given blog
141 * also fills $this->aMapIdToNucleusId
142 * array with info for each item having a Nucleus ID that is different
143 * from the original ID
144 * example: array(9999 => 1, 1234 => 2, 12 => 3)
147 * Location of the XML file. The XML file must be in the correct
148 * Nucleus import format
150 * 0 on failure. Use getLastError() to get error message
154 function importXmlFile($strXmlFile) {
155 $this->resetErrorMessage();
160 return $this->setErrorMessage('BlogImport object is invalid');
161 if (!@file_exists($strXmlFile))
162 return $this->setErrorMessage($strXmlFile . ' does not exist');
163 if (!@is_readable($strXmlFile))
164 return $this->setErrorMessage($strXmlFile . ' is not readable');
167 $this->fp = @fopen($strXmlFile, 'r');
169 return $this->setErrorMessage('Failed to open file/URL');
174 // parse file contents
175 while ($buffer = fread($this->fp, 4096)) {
176 $err = xml_parse( $this->parser, $buffer, feof($this->fp) );
177 if (!$err && $this->bDebug)
178 echo 'ERROR: ', xml_error_string(xml_get_error_code($this->parser)), '<br />';
189 * Identical to importXmlFile, but takes an almost-ready-for-addition array
192 * Array with item data, as prepared by import_fromXML
195 * 0 on failure. Use getLastError() to get error message
198 function importOneItem(&$aData) {
199 $this->resetErrorMessage();
201 // - do some logic to determine nucleus users and categories
204 // * find category id
205 $aData['nucleus_blogid'] = $this->iBlog;
206 $aData['nucleus_catid'] = $this->_findCategoryId($aData['category']);
207 $aData['nucleus_memberid'] = $this->_findMemberId($aData['author']);
208 if ($aData['nucleus_memberid'] == 0) {
209 $aData['nucleus_memberid'] = $this->aMapUserToNucleusId['_default'];
212 // - apply logic to comments
213 foreach (array_keys($aData['comments']) as $key) {
215 $aData['comments'][$key]['nucleus_memberid']
216 = $this->_findMemberId($aData['comments'][$key]['author']);
217 // * extract authorid
218 if ($aData['comments'][$key]['nucleus_memberid'] == 0) {
219 $url = $aData['comments'][$key]['url'];
220 $email = $aData['comments'][$key]['email'];
221 $authid = $aData['comments'][$key]['authorid'];
223 if (!$authid && $url)
224 $aData['comments'][$key]['authorid'] = $url;
225 else if (!$authid && $email)
226 $aData['comments'][$key]['authorid'] = $email;
231 if ($this->strCallback && function_exists($this->strCallback)) {
232 call_user_func_array($this->strCallback, array(&$aData));
241 // - insert item into nucleus database
242 $iNewId = $this->sql_addToItem(
246 $aData['nucleus_blogid'],
247 $aData['nucleus_memberid'],
249 ($aData['itemstatus'] == 'open') ? 0 : 1,
250 $aData['nucleus_catid'],
255 // - store id mapping if needed
256 $aData['nucleus_id'] = $iNewId;
257 if ($aData['nucleus_id'] != $aData['id'])
258 $this->aMapIdToNucleusId[$aData['id']] = $aData['nucleus_id'];
260 // - insert comments into nucleus database
261 foreach ($aData['comments'] as $comment) {
262 $cId = $this->sql_addToComments(
264 $comment['authorid'],
266 $aData['nucleus_blogid'],
267 $aData['nucleus_id'],
268 $comment['nucleus_memberid'],
269 $comment['timestamp'],
275 echo ' .'; // progress indicator
281 function getHtmlCode($what) {
284 // ----------------------------------------------------------------------------------------
285 case 'NucleusMemberOptions':
286 $res = sql_query('SELECT mname as text, mnumber as value FROM '.sql_table('member'));
287 while ($o = mysql_fetch_object($res)) {
288 echo '<option value="'.i18n::hsc($o->value).'">'.i18n::hsc($o->text).'</option>';
291 // ----------------------------------------------------------------------------------------
292 case 'NucleusBlogSelect':
293 $query = 'SELECT bname as text, bnumber as value FROM '.sql_table('blog');
294 $template['name'] = 'blogid';
295 $template['selected'] = $CONF['DefaultBlog'];
296 showlist($query,'select',$template);
298 // ----------------------------------------------------------------------------------------
299 case 'ConvertSelectMembers':
301 <h2>Assign Members to Authors</h2>
304 Below is a list of all the authors that Nucleus could discover (only authors that have posted at least one entry are listed). Please assign a Nucleus Member to all of these authors.
311 <th>Nucleus Member</th>
317 $authors = $this->getAuthorNames();
319 // get HTML code for selection list
320 $optionsHtml = $this->getHtmlCode('NucleusMemberOptions');
322 while ($a_name = array_pop($authors)) {
326 <strong><?php echo $a_name?></strong>
327 <input name="author[<?php echo $idx?>]" value="<?php echo i18n::hsc($a_name)?>" type="hidden"
330 <select name="memberid[<?php echo $idx?>]">
331 <?php echo $optionsHtml; ?>
335 <input name="admin[<?php echo $idx?>]" type="checkbox" value="1" id="admin<?php echo $idx?>" /><label for="admin<?php echo $idx?>">Blog Admin</label>
343 <td><em>Default Member</em></td>
345 <input name="author[<?php echo $idx?>]" value="_default" type="hidden"
346 <select name="memberid[<?php echo $idx?>]">
347 <?php echo $optionsHtml; ?>
349 <td><input name="admin[<?php echo $idx?>]" type="hidden" value="0" id="admin<?php echo $idx?>" /></td>
354 <input type="hidden" name="authorcount" value="<?php echo ++$idx?>" />
357 // ----------------------------------------------------------------------------------------
358 case 'ConvertSelectCategories':
360 <h2>Assign Categories</h2>
363 Below is a list of all the categories that Nucleus could discover (only categories that have been used at least once are listed). Please assign a Nucleus Category to all of these categories.
370 <th>Nucleus Category</th>
375 $catnames = $this->getCategoryNames();
377 // get HTML code for selection list
378 $optionsHtml = $this->getHtmlCode('NucleusCategoryOptions');
380 while ($a_name = array_pop($catnames)) {
384 <strong><?php echo $a_name?></strong>
385 <input name="category[<?php echo $idx?>]" value="<?php echo i18n::hsc($a_name)?>" type="hidden"
388 <select name="catid[<?php echo $idx?>]">
389 <?php echo $optionsHtml; ?>
397 <input type="hidden" name="catcount" value="<?php echo $idx?>" />
400 // ----------------------------------------------------------------------------------------
401 case 'ConvertSelectBlog':
403 <h2>Choose Destination Weblog</h2>
406 There are two options: you can either choose an existing blog to add the entries into, or you can choose to create a new weblog.
410 <input name="createnew" value="0" type="radio" checked='checked' id="createnew_no" /><label for="createnew_no">Choose existing weblog to add to:</label>
412 <?php echo $this->getHtmlCode('NucleusBlogSelect'); ?>
415 <input name="createnew" value="1" type="radio" id="createnew_yes" /><label for="createnew_yes">Create new weblog</label>
417 <li>New blog name: <input name="newblogname" /></li>
419 <select name="newowner">
420 <?php echo $this->getHtmlCode('NucleusMemberOptions'); ?>
427 // ----------------------------------------------------------------------------------------
430 <h2>Do the conversion!</h2>
433 <input type="submit" value="Step 3: Do the conversion!" />
434 <input type="hidden" name="action" value="doConversion" />
438 <strong>Note:</strong> Clicking the button once is enough, even if it takes a while to complete.
443 $htmlCode = ob_get_contents();
449 * Create blog if needed
450 * (request vars: blogid, createnew, newblogname, newowner)
454 function getBlogIdFromRequest() {
455 $createnew = intPostVar('createnew');
456 $newowner = intPostVar('newowner');
457 $newblogname = postVar('newblogname');
458 $blogid = intPostVar('blogid');
460 if ($createnew == 1) {
461 // choose unique name
462 $shortname = 'import';
463 if (BLOG::exists($shortname)) {
465 while (BLOG::exists($shortname . $idx))
467 $shortname = $shortname . $idx;
470 $nucleus_blogid = BlogImport::sql_addToBlog($newblogname, $shortname, $newowner);
472 echo '<h2>Creating new blog</h2>';
473 echo '<p>Your new weblog has been created.</p>';
475 return $nucleus_blogid;
482 function getFromRequest($what) {
487 $authorcount = intPostVar('authorcount');
489 $author = requestArray('author');
490 $memberid = requestIntArray('memberid');
491 $isadmin = requestIntArray('admin');
493 for ($i=0;$i<$authorcount;$i++) {
494 $authorname = undoMagic($author[$i]);
496 // add authors to team
497 $this->oBlog->addTeamMember(intval($memberid[$i]),intval($isadmin[$i]));
499 $aResult[$authorname] = $memberid[$i];
502 $this->aMapUserToNucleusId = $aResult;
506 $this->aMapCategoryToNucleusId = $aResult;
513 function _findCategoryId($name) {
514 $catid = @$this->aMapCategoryToNucleusId[$name];
515 if (!$catid && $this->oBlog)
516 // get default category for weblog
517 $catid = $this->oBlog->getDefaultCategory();
521 function _findMemberId($name) {
522 $memberid = intval(@$this->aMapUserToNucleusId[$name]);
527 * Returns the last error message. Use it to find out the reason for failure
529 function getLastError() {
530 return $this->strErrorMessage;
532 function resetErrorMessage() {
533 $this->strErrorMessage = '';
535 function setErrorMessage($strMsg) {
536 $this->strErrorMessage = $strMsg;
541 * Called by XML parser for each new start element encountered
543 function startElement($parser, $name, $attrs) {
544 if ($this->bDebug) echo 'START: ', $name, '<br />';
549 $this->strImportFileVersion = $attrs['version'];
550 // TODO: check version number
554 $this->aCurrentItem = $this->_blankItem($attrs['id']);
555 if (@$attrs['commentsOnly'] == 'true')
556 $this->aCurrentItem['commentsOnly'] = 1;
558 $this->aCurrentItem['commentsOnly'] = 0;
561 if ($this->inItem || $this->inComment) {
563 $this->currentTSFormat = $attrs['type'];
578 $this->inComment = 1;
579 $this->aCurrentComment = $this->_blankComment($attrs['id']);
590 echo 'UNEXPECTED TAG: ' , $name , '<br />';
594 // character data never contains other tags
595 $this->clearCharacterData();
600 * Called by the XML parser for each closing tag encountered
602 function endElement($parser, $name) {
603 if ($this->bDebug) echo 'END: ', $name, '<br />';
610 if (!$this->bReadNamesOnly) {
612 // TODO: check if succes or failure
613 $this->importOneItem($this->aCurrentItem);
617 // initialize item structure
618 $this->aCurrentItem = $this->_blankItem();
621 $timestamp = $this->getTime($this->getCharacterData(), $this->currentTSFormat);
622 if ($this->inComment)
623 $this->aCurrentComment['timestamp'] = $timestamp;
624 else if ($this->inItem)
625 $this->aCurrentItem['timestamp'] = $timestamp;
628 if ($this->inItem && !$this->inComment)
629 $this->_addAuthorName($this->getCharacterData());
630 if ($this->inComment)
631 $this->aCurrentComment['author'] = $this->getCharacterData();
632 else if ($this->inItem)
633 $this->aCurrentItem['author'] = $this->getCharacterData();
636 if ($this->inComment)
637 $this->aCurrentComment['title'] = $this->getCharacterData();
638 else if ($this->inItem)
639 $this->aCurrentItem['title'] = $this->getCharacterData();
642 if ($this->inComment)
643 $this->aCurrentComment['body'] = $this->getCharacterData();
644 else if ($this->inItem)
645 $this->aCurrentItem['body'] = $this->getCharacterData();
649 $this->aCurrentItem['extended'] = $this->getCharacterData();
652 $this->_addCategoryName($this->getCharacterData());
653 if ($this->inItem && !$this->aCurrentItem['category']) {
654 $this->aCurrentItem['category'] = $this->getCharacterData();
659 $this->aCurrentItem['itemstatus'] = $this->getCharacterData();
663 $this->aCurrentItem['posvotes'] = $this->getCharacterData();
667 $this->aCurrentItem['negvotes'] = $this->getCharacterData();
670 if ($this->inComment) {
671 array_push($this->aCurrentItem['comments'], $this->aCurrentComment);
672 $this->aCurrentComment = $this->_blankComment();
673 $this->inComment = 0;
677 if ($this->inComment)
678 $this->aCurrentComment['email'] = $this->getCharacterData();
681 if ($this->inComment)
682 $this->aCurrentComment['url'] = $this->getCharacterData();
685 if ($this->inComment)
686 $this->aCurrentComment['authorid'] = $this->getCharacterData();
689 if ($this->inComment)
690 $this->aCurrentComment['host'] = $this->getCharacterData();
693 if ($this->inComment)
694 $this->aCurrentComment['ip'] = $this->getCharacterData();
697 echo 'UNEXPECTED TAG: ' , $name, '<br />';
700 $this->clearCharacterData();
705 * Called by XML parser for data inside elements
707 function characterData ($parser, $data) {
708 if ($this->bDebug) echo 'NEW DATA: ', i18n::hsc($data), '<br />';
709 $this->cdata .= $data;
713 * Returns the data collected so far
715 function getCharacterData() {
720 * Clears the data buffer
722 function clearCharacterData() {
727 * Parses a given string into a unix timestamp.
730 * String, formatted as given in $strFormat
732 * Multiple date formats are supported:
733 * 'unix': plain unix timestamp (numeric)
734 * 'blogger': for blogger import: MM/DD/YYYY hh:MM:SS AM
736 function getTime($strTime, $strFormat = 'unix') {
737 $strFormat = strtolower($strFormat);
740 return intval($strTime);
742 // 7/24/2000 11:27:13 AM
743 if (eregi("(.*)/(.*)/(.*) (.*):(.*):(.*) (.*)",$strTime,$regs) != false) {
744 if (($regs[7] == "PM") && ($regs[4] != "12"))
747 return mktime($regs[4],$regs[5],$regs[6],$regs[1],$regs[2],$regs[3]);
755 function _blankItem($id = -1) {
759 'timestamp' => time(),
765 'itemstatus' => 'open',
768 'comments' => array()
772 function _blankComment($id = -1) {
775 'timestamp' => time(),
787 function _addAuthorName($name) {
788 if (!in_array($name, $this->aAuthorNames))
789 array_push($this->aAuthorNames, $name);
792 function _addCategoryName($name) {
793 if (!in_array($name, $this->aCategoryNames))
794 array_push($this->aCategoryNames, $name);
797 function sql_addToItem($title, $body, $more, $blogid, $authorid, $timestamp, $closed, $category, $karmapos, $karmaneg) {
798 $title = trim(addslashes($title));
799 $body = trim(addslashes($body));
800 $more = trim(addslashes($more));
801 $timestamp = date("Y-m-d H:i:s", $timestamp);
803 $query = 'INSERT INTO '.sql_table('item').' (ITITLE, IBODY, IMORE, IBLOG, IAUTHOR, ITIME, ICLOSED, IKARMAPOS, IKARMANEG, ICAT) '
804 . "VALUES ('$title', '$body', '$more', $blogid, $authorid, '$timestamp', $closed, $karmapos, $karmaneg, $category)";
806 mysql_query($query) or die("Error while executing query: " . $query);
808 return mysql_insert_id();
811 function sql_addToBlog($name, $shortname, $ownerid) {
812 $name = addslashes($name);
813 $shortname = addslashes($shortname);
815 // create new category first
816 mysql_query('INSERT INTO '.sql_table('category')." (CNAME, CDESC) VALUES ('General','Items that do not fit in another category')");
817 $defcat = mysql_insert_id();
819 $query = 'INSERT INTO '.sql_table('blog')." (BNAME, BSHORTNAME, BCOMMENTS, BMAXCOMMENTS, BDEFCAT) VALUES ('$name','$shortname',1 ,0, $defcat)";
820 mysql_query($query) or die("Error while executing query: " . $query);
821 $id = mysql_insert_id();
823 // update category row so it links to blog
824 mysql_query('UPDATE ' . sql_table('category') . ' SET cblog=' . intval($id). ' WHERE catid=' . intval($defcat));
826 BlogImport::sql_addToTeam($id,$ownerid,1);
832 function sql_addToComments($name, $url, $body, $blogid, $itemid, $memberid, $timestamp, $host, $ip='') {
833 $name = addslashes($name);
834 $url = addslashes($url);
835 $body = trim(addslashes($body));
836 $host = addslashes($host);
837 $ip = addslashes($ip);
838 $timestamp = date("Y-m-d H:i:s", $timestamp);
840 $query = 'INSERT INTO '.sql_table('comment')
841 . ' (CUSER, CMAIL, CMEMBER, CBODY, CITEM, CTIME, CHOST, CBLOG, CIP) '
842 . "VALUES ('$name', '$url', $memberid, '$body', $itemid, '$timestamp', '$host', $blogid, '$ip')";
844 mysql_query($query) or die("Error while executing query: " . $query);
846 return mysql_insert_id();
849 function sql_addToTeam($blogid, $memberid, $admin) {
851 $query = 'INSERT INTO '.sql_table('team').' (TMEMBER, TBLOG, TADMIN) '
852 . "VALUES ($memberid, $blogid, $admin)";
854 mysql_query($query) or die("Error while executing query: " . $query);
856 return mysql_insert_id();
863 // some sort of version checking
864 $ver = convert_getNucleusVersion();
866 convert_doError("You should check the Nucleus website for updates to this convert tool. This one might not work with your current Nucleus installation.");
868 // make sure the request variables get reqistered in the global scope
869 // Doing this should be avoided on code rewrite (this is a potential security risk)
870 if ((phpversion() >= "4.1.0") && (ini_get("register_globals") == 0)) {
871 @import_request_variables("gp",'');
874 /** this function gets the nucleus version, even if the getNucleusVersion
875 * function does not exist yet
876 * return 96 for all versions < 100
878 function convert_getNucleusVersion() {
879 if (!function_exists('getNucleusVersion')) return 96;
880 return getNucleusVersion();
883 // TODO: remove this function (replaced by BlogImport::sql_addToItem)
884 function convert_addToItem($title, $body, $more, $blogid, $authorid, $timestamp, $closed, $category, $karmapos, $karmaneg) {
885 $title = trim(addslashes($title));
886 $body = trim(addslashes($body));
887 $more = trim(addslashes($more));
889 $query = 'INSERT INTO '.sql_table('item').' (ITITLE, IBODY, IMORE, IBLOG, IAUTHOR, ITIME, ICLOSED, IKARMAPOS, IKARMANEG, ICAT) '
890 . "VALUES ('$title', '$body', '$more', $blogid, $authorid, '$timestamp', $closed, $karmapos, $karmaneg, $category)";
892 mysql_query($query) or die("Error while executing query: " . $query);
894 return mysql_insert_id();
898 // TODO: remove this function (replaced by BlogImport::sql_addToBlog)
899 function convert_addToBlog($name, $shortname, $ownerid) {
900 $name = addslashes($name);
901 $shortname = addslashes($shortname);
903 // create new category first
904 mysql_query('INSERT INTO '.sql_table('category')." (CNAME, CDESC) VALUES ('General','Items that do not fit in another categort')");
905 $defcat = mysql_insert_id();
907 $query = 'INSERT INTO '.sql_table('blog')." (BNAME, BSHORTNAME, BCOMMENTS, BMAXCOMMENTS, BDEFCAT) VALUES ('$name','$shortname',1 ,0, $defcat)";
908 mysql_query($query) or die("Error while executing query: " . $query);
909 $id = mysql_insert_id();
911 convert_addToTeam($id,$ownerid,1);
917 // TODO: remove this function (replaced by BlogImport::sql_addToComments)
918 function convert_addToComments($name, $url, $body, $blogid, $itemid, $memberid, $timestamp, $host, $ip='') {
919 $name = addslashes($name);
920 $url = addslashes($url);
921 $body = trim(addslashes($body));
922 $host = addslashes($host);
923 $ip = addslashes($ip);
925 $query = 'INSERT INTO '.sql_table('comment')
926 . ' (CUSER, CMAIL, CMEMBER, CBODY, CITEM, CTIME, CHOST, CBLOG, CIP) '
927 . "VALUES ('$name', '$url', $memberid, '$body', $itemid, '$timestamp', '$host', $blogid, '$ip')";
929 mysql_query($query) or die("Error while executing query: " . $query);
931 return mysql_insert_id();
934 // TODO: remove this function (replaced by BlogImport::sql_addToTeam)
935 function convert_addToTeam($blogid, $memberid, $admin) {
937 $query = 'INSERT INTO '.sql_table('team').' (TMEMBER, TBLOG, TADMIN) '
938 . "VALUES ($memberid, $blogid, $admin)";
940 mysql_query($query) or die("Error while executing query: " . $query);
942 return mysql_insert_id();
945 function convert_showLogin($type) {
948 <h1>Please Log in First</h1>
949 <p>Enter your data below:</p>
951 <form method="post" action="<?php echo $type?>">
954 <li>Name: <input name="login" /></li>
955 <li>Password <input name="password" type="password" /></li>
959 <input name="action" value="login" type="hidden" />
960 <input type="submit" value="Log in" />
964 <?php convert_foot();
968 function convert_head() {
970 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
971 <html xmlns="http://www.w3.org/1999/xhtml">
973 <title>Nucleus Convert</title>
975 @import url('../styles/manual.css');
981 function convert_foot() {
987 function convert_doError($msg) {
998 <p><a href="index.php" onclick="history.back();">Go Back</a></p>
1004 function endsWithSlash($s) {
1005 return (strrpos($s,'/') == strlen($s) - 1);