]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - install.php
39984aa0862df078f3f93795aab2609839f818a8
[quix0rs-gnu-social.git] / install.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2009, StatusNet, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 define('INSTALLDIR', dirname(__FILE__));
21
22 $external_libraries=array(
23     array(
24         'name'=>'gettext',
25         'url'=>'http://us.php.net/manual/en/book.gettext.php',
26         'check_function'=>'gettext'
27     ),
28     array(
29         'name'=>'PEAR',
30         'url'=>'http://pear.php.net/',
31         'deb'=>'php-pear',
32         'include'=>'PEAR.php',
33         'check_class'=>'PEAR'
34     ),
35     array(
36         'name'=>'DB',
37         'pear'=>'DB',
38         'url'=>'http://pear.php.net/package/DB',
39         'deb'=>'php-db',
40         'include'=>'DB/common.php',
41         'check_class'=>'DB_common'
42     ),
43     array(
44         'name'=>'DB_DataObject',
45         'pear'=>'DB_DataObject',
46         'url'=>'http://pear.php.net/package/DB_DataObject',
47         'include'=>'DB/DataObject.php',
48         'check_class'=>'DB_DataObject'
49     ),
50     array(
51         'name'=>'Console_Getopt',
52         'pear'=>'Console_Getopt',
53         'url'=>'http://pear.php.net/package/Console_Getopt',
54         'include'=>'Console/Getopt.php',
55         'check_class'=>'Console_Getopt'
56     ),
57     array(
58         'name'=>'Facebook API',
59         'url'=>'http://developers.facebook.com/',
60         'include'=>'facebook/facebook.php',
61         'check_class'=>'Facebook'
62     ),
63     array(
64         'name'=>'htmLawed',
65         'url'=>'http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed',
66         'include'=>'htmLawed/htmLawed.php',
67         'check_function'=>'htmLawed'
68     ),
69     array(
70         'name'=>'HTTP_Request',
71         'pear'=>'HTTP_Request',
72         'url'=>'http://pear.php.net/package/HTTP_Request',
73         'deb'=>'php-http-request',
74         'include'=>'HTTP/Request.php',
75         'check_class'=>'HTTP_Request'
76     ),
77     array(
78         'name'=>'Mail',
79         'pear'=>'Mail',
80         'url'=>'http://pear.php.net/package/Mail',
81         'deb'=>'php-mail',
82         'include'=>'Mail.php',
83         'check_class'=>'Mail'
84     ),
85     array(
86         'name'=>'Mail_mimeDecode',
87         'pear'=>'Mail_mimeDecode',
88         'url'=>'http://pear.php.net/package/Mail_mimeDecode',
89         'deb'=>'php-mail-mimedecode',
90         'include'=>'Mail/mimeDecode.php',
91         'check_class'=>'Mail_mimeDecode'
92     ),
93     array(
94         'name'=>'Mime_Type',
95         'pear'=>'Mime_Type',
96         'url'=>'http://pear.php.net/package/Mime_Type',
97         'include'=>'MIME/Type.php',
98         'check_class'=>'Mime_Type'
99     ),
100     array(
101         'name'=>'Net_URL_Mapper',
102         'pear'=>'Net_URL_Mapper',
103         'url'=>'http://pear.php.net/package/Net_URL_Mapper',
104         'include'=>'Net/URL/Mapper.php',
105         'check_class'=>'Net_URL_Mapper'
106     ),
107     array(
108         'name'=>'Net_Socket',
109         'pear'=>'Net_Socket',
110         'url'=>'http://pear.php.net/package/Net_Socket',
111         'deb'=>'php-net-socket',
112         'include'=>'Net/Socket.php',
113         'check_class'=>'Net_Socket'
114     ),
115     array(
116         'name'=>'Net_SMTP',
117         'pear'=>'Net_SMTP',
118         'url'=>'http://pear.php.net/package/Net_SMTP',
119         'deb'=>'php-net-smtp',
120         'include'=>'Net/SMTP.php',
121         'check_class'=>'Net_SMTP'
122     ),
123     array(
124         'name'=>'Net_URL',
125         'pear'=>'Net_URL',
126         'url'=>'http://pear.php.net/package/Net_URL',
127         'deb'=>'php-net-url',
128         'include'=>'Net/URL.php',
129         'check_class'=>'Net_URL'
130     ),
131     array(
132         'name'=>'Net_URL2',
133         'pear'=>'Net_URL2',
134         'url'=>'http://pear.php.net/package/Net_URL2',
135         'include'=>'Net/URL2.php',
136         'check_class'=>'Net_URL2'
137     ),
138     array(
139         'name'=>'Services_oEmbed',
140         'pear'=>'Services_oEmbed',
141         'url'=>'http://pear.php.net/package/Services_oEmbed',
142         'include'=>'Services/oEmbed.php',
143         'check_class'=>'Services_oEmbed'
144     ),
145     array(
146         'name'=>'Stomp',
147         'url'=>'http://stomp.codehaus.org/PHP',
148         'include'=>'Stomp.php',
149         'check_class'=>'Stomp'
150     ),
151     array(
152         'name'=>'System_Command',
153         'pear'=>'System_Command',
154         'url'=>'http://pear.php.net/package/System_Command',
155         'include'=>'System/Command.php',
156         'check_class'=>'System_Command'
157     ),
158     array(
159         'name'=>'XMPPHP',
160         'url'=>'http://code.google.com/p/xmpphp',
161         'include'=>'XMPPHP/XMPP.php',
162         'check_class'=>'XMPPHP_XMPP'
163     ),
164     array(
165         'name'=>'PHP Markdown',
166         'url'=>'http://www.michelf.com/projects/php-markdown/',
167         'include'=>'markdown.php',
168         'check_class'=>'Markdown_Parser'
169     ),
170     array(
171         'name'=>'OAuth',
172         'url'=>'http://code.google.com/p/oauth-php',
173         'include'=>'OAuth.php',
174         'check_class'=>'OAuthRequest'
175     ),
176     array(
177         'name'=>'Validate',
178         'pear'=>'Validate',
179         'url'=>'http://pear.php.net/package/Validate',
180         'include'=>'Validate.php',
181         'check_class'=>'Validate'
182     )
183 );
184
185 function main()
186 {
187     if (!checkPrereqs())
188     {
189         return;
190     }
191     
192     if( $_GET['checklibs'] ){
193         showLibs();
194     }else{
195         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
196             handlePost();
197         } else {
198             showForm();
199         }
200     }
201 }
202
203 function haveExternalLibrary($external_library)
204 {
205     if(isset($external_library['include']) && ! include_once($external_library['include'])){
206         return false;
207     }
208     if(isset($external_library['check_function']) && ! function_exists($external_library['check_function'])){
209         return false;
210     }
211     if(isset($external_library['check_class']) && ! class_exists($external_library['check_class'])){
212         return false;
213     }
214     return true;
215 }
216
217 function checkPrereqs()
218 {
219         $pass = true;
220
221     if (file_exists(INSTALLDIR.'/config.php')) {
222          ?><p class="error">Config file &quot;config.php&quot; already exists.</p>
223          <?php
224         $pass = false;
225     }
226
227     if (version_compare(PHP_VERSION, '5.2.3', '<')) {
228             ?><p class="error">Require PHP version 5.2.3 or greater.</p><?php
229                     $pass = false;
230     }
231
232     $reqs = array('gd', 'curl',
233                   'xmlwriter', 'mbstring','tidy');
234
235     foreach ($reqs as $req) {
236         if (!checkExtension($req)) {
237             ?><p class="error">Cannot load required extension: <code><?php echo $req; ?></code></p><?php
238                     $pass = false;
239         }
240     }
241     if (!checkExtension('pgsql') && !checkExtension('mysql')) {
242       ?><p class="error">Cannot find mysql or pgsql extension. You need one or the other: <code><?php echo $req; ?></code></p><?php
243                     $pass = false;
244     }
245
246         if (!is_writable(INSTALLDIR)) {
247          ?><p class="error">Cannot write config file to: <code><?php echo INSTALLDIR; ?></code></p>
248                <p>On your server, try this command: <code>chmod a+w <?php echo INSTALLDIR; ?></code>
249          <?php
250              $pass = false;
251         }
252
253         // Check the subdirs used for file uploads
254         $fileSubdirs = array('avatar', 'background', 'file');
255         foreach ($fileSubdirs as $fileSubdir) {
256                 $fileFullPath = INSTALLDIR."/$fileSubdir/";
257                 if (!is_writable($fileFullPath)) {
258              ?><p class="error">Cannot write <?php echo $fileSubdir; ?> directory: <code><?php echo $fileFullPath; ?></code></p>
259                        <p>On your server, try this command: <code>chmod a+w <?php echo $fileFullPath; ?></code></p>
260              <?php
261                      $pass = false;
262                 }
263         }
264
265         return $pass;
266 }
267
268 function checkExtension($name)
269 {
270     if (extension_loaded($name)) {
271         return true;
272     } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
273         // dl will throw a fatal error if it's disabled or we're in safe mode.
274         // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
275         $soname = $name . '.' . PHP_SHLIB_SUFFIX;
276         if (PHP_SHLIB_SUFFIX == 'dll') {
277                 $soname = "php_" . $soname;
278         }
279         return @dl($soname);
280     } else {
281         return false;
282     }
283 }
284
285 function showLibs()
286 {
287     global $external_libraries;
288     $present_libraries=array();
289     $absent_libraries=array();
290     foreach($external_libraries as $external_library){
291         if(haveExternalLibrary($external_library)){
292             $present_libraries[]=$external_library;
293         }else{
294             $absent_libraries[]=$external_library;
295         }
296     }
297     echo<<<E_O_T
298     <div class="instructions">
299         <p>Laconica comes bundled with a number of libraries required for the application to work. However, it is best that you use PEAR or you distribution to manage
300         libraries instead, as they tend to provide security updates faster, and may offer improved performance.</p>
301         <p>On Debian based distributions, such as Ubuntu, use a package manager (such as &quot;aptitude&quot;, &quot;apt-get&quot;, and &quot;synaptic&quot;) to install the package listed.</p>
302         <p>On RPM based distributions, such as Red Hat, Fedora, CentOS, Scientific Linux, Yellow Dog Linux and Oracle Enterprise Linux, use a package manager (such as &quot;yum&quot;, &quot;apt-rpm&quot;, and &quot;up2date&quot;) to install the package listed.</p>
303         <p>On servers without a package manager (such as Windows), or if the library is not packaged for your distribution, you can use PHP's PEAR to install the library. Simply run &quot;pear install &lt;name&gt;&quot;.</p>
304     </div>
305     <h2>Absent Libraries</h2>
306     <ul id="absent_libraries">
307 E_O_T;
308     foreach($absent_libraries as $library)
309     {
310         echo '<li>';
311         if($library['url']){
312             echo '<a href=">'.$library['url'].'">'.htmlentities($library['name']).'</a>';
313         }else{
314             echo htmlentities($library['name']);
315         }
316         echo '<ul>';
317         if($library['deb']){
318             echo '<li class="deb package">deb: <a href="apt:' . urlencode($library['deb']) . '">' . htmlentities($library['deb']) . '</a></li>';
319         }
320         if($library['rpm']){
321             echo '<li class="rpm package">rpm: ' . htmlentities($library['rpm']) . '</li>';
322         }
323         if($library['pear']){
324             echo '<li class="pear package">pear: ' . htmlentities($library['pear']) . '</li>';
325         }
326         echo '</ul>';
327     }
328     echo<<<E_O_T
329     </ul>
330     <h2>Installed Libraries</h2>
331     <ul id="present_libraries">
332 E_O_T;
333     foreach($present_libraries as $library)
334     {
335         echo '<li>';
336         if($library['url']){
337             echo '<a href=">'.$library['url'].'">'.htmlentities($library['name']).'</a>';
338         }else{
339             echo htmlentities($library['name']);
340         }
341         echo '</li>';
342     }
343     echo<<<E_O_T
344     </ul>
345 E_O_T;
346 }
347
348 function showForm()
349 {
350     echo<<<E_O_T
351         </ul>
352     </dd>
353 </dl>
354 <dl id="page_notice" class="system_notice">
355     <dt>Page notice</dt>
356     <dd>
357         <div class="instructions">
358             <p>Enter your database connection information below to initialize the database.</p>
359             <p>Laconica bundles a number of libraries for ease of installation. <a href="?checklibs=true">You can see what bundled libraries you are using, versus what libraries are installed on your server.</a>
360         </div>
361     </dd>
362 </dl>
363 <form method="post" action="install.php" class="form_settings" id="form_install">
364     <fieldset>
365         <legend>Connection settings</legend>
366         <ul class="form_data">
367             <li>
368                 <label for="sitename">Site name</label>
369                 <input type="text" id="sitename" name="sitename" />
370                 <p class="form_guide">The name of your site</p>
371             </li>
372             <li>
373                 <label for="fancy-enable">Fancy URLs</label>
374                 <input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
375                 <input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
376                 <p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p>
377             </li>
378             <li>
379                 <label for="host">Hostname</label>
380                 <input type="text" id="host" name="host" />
381                 <p class="form_guide">Database hostname</p>
382             </li>
383             <li>
384
385                 <label for="dbtype">Type</label>
386                 <input type="radio" name="dbtype" id="fancy-mysql" value="mysql" checked='checked' /> MySQL<br />
387                 <input type="radio" name="dbtype" id="dbtype-pgsql" value="pgsql" /> PostgreSQL<br />
388                 <p class="form_guide">Database type</p>
389             </li>
390
391             <li>
392                 <label for="database">Name</label>
393                 <input type="text" id="database" name="database" />
394                 <p class="form_guide">Database name</p>
395             </li>
396             <li>
397                 <label for="username">Username</label>
398                 <input type="text" id="username" name="username" />
399                 <p class="form_guide">Database username</p>
400             </li>
401             <li>
402                 <label for="password">Password</label>
403                 <input type="password" id="password" name="password" />
404                 <p class="form_guide">Database password (optional)</p>
405             </li>
406         </ul>
407         <input type="submit" name="submit" class="submit" value="Submit" />
408     </fieldset>
409 </form>
410
411 E_O_T;
412 }
413
414 function updateStatus($status, $error=false)
415 {
416 ?>
417                 <li <?php echo ($error) ? 'class="error"': ''; ?>><?php echo $status;?></li>
418
419 <?php
420 }
421
422 function handlePost()
423 {
424 ?>
425
426 <?php
427     $host     = $_POST['host'];
428     $dbtype   = $_POST['dbtype'];
429     $database = $_POST['database'];
430     $username = $_POST['username'];
431     $password = $_POST['password'];
432     $sitename = $_POST['sitename'];
433     $fancy    = !empty($_POST['fancy']);
434     $server = $_SERVER['HTTP_HOST'];
435     $path = substr(dirname($_SERVER['PHP_SELF']), 1);
436
437 ?>
438     <dl class="system_notice">
439         <dt>Page notice</dt>
440         <dd>
441             <ul>
442 <?php
443         $fail = false;
444
445     if (empty($host)) {
446         updateStatus("No hostname specified.", true);
447                 $fail = true;
448     }
449
450     if (empty($database)) {
451         updateStatus("No database specified.", true);
452                 $fail = true;
453     }
454
455     if (empty($username)) {
456         updateStatus("No username specified.", true);
457                 $fail = true;
458     }
459
460 //     if (empty($password)) {
461 //         updateStatus("No password specified.", true);
462 //              $fail = true;
463 //     }
464
465     if (empty($sitename)) {
466         updateStatus("No sitename specified.", true);
467                 $fail = true;
468     }
469
470     if($fail){
471             showForm();
472         return;
473     }
474
475     // FIXME: use PEAR::DB or PDO instead of our own switch
476
477     switch($dbtype) {
478         case 'mysql':
479             $db = mysql_db_installer($host, $database, $username, $password);
480             break;
481         case 'pgsql':
482             $db = pgsql_db_installer($host, $database, $username, $password);
483             break;
484         default:
485     }
486
487     if (!$db) {
488         // database connection failed, do not move on to create config file.
489         return false;
490     }
491
492     updateStatus("Writing config file...");
493     $res = writeConf($sitename, $server, $path, $fancy, $db);
494
495     if (!$res) {
496         updateStatus("Can't write config file.", true);
497         showForm();
498         return;
499     }
500
501     /*
502         TODO https needs to be considered
503     */
504     $link = "http://".$server.'/'.$path;
505
506     updateStatus("StatusNet has been installed at $link");
507     updateStatus("You can visit your <a href='$link'>new StatusNet site</a>.");
508 ?>
509
510 <?php
511 }
512
513 function pgsql_db_installer($host, $database, $username, $password) {
514   $connstring = "dbname=$database host=$host user=$username";
515
516   //No password would mean trust authentication used.
517   if (!empty($password)) {
518     $connstring .= " password=$password";
519   }
520   updateStatus("Starting installation...");
521   updateStatus("Checking database...");
522   $conn = pg_connect($connstring);
523
524   if ($conn ===false) {
525     updateStatus("Failed to connect to database: $connstring");
526     showForm();
527     return false;
528   }
529
530   //ensure database encoding is UTF8
531   $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
532   if ($record->server_encoding != 'UTF8') {
533     updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
534     showForm();
535     return false;
536   }
537
538   updateStatus("Running database script...");
539   //wrap in transaction;
540   pg_query($conn, 'BEGIN');
541   $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
542
543   if ($res === false) {
544       updateStatus("Can't run database script.", true);
545       showForm();
546       return false;
547   }
548   foreach (array('sms_carrier' => 'SMS carrier',
549                 'notice_source' => 'notice source',
550                 'foreign_services' => 'foreign service')
551           as $scr => $name) {
552       updateStatus(sprintf("Adding %s data to database...", $name));
553       $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
554       if ($res === false) {
555           updateStatus(sprintf("Can't run %d script.", $name), true);
556           showForm();
557           return false;
558       }
559   }
560   pg_query($conn, 'COMMIT');
561
562   if (empty($password)) {
563     $sqlUrl = "pgsql://$username@$host/$database";
564   }
565   else {
566     $sqlUrl = "pgsql://$username:$password@$host/$database";
567   }
568
569   $db = array('type' => 'pgsql', 'database' => $sqlUrl);
570
571   return $db;
572 }
573
574 function mysql_db_installer($host, $database, $username, $password) {
575   updateStatus("Starting installation...");
576   updateStatus("Checking database...");
577
578   $conn = mysql_connect($host, $username, $password);
579   if (!$conn) {
580       updateStatus("Can't connect to server '$host' as '$username'.", true);
581       showForm();
582       return false;
583   }
584   updateStatus("Changing to database...");
585   $res = mysql_select_db($database, $conn);
586   if (!$res) {
587       updateStatus("Can't change to database.", true);
588       showForm();
589       return false;
590   }
591   updateStatus("Running database script...");
592   $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
593   if ($res === false) {
594       updateStatus("Can't run database script.", true);
595       showForm();
596       return false;
597   }
598   foreach (array('sms_carrier' => 'SMS carrier',
599                 'notice_source' => 'notice source',
600                 'foreign_services' => 'foreign service')
601           as $scr => $name) {
602       updateStatus(sprintf("Adding %s data to database...", $name));
603       $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
604       if ($res === false) {
605           updateStatus(sprintf("Can't run %d script.", $name), true);
606           showForm();
607           return false;
608       }
609   }
610
611       $sqlUrl = "mysqli://$username:$password@$host/$database";
612       $db = array('type' => 'mysql', 'database' => $sqlUrl);
613       return $db;
614 }
615
616 function writeConf($sitename, $server, $path, $fancy, $db)
617 {
618     // assemble configuration file in a string
619     $cfg =  "<?php\n".
620             "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
621
622             // site name
623             "\$config['site']['name'] = '$sitename';\n\n".
624
625             // site location
626             "\$config['site']['server'] = '$server';\n".
627             "\$config['site']['path'] = '$path'; \n\n".
628
629             // checks if fancy URLs are enabled
630             ($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
631
632             // database
633             "\$config['db']['database'] = '{$db['database']}';\n\n".
634             ($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
635             "\$config['db']['type'] = '{$db['type']}';\n\n".
636
637             "?>";
638     // write configuration file out to install directory
639     $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
640
641     return $res;
642 }
643
644 function runDbScript($filename, $conn, $type = 'mysql')
645 {
646     $sql = trim(file_get_contents($filename));
647     $stmts = explode(';', $sql);
648     foreach ($stmts as $stmt) {
649         $stmt = trim($stmt);
650         if (!mb_strlen($stmt)) {
651             continue;
652         }
653         // FIXME: use PEAR::DB or PDO instead of our own switch
654         switch ($type) {
655         case 'mysql':
656             $res = mysql_query($stmt, $conn);
657             if ($res === false) {
658                 $error = mysql_error();
659             }
660             break;
661         case 'pgsql':
662             $res = pg_query($conn, $stmt);
663             if ($res === false) {
664                 $error = pg_last_error();
665             }
666             break;
667         default:
668             updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
669         }
670         if ($res === false) {
671             updateStatus("ERROR ($error) for SQL '$stmt'");
672             return $res;
673         }
674     }
675     return true;
676 }
677
678 ?>
679 <?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
680 <!DOCTYPE html>
681 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
682     <head>
683         <title>Install StatusNet</title>
684         <link rel="shortcut icon" href="favicon.ico"/>
685         <link rel="stylesheet" type="text/css" href="theme/default/css/display.css?version=0.8" media="screen, projection, tv"/>
686         <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css?version=0.8" /><![endif]-->
687         <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css?version=0.8" /><![endif]-->
688         <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css?version=0.8" /><![endif]-->
689         <script src="js/jquery.min.js"></script>
690         <script src="js/install.js"></script>
691     </head>
692     <body id="install">
693         <div id="wrap">
694             <div id="header">
695                 <address id="site_contact" class="vcard">
696                     <a class="url home bookmark" href=".">
697                         <img class="logo photo" src="theme/default/logo.png" alt="StatusNet"/>
698                         <span class="fn org">StatusNet</span>
699                     </a>
700                 </address>
701             </div>
702             <div id="core">
703                 <div id="content">
704                     <h1>Install StatusNet</h1>
705 <?php main(); ?>
706                 </div>
707             </div>
708         </div>
709     </body>
710 </html>