1<?php
2
3require_once('../config.inc.php');
4
5require_once(HTML2PS_DIR.'stubs.common.inc.php');
6require_once(HTML2PS_DIR.'error.php');
7require_once(HTML2PS_DIR.'config.parse.php');
8
9parse_config_file('../html2ps.config');
10
11define('CHECK_STATUS_FAILED',  0);
12define('CHECK_STATUS_WARNING', 1);
13define('CHECK_STATUS_SUCCESS', 2);
14
15error_reporting(E_ALL);
16ini_set("display_errors","1");
17
18$__g_registered_checks = array();
19
20function out_header() {
21  readfile('systemcheck.header.tpl');
22}
23
24function out_footer() {
25  readfile('systemcheck.footer.tpl');
26}
27
28function status2class($status) {
29  $mapping = array(CHECK_STATUS_FAILED  => "failed",
30                   CHECK_STATUS_WARNING => "warning",
31                   CHECK_STATUS_SUCCESS => "success");
32  if (isset($mapping[$status])) {
33    return $mapping[$status];
34  };
35
36  error_log(sprintf("Unknown status code passed to 'status2class': %s", $status));
37  return "unknown";
38}
39
40function out_check_list() {
41  $checks = ManagerChecks::getChecks();
42  foreach ($checks as $check) {
43    $title   = htmlspecialchars($check->title());
44    $message = nl2br($check->getMessage());
45    $status_class = status2class($check->getStatus());
46
47    print <<<EOF
48<div class="check">
49<div class="title ${status_class}">${title}</div>
50<div class="message">${message}</div>
51</div>
52EOF;
53  };
54}
55
56class ManagerChecks {
57  function register($check) {
58    global $__g_registered_checks;
59    $__g_registered_checks[] = $check;
60  }
61
62  function run() {
63    global $__g_registered_checks;
64    $size = count($__g_registered_checks);
65    for ($i=0; $i<$size; $i++) {
66      $__g_registered_checks[$i]->run();
67    };
68  }
69
70  function getChecks() {
71    global $__g_registered_checks;
72    return $__g_registered_checks;
73  }
74}
75
76class CheckSimple {
77  var $_message;
78
79  /**
80   * Invariants
81   */
82  function title() {
83    error_no_method('title', get_class($this));
84  }
85
86  function description() {
87    error_no_method('description', get_class($this));
88  }
89
90  /**
91   * Start checking
92   */
93  function run() {
94    error_no_method('run', get_class($this));
95  }
96
97  /**
98   * Get check status code; status code should be one of the following
99   * predefined constants:
100   * CHECK_STATUS_FAILED  - check failed, script will not work unless this issue is fixed
101   * CHECK_STATUS_WARNING - check succeeded, script may encounter minor issues
102   * CHECK_STATUS_SUCCESS - check succeeded without any problems
103   *
104   * @return Integer Status code
105   */
106  function getStatus() {
107    error_no_method('status', get_class($this));
108  }
109
110  /**
111   * Returns a short human-readable  message describing results of the
112   * check run. By default, this message is generated in 'run' method
113   * (overridden in CheckSimple children) and stored via 'setMessage'
114   *
115   * @return String description of the test results
116   */
117  function getMessage() {
118    return $this->_message;
119  }
120
121  function setMessage($message) {
122    $this->_message = $message;
123  }
124}
125
126/**
127 */
128class CheckBinary extends CheckSimple {
129  var $_success;
130
131  function setSuccess($success) {
132    $this->_success = $success;
133  }
134
135  function getSuccess() {
136    return $this->_success;
137  }
138}
139
140/**
141 */
142class CheckBinaryRequired extends CheckBinary {
143  function getStatus() {
144    if ($this->getSuccess()) {
145      return CHECK_STATUS_SUCCESS;
146    } else {
147      return CHECK_STATUS_FAILED;
148    };
149  }
150}
151
152/**
153 */
154class CheckBinaryRecommended extends CheckBinary {
155  function getStatus() {
156    if ($this->getSuccess()) {
157      return CHECK_STATUS_SUCCESS;
158    } else {
159      return CHECK_STATUS_WARNING;
160    };
161  }
162}
163
164/**
165 */
166class CheckTriState extends CheckSimple {
167  var $_status;
168
169  function getStatus() {
170    return $this->_status;
171  }
172
173  function setStatus($status) {
174    $this->_status = $status;
175  }
176}
177
178/**
179 * Actual checks
180 */
181
182/**
183 * PHP version
184 */
185class CheckPHPVersion extends CheckTriState {
186  function title() {
187    return "PHP Version";
188  }
189
190  function description() {
191    return "";
192  }
193
194  function run() {
195    //    > "4.3.0";
196  }
197}
198// ManagerChecks::register(new CheckPHPVersion());
199
200/**
201 * Required / recommended extensions
202 */
203
204/**
205 * Presense of DOM/XML extensions
206 */
207class CheckDOM extends CheckTriState {
208  function title() {
209    return "XML DOM extension";
210  }
211
212  function description() {
213    return "HTML files are parsed using XML DOM extensions";
214  }
215
216  function run() {
217    if (function_exists('domxml_open_mem') ||
218        class_exists('DOMDocument')) {
219      $this->setStatus(CHECK_STATUS_SUCCESS);
220      $this->setMessage('Native XML DOM extension found');
221      return;
222    };
223
224    if (file_exists(HTML2PS_DIR.'classes/include.php')) {
225      $this->setStatus(CHECK_STATUS_WARNING);
226      $this->setMessage('No native XML DOM extension found, falling back to Active-State DOM XML. Note that it is <b>highly recommended to use native PHP XML DOM extension</b>.');
227      return;
228    };
229
230    $this->setStatus(CHECK_STATUS_FAILED);
231    $this->setMessage('No XML DOM extension found');
232  }
233}
234
235/**
236 * Presense of PDFLIB extension
237 */
238class CheckPDFLIB extends CheckBinaryRecommended {
239}
240
241/**
242 * Presense of Curl extension
243 */
244class CheckCurl extends CheckBinaryRecommended {
245  function title() {
246    return "Curl PHP Extension";
247  }
248
249  function description() {
250    return "Curl PHP extension is recommended for fetching files via HTTP protocol";
251  }
252
253  function run() {
254    $this->setSuccess(false);
255
256    if (!extension_loaded('curl')) {
257      $this->setMessage('Missing Curl extension. Script will use pure-PHP fallback (allow_url_fopen=On is required!). <b>Proxy support is not available</b>');
258      return;
259    };
260
261    $version = curl_version();
262    // PHP 5.0.1 and greater return array instead of string
263    if (is_array($version)) {
264      $version = $version['version'];
265    };
266    $this->setMessage(sprintf('Found Curl extension version %s.', $version['version']));
267    $this->setSuccess(true);
268  }
269}
270
271/**
272 * Presense of GD extension
273 */
274class CheckGD extends CheckBinaryRequired {
275  function title() {
276    return "GD PHP Extension";
277  }
278
279  function description() {
280    return "GD PHP extension is required for graphic file processing";
281  }
282
283  function run() {
284    $this->setSuccess(false);
285
286    if (!extension_loaded('gd')) {
287      $this->setMessage('Missing GD extension. Please refer to <a href="http://php.net/gd">PHP.net instructions</a> on installing/enabling this extension.');
288      return;
289    };
290
291    $gd_info = gd_info();
292    $gd_version_string = $gd_info['GD Version'];
293
294    /**
295     * Extract version number if it is a bundled version; otherwise we assume that
296     * version string should contain verions number only
297     */
298    if (preg_match("/bundled \(([\d\.]+) compatible\)/", $gd_version_string, $matches)) {
299      $gd_version = $matches[1];
300    } else {
301      $gd_version = $gd_version_string;
302    };
303
304    if (!function_exists('imagecreatetruecolor')) {
305      $this->setMessage("GD version 2.0.1+ required for 'imagecreatetruecolor' function to work");
306      return;
307    };
308
309    $this->setMessage("Found GD version $gd_version.");
310    $this->setSuccess(true);
311  }
312}
313
314/**
315 * Presense of ZLIB extension (compressed files)
316 */
317class CheckZLIB extends CheckBinaryRecommended {
318}
319
320/**
321 * System limits & settings
322 */
323
324/**
325 * Execution time limit
326 */
327class CheckMaxExecutionTime extends CheckTriState {
328}
329
330/**
331 * Memory limit
332 */
333class CheckMemoryLimit extends CheckTriState {
334}
335
336/**
337 * Allow_url_fopen setting
338 */
339class CheckAllowURLFopen extends CheckBinaryRecommended {
340  function title() {
341    return "allow_url_fopen ini setting";
342  }
343
344  function description() {
345    return "allow_url_fopen should be enabled when CURL extension is not available";
346  }
347
348  function run() {
349    $this->setSuccess(false);
350
351    $setting = ini_get('allow_url_fopen');
352    if (!$setting) {
353      $this->setMessage('<a href="http://php.net/filesystem">allow_url_fopen</a> is disabled. You will not be able to fetch files via HTTP without CURL extension.');
354      return;
355    }
356
357    $this->setMessage('allow_url_fopen is enabled');
358    $this->setSuccess(true);
359  }
360}
361
362
363/**
364 * pcre.backtrack_limit setting (PHP 5.2)
365 */
366class CheckPCREBacktrack extends CheckBinaryRecommended {
367  function title() {
368    return "pcre.backtrack_limit ini setting";
369  }
370
371  function description() {
372    return "It is recommended to increase pcre.backtrack_limit value to 1,000,000";
373  }
374
375  function run() {
376    $this->setSuccess(false);
377
378    $version = explode('.', PHP_VERSION);
379    if ($version[0] < 5 ||
380        ($version[0] == 5 && $version[1] < 2)) {
381      $this->setMessage('pcre.backtrack_limit is not available in PHP prior to 5.2.0');
382      $this->setSuccess(true);
383      return;
384    };
385
386    $setting = ini_get('pcre.backtrack_limit');
387    if ($setting < 1000000) {
388      $this->setMessage(sprintf('<a href="http://php.net/filesystem">pcre.backtrack_limit</a> is set to %s (less than 1,000,000). You could experience issues converting large pages.',
389                                $setting));
390      return;
391    }
392
393    $this->setMessage('pcre.backtrack_limit is greater than 1,000,000');
394    $this->setSuccess(true);
395  }
396}
397
398
399/**
400 * Access/permissions
401 */
402
403/**
404 * permissions on cache directory
405 */
406class CheckPermissionsCache extends CheckBinaryRequired {
407  function title() {
408    return "Permissions on 'cache' subdirectory";
409  }
410
411  function description() {
412    return "Script should have full access to 'cache' subdirectory to keep cached files there";
413  }
414
415  function run() {
416    if (!file_exists(HTML2PS_DIR.'/cache/')) {
417      $this->setMessage("'cache' subdirectory is missing");
418      $this->setSuccess(false);
419      return;
420    };
421
422    if (!is_readable(HTML2PS_DIR.'/cache/')) {
423      $this->setMessage("'cache' subdirectory is not readable");
424      $this->setSuccess(false);
425      return;
426    };
427
428    if (!is_writable(HTML2PS_DIR.'/cache/')) {
429      $this->setMessage("'cache' subdirectory is not writable");
430      $this->setSuccess(false);
431      return;
432    };
433
434    if (!is_executable(HTML2PS_DIR.'/cache/') && PHP_OS != "WINNT") {
435      $this->setMessage("'cache' subdirectory is not executable");
436      $this->setSuccess(false);
437      return;
438    };
439
440    $this->setMessage("'cache' subdirectory is fully accessible to the script");
441    $this->setSuccess(true);
442  }
443}
444
445/**
446 * Permissions on 'out' directory
447 */
448class CheckPermissionsOut extends CheckBinaryRecommended {
449  function title() {
450    return "Permissions on 'out' subdirectory";
451  }
452
453  function description() {
454    return "Script should have full access to 'out' subdirectory to put generated files there";
455  }
456
457  function run() {
458    if (!file_exists(HTML2PS_DIR.'/out/')) {
459      $this->setMessage("'out' subdirectory is missing");
460      $this->setSuccess(false);
461      return;
462    };
463
464    if (!is_readable(HTML2PS_DIR.'/out/')) {
465      $this->setMessage("'out' subdirectory is not readable");
466      $this->setSuccess(false);
467      return;
468    };
469
470    if (!is_writable(HTML2PS_DIR.'/out/')) {
471      $this->setMessage("'out' subdirectory is not writable");
472      $this->setSuccess(false);
473      return;
474    };
475
476    if (!is_executable(HTML2PS_DIR.'/out/') && PHP_OS != "WINNT") {
477      $this->setMessage("'out' subdirectory is not executable");
478      $this->setSuccess(false);
479      return;
480    };
481
482    $this->setMessage("'out' subdirectory is fully accessible to the script");
483    $this->setSuccess(true);
484  }
485}
486
487/**
488 * Permissions on 'temp' directory (system-dependent)
489 */
490class CheckPermissionsTemp extends CheckBinaryRequired {
491  function title() {
492    return "Permissions on 'temp' subdirectory";
493  }
494
495  function description() {
496    return "Script should have full access to 'temp' subdirectory to keep temporary files there";
497  }
498
499  function run() {
500    if (!file_exists(HTML2PS_DIR.'/temp/')) {
501      $this->setMessage("'temp' subdirectory is missing");
502      $this->setSuccess(false);
503      return;
504    };
505
506    if (!is_readable(HTML2PS_DIR.'/temp/')) {
507      $this->setMessage("'temp' subdirectory is not readable");
508      $this->setSuccess(false);
509      return;
510    };
511
512    if (!is_writable(HTML2PS_DIR.'/temp/')) {
513      $this->setMessage("'temp' subdirectory is not writable");
514      $this->setSuccess(false);
515      return;
516    };
517
518    if (!is_executable(HTML2PS_DIR.'/temp/') && PHP_OS != "WINNT") {
519      $this->setMessage("'temp' subdirectory is not executable");
520      $this->setSuccess(false);
521      return;
522    };
523
524    $this->setMessage("'temp' subdirectory is fully accessible to the script");
525    $this->setSuccess(true);
526  }
527}
528
529/**
530 * Permissions/availability of GS executable
531 */
532
533/**
534 * Permissions of fonts directory
535 */
536class CheckPermissionsFonts extends CheckBinaryRequired {
537  function title() {
538    return "Permissions on 'fonts' subdirectory";
539  }
540
541  function description() {
542    return "Script should be able to read 'fonts' subdirectory to access installed fonts";
543  }
544
545  function run() {
546    if (!file_exists(HTML2PS_DIR.'/fonts/')) {
547      $this->setMessage("'fonts' subdirectory is missing");
548      $this->setSuccess(false);
549      return;
550    };
551
552    if (!is_readable(HTML2PS_DIR.'/fonts/')) {
553      $this->setMessage("'fonts' subdirectory is not readable");
554      $this->setSuccess(false);
555      return;
556    };
557
558    if (!is_executable(HTML2PS_DIR.'/fonts/') && PHP_OS != "WINNT") {
559      $this->setMessage("'fonts' subdirectory is not executable");
560      $this->setSuccess(false);
561      return;
562    };
563
564    $this->setMessage("'fonts' subdirectory is readable and executable by the script");
565    $this->setSuccess(true);
566  }
567}
568
569/**
570 * Permissions/presence of Type1 fonts repository
571 */
572class CheckPermissionsType1 extends CheckBinaryRecommended {
573  function title() {
574    return "Permissions on Type1 fonts directory";
575  }
576
577  function description() {
578    return "Script should be able to access Type1 fonts directory containing font metrics in order to generate Postscript files";
579  }
580
581  function run() {
582    if (!file_exists(TYPE1_FONTS_REPOSITORY)) {
583      $this->setMessage("Type1 fonts directory (".TYPE1_FONTS_REPOSITORY.") is missing. You will not be able to generate postscript files.");
584      $this->setSuccess(false);
585      return;
586    };
587
588    if (!is_readable(TYPE1_FONTS_REPOSITORY)) {
589      $this->setMessage("Type1 fonts directory (".TYPE1_FONTS_REPOSITORY.") is not readable. You will not be able to generate postscript files.");
590      $this->setSuccess(false);
591      return;
592    };
593
594    if (!is_executable(HTML2PS_DIR.'/fonts/') && PHP_OS != "WINNT") {
595      $this->setMessage("Type1 fonts directory (".TYPE1_FONTS_REPOSITORY.") is not executable. You will not be able to generate postscript files.");
596      $this->setSuccess(false);
597      return;
598    };
599
600    $this->setMessage("Type1 fonts directory is readable and executable by the script");
601    $this->setSuccess(true);
602  }
603}
604
605/**
606 * Fonts
607 */
608
609/**
610 * Permissions/presense of TTF files
611 */
612class CheckPresenceTTF extends CheckBinaryRecommended {
613  function title() {
614    return "Presense of registered TTF files";
615  }
616
617  function description() {
618    return "TrueType fonts registered in html2ps.config should be present in order to generate PDF files with these fonts.";
619  }
620
621  function run() {
622    $message = "";
623    $this->setSuccess(true);
624
625    global $g_font_resolver_pdf;
626    foreach ($g_font_resolver_pdf->ttf_mappings as $file) {
627      $fullname = HTML2PS_DIR.'/fonts/'.$file;
628
629      if (!file_exists($fullname)) {
630        $message .= "Font ".$fullname." is missing. You will not be able to generate PDF files with this font.\n";
631        $this->setSuccess(false);
632      } elseif (!file_exists($fullname)) {
633        $message .= "Font ".$fullname." is not readable. You will not be able to generate PDF files with this font.\n";
634        $this->setSuccess(false);
635      } else {
636        $message .= "Font ".$fullname." is present and readable.\n";
637      };
638    };
639
640    $this->setMessage($message);
641  }
642}
643
644/**
645 * Permissions/presense of Type1 fonts
646 */
647
648/**
649 * Permissions/presense of AFM files for Type1 fonts
650 */
651class CheckPresenceType1AFM extends CheckBinaryRecommended {
652  function title() {
653    return "Presense of registered TTF files";
654  }
655
656  function description() {
657    return "TrueType fonts registered in html2ps.config should be present in order to generate PDF files with these fonts.";
658  }
659
660  function run() {
661    $message = "";
662    $this->setSuccess(true);
663
664    global $g_font_resolver;
665    foreach ($g_font_resolver->afm_mappings as $file) {
666      $fullname = TYPE1_FONTS_REPOSITORY.$file.'.afm';
667
668      if (!file_exists($fullname)) {
669        $message .= "Font ".$fullname." is missing. You will not be able to generate PDF files with this font.\n";
670        $this->setSuccess(false);
671      } elseif (!file_exists($fullname)) {
672        $message .= "Font ".$fullname." is not readable. You will not be able to generate PDF files with this font.\n";
673        $this->setSuccess(false);
674      } else {
675        $message .= "Font ".$fullname." is present and readable.\n";
676      };
677    };
678
679    $this->setMessage($message);
680  }
681}
682
683/**
684 * Graphics
685 */
686
687/**
688 * Generic
689 */
690class CheckGDFormat extends CheckBinaryRequired {
691  function title() {
692    return "GD ".$this->_getFormatName()." Support";
693  }
694
695  function description() {
696    return "GD ".$this->_getFormatName()." Support is required for reading images in ".$this->_getFormatName()." format";
697  }
698
699  function run() {
700    $this->setSuccess(false);
701
702    if (!extension_loaded('gd')) {
703      $this->setMessage('Missing GD extension. Please refer to <a href="http://php.net/gd">PHP.net instructions</a> on installing/enabling this extension.');
704      return;
705    };
706
707    $gd_info = gd_info();
708    if (!$gd_info[$this->_getInfoKey()]) {
709      $this->setMessage("No ".$this->_getFormatName()." support, some images will not be displayed");
710      return;
711    };
712
713    $this->setMessage($this->_getFormatName()." support enabled");
714    $this->setSuccess(true);
715  }
716}
717
718/**
719 * JPEG support
720 */
721class CheckGDJPEG extends CheckGDFormat {
722  function _getFormatName() {
723    return "JPEG";
724  }
725
726  function _getInfoKey() {
727    return "JPG Support";
728  }
729}
730
731/**
732 * GIF support
733 */
734class CheckGDGIF extends CheckGDFormat {
735  function _getFormatName() {
736    return "GIF";
737  }
738
739  function _getInfoKey() {
740    return "GIF Read Support";
741  }
742}
743
744/**
745 * PNG support
746 */
747class CheckGDPNG extends CheckGDFormat {
748  function _getFormatName() {
749    return "PNG";
750  }
751
752  function _getInfoKey() {
753    return "PNG Support";
754  }
755}
756
757/**
758 * Freetype support
759 */
760
761/**
762 * Miscellanous
763 */
764
765/**
766 * Check if outgoing connections are allowed
767 */
768class CheckOutgoingConnections extends CheckBinary {
769}
770
771ManagerChecks::register(new CheckDOM());
772ManagerChecks::register(new CheckCurl());
773ManagerChecks::register(new CheckAllowURLFopen());
774ManagerChecks::register(new CheckPCREBacktrack());
775ManagerChecks::register(new CheckGD());
776ManagerChecks::register(new CheckGDJPEG());
777ManagerChecks::register(new CheckGDGIF());
778ManagerChecks::register(new CheckGDPNG());
779ManagerChecks::register(new CheckPermissionsTemp());
780ManagerChecks::register(new CheckPermissionsFonts());
781ManagerChecks::register(new CheckPermissionsType1());
782ManagerChecks::register(new CheckPresenceTTF());
783ManagerChecks::register(new CheckPresenceType1AFM());
784
785ManagerChecks::run();
786out_header();
787out_check_list();
788out_footer();
789
790?>