1<?php
2/** todo work out something more than true/false returns for dependency checks */
3
4
5function i18n($value) {
6  return $value;  /* Just pass the value through */
7}
8
9$skip_errors = false; // We need to hide a couple of unsightly errors even here...
10function log_setup_error($errno , $errstr , $errfile , $errline) {
11  global $skip_errors;
12  if ( $skip_errors ) return;
13  error_log('DAViCal setup.php: Informational: '.$errfile.'('.$errline.'): ['.$errno.'] '.$errstr);
14}
15
16function catch_setup_errors($errno , $errstr , $errfile , $errline , $errcontext = null ) {
17  if ( $errno == 2 ) {
18    // A working installation will regularly fail to include_once() for several files as it searches for the location
19    log_setup_error($errno , $errstr , $errfile , $errline);
20    return true;
21  }
22  if ( $errno == 8 ) {
23    // Yeah, OK, so we redundantly call ob_flash() without needing to...
24    log_setup_error($errno , $errstr , $errfile , $errline);
25    return true;
26  }
27  else if ( $errno == 256 ) {
28    // This will (probably) be a database connection error, which will throw an exception if we return.
29    log_setup_error($errno , $errstr , $errfile , $errline);
30    return true;
31  }
32  if ( !headers_sent() ) header("Content-type: text/plain"); else echo "<pre>\n";
33  try {
34    @ob_flush(); // Seems like it should be better to do the following but is problematic on PHP5.3 at least: while ( ob_get_level() > 0 ) ob_end_flush();
35  }
36  catch( Exception $ignored ) {}
37  echo "Error [".$errno."] ".$errstr."\n";
38  echo "At line ", $errline, " of ", $errfile, "\n";
39
40  $e = new Exception();
41  $trace = array_reverse($e->getTrace());
42  echo "================= Stack Trace ===================\n";
43  foreach( $trace AS $k => $v ) {
44    printf( "%s[%d] %s%s%s()\n", $v['file'], $v['line'], (isset($v['class'])?$v['class']:''), (isset($v['type'])?$v['type']:''), (isset($v['function'])?$v['function']:'') );
45  }
46}
47
48set_error_handler('catch_setup_errors', E_ALL);
49
50class CheckResult {
51  private $ok;
52  private $use_class;
53  private $description;
54
55  function __construct( $success, $description=null, $use_class=null ) {
56    $this->ok = (boolean) $success;
57    $this->description = (isset($description)?$description : ($success===true? i18n('Passed') : i18n('Fail')));
58    $this->use_class = (isset($use_class)?$use_class:($success===true?'dep_ok' : 'dep_fail'));
59  }
60
61  public function getClass() {
62    return $this->use_class;
63  }
64
65  public function setClass( $new_class ) {
66    $this->use_class = $new_class;
67  }
68
69  public function getOK() {
70    return $this->ok;
71  }
72
73  public function getDescription() {
74    return translate($this->description);
75  }
76
77  public function setDescription( $new_desc ) {
78    $this->description = $new_desc;
79  }
80
81}
82
83/**
84 * We put many of these checks before we even try to load always.php so that we
85 * can try and do some diagnostic work to ensure it will load OK.
86 */
87function check_pgsql() {
88  return new CheckResult(function_exists('pg_connect'));
89}
90
91function check_pdo() {
92  return new CheckResult(class_exists('PDO'));
93}
94
95function check_pdo_pgsql() {
96  global $loaded_extensions;
97
98  if ( !check_pdo() ) return new CheckResult(false);
99  return new CheckResult(isset($loaded_extensions['pdo_pgsql']));
100}
101
102function check_database_connection() {
103  global $c;
104
105  if ( !check_pdo_pgsql() ) return new CheckResult(false);
106  return new CheckResult( !( empty($c->schema_major) || $c->schema_major == 0 || empty($c->schema_minor) || $c->schema_minor == 0) );
107}
108
109function check_gettext() {
110  global $phpinfo, $loaded_extensions;
111
112  if ( !function_exists('gettext') ) return new CheckResult(false);
113  return new CheckResult(isset($loaded_extensions['gettext']));
114}
115
116function check_iconv() {
117  global $phpinfo, $loaded_extensions;
118
119  if ( !function_exists('iconv') ) return new CheckResult(false);
120  return new CheckResult(isset($loaded_extensions['iconv']));
121}
122
123function check_ldap() {
124  global $phpinfo, $loaded_extensions;
125
126  if (!function_exists('ldap_connect')) return new CheckResult(false);
127  return new CheckResult(isset($loaded_extensions['ldap']));
128}
129
130function check_real_php() {
131  global $phpinfo, $loaded_extensions;
132  // Looking for "Server API </td><td class="v">Apache 2.0 Filter" in the phpinfo
133  if ( preg_match('{Server API.*Apache 2\.. Filter}', $phpinfo) ) return new CheckResult(false);
134  return new CheckResult(true);
135}
136
137function check_calendar() {
138  global $phpinfo, $loaded_extensions;
139
140  if (!function_exists('cal_days_in_month')) return new CheckResult(false);
141  return new CheckResult(isset($loaded_extensions['calendar']));
142}
143
144function check_suhosin_server_strip() {
145  global $loaded_extensions;
146
147  if ( !isset($loaded_extensions['suhosin']) ) return new CheckResult(true);
148  return new CheckResult( ini_get('suhosin.server.strip') == "0"
149       || strtolower(ini_get('suhosin.server.strip')) == "off"
150       || ini_get('suhosin.server.strip') == "" );
151}
152
153function check_magic_quotes_gpc() {
154  return new CheckResult( (get_magic_quotes_gpc() == 0) );
155}
156
157function check_magic_quotes_runtime() {
158  return new CheckResult( (get_magic_quotes_runtime() == 0) );
159}
160
161function check_curl() {
162  global $phpinfo, $loaded_extensions;
163
164  if (!function_exists('curl_init')) return new CheckResult(false);
165  return new CheckResult(isset($loaded_extensions['curl']));
166}
167
168$loaded_extensions = array_flip(get_loaded_extensions());
169
170
171function do_error( $errormessage ) {
172  // We can't translate this because we're testing these things even before
173  // the translation interface is available...
174  printf("<p class='error'><br/>%s</p>", $errormessage );
175}
176
177if ( !check_gettext()->getOK() )   do_error("The GNU 'gettext' extension for PHP is not available.");
178if ( !check_pgsql()->getOK() )     do_error("PHP 'pgsql' functions are not available");
179if ( !check_pdo()->getOK() )       do_error("PHP 'PDO' module is not available");
180if ( !check_pdo_pgsql()->getOK() ) do_error("The PDO drivers for PostgreSQL are not available");
181if ( !check_iconv()->getOK() )     do_error("The 'iconv' extension for PHP is not available");
182
183function get_phpinfo() {
184  ob_start( );
185  phpinfo();
186  $phpinfo = ob_get_contents( );
187  ob_end_clean( );
188
189  $phpinfo = preg_replace( '{^.*?<body>}s', '', $phpinfo);
190  $phpinfo = preg_replace( '{</body>.*?$}s', '', $phpinfo);
191  return $phpinfo;
192}
193$phpinfo = get_phpinfo();
194
195try {
196  include("./always.php");
197  set_error_handler('log_setup_error', E_ALL);
198  include("DAViCalSession.php");
199  if ( check_pgsql()->GetOK() ) {
200    $session->LoginRequired( (isset($c->restrict_setup_to_admin) && $c->restrict_setup_to_admin ? 'Admin' : null ) );
201  }
202}
203catch( Exception $e ) {
204  class setupFakeSession {
205    function AllowedTo() {
206      return true;
207    }
208  }
209  $session = new setupFakeSession(1);
210}
211
212
213include("interactive-page.php");
214include("page-header.php");
215
216require_once("AwlQuery.php");
217
218
219function check_datetime() {
220  if ( class_exists('DateTime') ) return new CheckResult(true);
221  $result = new CheckResult(false);
222  $result->setClass('dep_warning');
223  $result->setDescription(i18n('Most of DAViCal will work but upgrading to PHP 5.2 or later is strongly recommended.'));
224  return $result;
225}
226
227function check_xml() {
228  return new CheckResult(function_exists('xml_parser_create_ns'));
229}
230
231function check_schema_version() {
232  global $c;
233  if ( $c->want_dbversion[0] == $c->schema_major
234    && $c->want_dbversion[1] == $c->schema_minor
235    && $c->want_dbversion[2] == $c->schema_patch ) {
236    return new CheckResult( true );
237  }
238  $result = new CheckResult(false);
239  if ( $c->want_dbversion[0] < $c->schema_major
240       || ($c->want_dbversion[0] == $c->schema_major && $c->want_dbversion[1] < $c->schema_minor)
241       || ($c->want_dbversion[0] == $c->schema_major
242              && $c->want_dbversion[1] == $c->schema_minor
243              && $c->want_dbversion[2] < $c->schema_patch)
244      )
245    {
246      $result->setClass('dep_warning');
247    }
248    $result->setDescription( sprintf(i18n('Want: %s, Currently: %s'), implode('.',$c->want_dbversion),
249            $c->schema_major.'.'.$c->schema_minor.'.'.$c->schema_patch));
250    return $result;
251}
252
253function check_davical_version() {
254  global $c;
255  if ( ! ini_get('allow_url_fopen') )
256    return new CheckResult( false, translate("Cannot determine upstream version, because PHP has set “<a href=\"https://secure.php.net/manual/en/filesystem.configuration.php#ini.allow-url-fopen\"><code>allow_url_fopen</code></a>” to “<code>FALSE</code>”."), 'dep_warning' );
257  $url = 'https://www.davical.org/current_davical_version?v='.$c->version_string;
258  $version_file = @fopen($url, 'r');
259  if ( ! $version_file ) return new CheckResult( false, translate("Could not retrieve") . " '$url'", 'dep_warning' );
260  $current_version = htmlentities( trim(fread( $version_file,12)) );
261  fclose($version_file);
262  $result = new CheckResult($c->version_string == $current_version);
263  if ( ! $result->getOK() ) {
264    if ( $c->version_string > $current_version ) {
265      $result->setClass('dep_ok');
266      $result->setDescription( sprintf(i18n('Stable: %s, We have: %s !'), $current_version, $c->version_string) );
267    }
268    else {
269      $result->setDescription( sprintf(i18n('Want: %s, Currently: %s'), $current_version, $c->version_string) );
270    }
271  } else {
272      $result->setDescription( sprintf(i18n('Passed: %s'), $c->version_string) );
273  }
274  return $result;
275}
276
277
278function check_awl_version() {
279  global $c;
280
281  if ( !function_exists('awl_version') ) return new CheckResult(false);
282
283  $result = new CheckResult($c->want_awl_version == awl_version());
284  if ( ! $result->getOK() ) {
285    $result->setDescription( sprintf(i18n('Want: %s, Currently: %s'), $c->want_awl_version, awl_version()) );
286    if ( $c->want_awl_version < awl_version() ) $result->setClass('dep_warning');
287  } else {
288    $result->setDescription( sprintf(i18n('Passed: %s'), $c->want_awl_version) );
289  }
290  return $result;
291
292}
293
294
295function build_site_statistics() {
296  $principals  = translate('No. of Principals');
297  $collections = translate('No. of Collections');
298  $resources   = translate('No. of Resources');
299  $table = <<<EOTABLE
300<table class="statistics">
301<tr><th>$principals</th><th>$collections</th><th>$resources</th></tr>
302<tr>%s</tr>
303</table>
304EOTABLE;
305
306  if ( !check_database_connection() ) {
307    return sprintf( $table, '<td colspan="3">'.translate('Site Statistics require the database to be available!').'</td>');
308  }
309  $sql = 'SELECT
310(SELECT count(1) FROM principal) AS principals,
311(SELECT count(1) FROM collection) AS collections,
312(SELECT count(1) FROM caldav_data) AS resources';
313  $qry = new AwlQuery($sql);
314  if ( $qry->Exec('setup',__LINE__,__FILE__) && $s = $qry->Fetch() ) {
315    $row = sprintf('<td align="center">%s</td><td align="center">%s</td><td align="center">%s</td>',
316                                       $s->principals, $s->collections, $s->resources );
317    return sprintf( $table, $row );
318  }
319  return sprintf( $table, '<td colspan="3">'.translate('Site Statistics require the database to be available!').'</td>');
320}
321
322
323function build_dependencies_table( ) {
324  global $c;
325
326  $dependencies = array(
327    translate('Current DAViCal version ')         => 'check_davical_version',
328    translate('AWL Library version ')             => 'check_awl_version',
329    translate('PHP not using Apache Filter mode') => 'check_real_php',
330    translate('PHP PDO module available')         => 'check_pdo',
331    translate('PDO PostgreSQL drivers')           => 'check_pdo_pgsql',
332    translate('Database is Connected')            => 'check_database_connection',
333    translate('DAViCal DB Schema version ')       => 'check_schema_version',
334    translate('GNU gettext support')              => 'check_gettext',
335    translate('PHP iconv support')                => 'check_iconv',
336    translate('PHP DateTime class')               => 'check_datetime',
337    translate('PHP XML support')                  => 'check_xml',
338    translate('Suhosin "server.strip" disabled')  => 'check_suhosin_server_strip',
339    translate('PHP Magic Quotes GPC off')         => 'check_magic_quotes_gpc',
340    translate('PHP Magic Quotes runtime off')     => 'check_magic_quotes_runtime',
341    translate('PHP calendar extension available') => 'check_calendar',
342    translate('PHP curl support')                 => 'check_curl'
343    );
344
345  if ( isset($c->authenticate_hook) && isset($c->authenticate_hook['call']) && $c->authenticate_hook['call'] == 'LDAP_check') {
346    $dependencies[translate('PHP LDAP module available')] = 'check_ldap';
347  }
348
349  $translated_failure_code = translate('<a href="https://wiki.davical.org/w/Setup_Failure_Codes/%s">Explanation on DAViCal Wiki</a>');
350
351  $dependencies_table = '';
352  $dep_tpl = '<tr class="%s">
353  <td>%s</td>
354  <td>%s</td>
355  <td>'.$translated_failure_code.'</td>
356</tr>
357';
358  foreach( $dependencies AS $k => $v ) {
359    $check_result = $v();
360    $dependencies_table .= sprintf( $dep_tpl, $check_result->getClass(),
361                             $k,
362                             $check_result->getDescription(),
363                             rawurlencode($k)
364                           );
365  }
366
367  return $dependencies_table;
368}
369
370
371$heading_setup = translate('Setup');
372$paragraph_setup = translate('This page primarily checks the environment needed for DAViCal to work correctly.  Suggestions or patches to make it do more useful stuff will be gratefully received.');
373
374/*
375$want_dbversion = implode('.',$c->want_dbversion);
376$heading_versions = translate('Current Versions');
377if ( check_schema_version() != true )
378{
379  $paragraph_versions = translate('You are currently running DAViCal version %s. The database schema should be at version %s and it is at version %d.%d.%d.');
380  $paragraph_versions = sprintf( $paragraph_versions, $c->version_string, $want_dbversion, $c->schema_major, $c->schema_minor, $c->schema_patch);
381} else {
382  $paragraph_versions = translate('You are currently running DAViCal version %s. The database schema is at version %d.%d.%d.');
383  $paragraph_versions = sprintf( $paragraph_versions, $c->version_string, $c->schema_major, $c->schema_minor, $c->schema_patch);
384}
385*/
386
387$heading_dependencies = translate('Dependencies');
388$th_dependency = translate('Dependency');
389$th_status     = translate('Status');
390$dependencies_table = build_dependencies_table();
391
392$heading_site_statistics = translate('Site Statistics');
393if ( check_database_connection()->GetOK() ) {
394  try {
395    $site_statistics_table = build_site_statistics();
396  }
397  catch( Exception $e ) {
398    $site_statistics_table = translate('Statistics unavailable');
399  }
400}
401else {
402  $site_statistics_table = translate('Statistics unavailable');
403}
404
405$heading_php_info = translate('PHP Information');
406
407// Translations shared with index.php
408$heading_clients = translate('Configuring Calendar Clients for DAViCal');
409$content_cli1 = translate('The <a href="https://www.davical.org/clients.php">client setup page on the DAViCal website</a> has information on how to configure Evolution, Sunbird, Lightning and Mulberry to use remotely hosted calendars.');
410$content_cli2 = translate('The administrative interface has no facility for viewing or modifying calendar data.');
411
412// Translations shared with index.php
413$heading_configure = translate('Configuring DAViCal');
414$content_config1 = translate('If you can read this then things must be mostly working already.');
415$content_config2 = ( $config_warnings == '' ? '' : '<div class="error"><h3 class="error">'
416             . translate('Your configuration produced PHP errors which should be corrected') . '</h3><pre>'
417             . $config_warnings.'</pre></div>'
418          );
419$content_config3 = translate('The <a href="https://www.davical.org/installation.php">DAViCal installation page</a> on the DAViCal website has some further information on how to install and configure this application.');
420
421
422  echo <<<EOBODY
423<style>
424tr.dep_ok {
425  background-color:#80ff80;
426}
427tr.dep_fail {
428  background-color:#ff8080;
429}
430tr.dep_warning {
431  background-color:#ffb040;
432}
433table, table.dependencies {
434  border: 1px grey solid;
435  border-collapse: collapse;
436  padding: 0.1em;
437  margin: 0 1em 1.5em;
438}
439table tr td, table tr th, table.dependencies tr td, table.dependencies tr th {
440  border: 1px grey solid;
441  padding: 0.1em 0.2em;
442}
443p {
444  padding: 0.3em 0.2em 0.7em;
445}
446</style>
447
448<h1>$heading_setup</h1>
449<p>$paragraph_setup
450
451<h2>$heading_dependencies</h2>
452<p>
453<table class="dependencies">
454<tr>
455<th>$th_dependency</th>
456<th>$th_status</th>
457</tr>
458$dependencies_table
459</table>
460</p>
461<h2>$heading_configure</h2>
462<p>$content_config1</p>
463$content_config2
464<p>$content_config3</p>
465
466<h2>$heading_clients</h2>
467<p>$content_cli1</p>
468<p>$content_cli2</p>
469
470<h2>$heading_site_statistics</h2>
471<p>$site_statistics_table</p>
472
473<h2>$heading_php_info</h2>
474<script language="javascript">
475function toggle_visible() {
476  var argv = toggle_visible.arguments;
477  var argc = argv.length;
478
479  var fld_checkbox =  document.getElementById(argv[0]);
480
481  if ( argc < 2 ) {
482    return;
483  }
484
485  for (var i = 1; i < argc; i++) {
486    var block_id = argv[i].substr(1);
487    var block_logical = argv[i].substr(0,1);
488    var b = document.getElementById(block_id);
489    if ( block_logical == '!' )
490      b.style.display = (fld_checkbox.checked ? 'none' : '');
491    else
492      b.style.display = (!fld_checkbox.checked ? 'none' : '');
493  }
494}
495</script><p><label>Show phpinfo() output:<input type="checkbox" value="1" id="fld_show_phpinfo" onclick="toggle_visible('fld_show_phpinfo','=phpinfo')"></label></p>
496<div style="display:none" id="phpinfo">$phpinfo</div>
497
498EOBODY;
499
500include("page-footer.php");
501