~josephjamesmills/zpanelcp/zpanelcp

« back to all changes in this revision

Viewing changes to backups/zpanelx-10-zpanelx/etc/apps/webmail/program/include/rcube_spellchecker.php

  • Committer: Joseph Mills
  • Date: 2012-05-12 06:38:23 UTC
  • Revision ID: josephjamesmills@gmail.com-20120512063823-nnb5w44xdkkbg8ds
made new framework and got ride of the backupfiles fixed amny of the bugs or tried too at least. added steps tpwards making ssl by default. Fixed apache virtual host files and moved to the right area. fixed all things too go under /var/www. and not /etc/. changed all the persission so that no one can read all files.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
<?php
2
 
 
3
 
/*
4
 
 +-----------------------------------------------------------------------+
5
 
 | program/include/rcube_spellchecker.php                                |
6
 
 |                                                                       |
7
 
 | This file is part of the Roundcube Webmail client                     |
8
 
 | Copyright (C) 2011, Kolab Systems AG                                  |
9
 
 | Copyright (C) 2008-2011, The Roundcube Dev Team                       |
10
 
 | Licensed under the GNU GPL                                            |
11
 
 |                                                                       |
12
 
 | PURPOSE:                                                              |
13
 
 |   Spellchecking using different backends                              |
14
 
 |                                                                       |
15
 
 +-----------------------------------------------------------------------+
16
 
 | Author: Aleksander Machniak <machniak@kolabsys.com>                   |
17
 
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
18
 
 +-----------------------------------------------------------------------+
19
 
 
20
 
 $Id: rcube_spellchecker.php 5181 2011-09-06 13:39:45Z alec $
21
 
 
22
 
*/
23
 
 
24
 
 
25
 
/**
26
 
 * Helper class for spellchecking with Googielspell and PSpell support.
27
 
 *
28
 
 * @package Core
29
 
 */
30
 
class rcube_spellchecker
31
 
{
32
 
    private $matches = array();
33
 
    private $engine;
34
 
    private $lang;
35
 
    private $rc;
36
 
    private $error;
37
 
    private $separator = '/[\s\r\n\t\(\)\/\[\]{}<>\\"]+|[:;?!,\.]([^\w]|$)/';
38
 
    private $options = array();
39
 
    private $dict;
40
 
    private $have_dict;
41
 
 
42
 
 
43
 
    // default settings
44
 
    const GOOGLE_HOST = 'ssl://www.google.com';
45
 
    const GOOGLE_PORT = 443;
46
 
    const MAX_SUGGESTIONS = 10;
47
 
 
48
 
 
49
 
    /**
50
 
     * Constructor
51
 
     *
52
 
     * @param string $lang Language code
53
 
     */
54
 
    function __construct($lang = 'en')
55
 
    {
56
 
        $this->rc     = rcmail::get_instance();
57
 
        $this->engine = $this->rc->config->get('spellcheck_engine', 'googie');
58
 
        $this->lang   = $lang ? $lang : 'en';
59
 
 
60
 
        if ($this->engine == 'pspell' && !extension_loaded('pspell')) {
61
 
            raise_error(array(
62
 
                'code' => 500, 'type' => 'php',
63
 
                'file' => __FILE__, 'line' => __LINE__,
64
 
                'message' => "Pspell extension not available"), true, true);
65
 
        }
66
 
 
67
 
        $this->options = array(
68
 
            'ignore_syms' => $this->rc->config->get('spellcheck_ignore_syms'),
69
 
            'ignore_nums' => $this->rc->config->get('spellcheck_ignore_nums'),
70
 
            'ignore_caps' => $this->rc->config->get('spellcheck_ignore_caps'),
71
 
            'dictionary'  => $this->rc->config->get('spellcheck_dictionary'),
72
 
        );
73
 
    }
74
 
 
75
 
 
76
 
    /**
77
 
     * Set content and check spelling
78
 
     *
79
 
     * @param string $text    Text content for spellchecking
80
 
     * @param bool   $is_html Enables HTML-to-Text conversion
81
 
     *
82
 
     * @return bool True when no mispelling found, otherwise false
83
 
     */
84
 
    function check($text, $is_html = false)
85
 
    {
86
 
        // convert to plain text
87
 
        if ($is_html) {
88
 
            $this->content = $this->html2text($text);
89
 
        }
90
 
        else {
91
 
            $this->content = $text;
92
 
        }
93
 
 
94
 
        if ($this->engine == 'pspell') {
95
 
            $this->matches = $this->_pspell_check($this->content);
96
 
        }
97
 
        else {
98
 
            $this->matches = $this->_googie_check($this->content);
99
 
        }
100
 
 
101
 
        return $this->found() == 0;
102
 
    }
103
 
 
104
 
 
105
 
    /**
106
 
     * Number of mispellings found (after check)
107
 
     *
108
 
     * @return int Number of mispellings
109
 
     */
110
 
    function found()
111
 
    {
112
 
        return count($this->matches);
113
 
    }
114
 
 
115
 
 
116
 
    /**
117
 
     * Returns suggestions for the specified word
118
 
     *
119
 
     * @param string $word The word
120
 
     *
121
 
     * @return array Suggestions list
122
 
     */
123
 
    function get_suggestions($word)
124
 
    {
125
 
        if ($this->engine == 'pspell') {
126
 
            return $this->_pspell_suggestions($word);
127
 
        }
128
 
 
129
 
        return $this->_googie_suggestions($word);
130
 
    }
131
 
 
132
 
 
133
 
    /**
134
 
     * Returns mispelled words
135
 
     *
136
 
     * @param string $text The content for spellchecking. If empty content
137
 
     *                     used for check() method will be used.
138
 
     *
139
 
     * @return array List of mispelled words
140
 
     */
141
 
    function get_words($text = null, $is_html=false)
142
 
    {
143
 
        if ($this->engine == 'pspell') {
144
 
            return $this->_pspell_words($text, $is_html);
145
 
        }
146
 
 
147
 
        return $this->_googie_words($text, $is_html);
148
 
    }
149
 
 
150
 
 
151
 
    /**
152
 
     * Returns checking result in XML (Googiespell) format
153
 
     *
154
 
     * @return string XML content
155
 
     */
156
 
    function get_xml()
157
 
    {
158
 
        // send output
159
 
        $out = '<?xml version="1.0" encoding="'.RCMAIL_CHARSET.'"?><spellresult charschecked="'.mb_strlen($this->content).'">';
160
 
 
161
 
        foreach ($this->matches as $item) {
162
 
            $out .= '<c o="'.$item[1].'" l="'.$item[2].'">';
163
 
            $out .= is_array($item[4]) ? implode("\t", $item[4]) : $item[4];
164
 
            $out .= '</c>';
165
 
        }
166
 
 
167
 
        $out .= '</spellresult>';
168
 
 
169
 
        return $out;
170
 
    }
171
 
 
172
 
 
173
 
    /**
174
 
     * Returns checking result (mispelled words with suggestions)
175
 
     *
176
 
     * @return array Spellchecking result. An array indexed by word.
177
 
     */
178
 
    function get()
179
 
    {
180
 
        $result = array();
181
 
 
182
 
        foreach ($this->matches as $item) {
183
 
            if ($this->engine == 'pspell') {
184
 
                $word = $item[0];
185
 
            }
186
 
            else {
187
 
                $word = mb_substr($this->content, $item[1], $item[2], RCMAIL_CHARSET);
188
 
            }
189
 
            $result[$word] = is_array($item[4]) ? implode("\t", $item[4]) : $item[4];
190
 
        }
191
 
 
192
 
        return $result;
193
 
    }
194
 
 
195
 
 
196
 
    /**
197
 
     * Returns error message
198
 
     *
199
 
     * @return string Error message
200
 
     */
201
 
    function error()
202
 
    {
203
 
        return $this->error;
204
 
    }
205
 
 
206
 
 
207
 
    /**
208
 
     * Checks the text using pspell
209
 
     *
210
 
     * @param string $text Text content for spellchecking
211
 
     */
212
 
    private function _pspell_check($text)
213
 
    {
214
 
        // init spellchecker
215
 
        $this->_pspell_init();
216
 
 
217
 
        if (!$this->plink) {
218
 
            return array();
219
 
        }
220
 
 
221
 
        // tokenize
222
 
        $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
223
 
 
224
 
        $diff       = 0;
225
 
        $matches    = array();
226
 
 
227
 
        foreach ($text as $w) {
228
 
            $word = trim($w[0]);
229
 
            $pos  = $w[1] - $diff;
230
 
            $len  = mb_strlen($word);
231
 
 
232
 
            // skip exceptions
233
 
            if ($this->is_exception($word)) {
234
 
            }
235
 
            else if (!pspell_check($this->plink, $word)) {
236
 
                $suggestions = pspell_suggest($this->plink, $word);
237
 
 
238
 
                    if (sizeof($suggestions) > self::MAX_SUGGESTIONS)
239
 
                        $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
240
 
 
241
 
                $matches[] = array($word, $pos, $len, null, $suggestions);
242
 
            }
243
 
 
244
 
            $diff += (strlen($word) - $len);
245
 
        }
246
 
 
247
 
        return $matches;
248
 
    }
249
 
 
250
 
 
251
 
    /**
252
 
     * Returns the mispelled words
253
 
     */
254
 
    private function _pspell_words($text = null, $is_html=false)
255
 
    {
256
 
        $result = array();
257
 
 
258
 
        if ($text) {
259
 
            // init spellchecker
260
 
            $this->_pspell_init();
261
 
 
262
 
            if (!$this->plink) {
263
 
                return array();
264
 
            }
265
 
 
266
 
            // With PSpell we don't need to get suggestions to return mispelled words
267
 
            if ($is_html) {
268
 
                $text = $this->html2text($text);
269
 
            }
270
 
 
271
 
            $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
272
 
 
273
 
            foreach ($text as $w) {
274
 
                $word = trim($w[0]);
275
 
 
276
 
                // skip exceptions
277
 
                if ($this->is_exception($word)) {
278
 
                    continue;
279
 
                }
280
 
 
281
 
                if (!pspell_check($this->plink, $word)) {
282
 
                    $result[] = $word;
283
 
                }
284
 
            }
285
 
 
286
 
            return $result;
287
 
        }
288
 
 
289
 
        foreach ($this->matches as $m) {
290
 
            $result[] = $m[0];
291
 
        }
292
 
 
293
 
        return $result;
294
 
    }
295
 
 
296
 
 
297
 
    /**
298
 
     * Returns suggestions for mispelled word
299
 
     */
300
 
    private function _pspell_suggestions($word)
301
 
    {
302
 
        // init spellchecker
303
 
        $this->_pspell_init();
304
 
 
305
 
        if (!$this->plink) {
306
 
            return array();
307
 
        }
308
 
 
309
 
        $suggestions = pspell_suggest($this->plink, $word);
310
 
 
311
 
        if (sizeof($suggestions) > self::MAX_SUGGESTIONS)
312
 
            $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS);
313
 
 
314
 
        return is_array($suggestions) ? $suggestions : array();
315
 
    }
316
 
 
317
 
 
318
 
    /**
319
 
     * Initializes PSpell dictionary
320
 
     */
321
 
    private function _pspell_init()
322
 
    {
323
 
        if (!$this->plink) {
324
 
            $this->plink = pspell_new($this->lang, null, null, RCMAIL_CHARSET, PSPELL_FAST);
325
 
        }
326
 
 
327
 
        if (!$this->plink) {
328
 
            $this->error = "Unable to load Pspell engine for selected language";
329
 
        }
330
 
    }
331
 
 
332
 
 
333
 
    private function _googie_check($text)
334
 
    {
335
 
        // spell check uri is configured
336
 
        $url = $this->rc->config->get('spellcheck_uri');
337
 
 
338
 
        if ($url) {
339
 
            $a_uri = parse_url($url);
340
 
            $ssl   = ($a_uri['scheme'] == 'https' || $a_uri['scheme'] == 'ssl');
341
 
            $port  = $a_uri['port'] ? $a_uri['port'] : ($ssl ? 443 : 80);
342
 
            $host  = ($ssl ? 'ssl://' : '') . $a_uri['host'];
343
 
            $path  = $a_uri['path'] . ($a_uri['query'] ? '?'.$a_uri['query'] : '') . $this->lang;
344
 
        }
345
 
        else {
346
 
            $host = self::GOOGLE_HOST;
347
 
            $port = self::GOOGLE_PORT;
348
 
            $path = '/tbproxy/spell?lang=' . $this->lang;
349
 
        }
350
 
 
351
 
        // Google has some problem with spaces, use \n instead
352
 
        $gtext = str_replace(' ', "\n", $text);
353
 
 
354
 
        $gtext = '<?xml version="1.0" encoding="utf-8" ?>'
355
 
            .'<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">'
356
 
            .'<text>' . $gtext . '</text>'
357
 
            .'</spellrequest>';
358
 
 
359
 
        $store = '';
360
 
        if ($fp = fsockopen($host, $port, $errno, $errstr, 30)) {
361
 
            $out = "POST $path HTTP/1.0\r\n";
362
 
            $out .= "Host: " . str_replace('ssl://', '', $host) . "\r\n";
363
 
            $out .= "Content-Length: " . strlen($gtext) . "\r\n";
364
 
            $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
365
 
            $out .= "Connection: Close\r\n\r\n";
366
 
            $out .= $gtext;
367
 
            fwrite($fp, $out);
368
 
 
369
 
            while (!feof($fp))
370
 
                $store .= fgets($fp, 128);
371
 
            fclose($fp);
372
 
        }
373
 
 
374
 
        if (!$store) {
375
 
            $this->error = "Empty result from spelling engine";
376
 
        }
377
 
 
378
 
        preg_match_all('/<c o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/c>/', $store, $matches, PREG_SET_ORDER);
379
 
 
380
 
        // skip exceptions (if appropriate options are enabled)
381
 
        if (!empty($this->options['ignore_syms']) || !empty($this->options['ignore_nums'])
382
 
            || !empty($this->options['ignore_caps']) || !empty($this->options['dictionary'])
383
 
        ) {
384
 
            foreach ($matches as $idx => $m) {
385
 
                $word = mb_substr($text, $m[1], $m[2], RCMAIL_CHARSET);
386
 
                // skip  exceptions
387
 
                if ($this->is_exception($word)) {
388
 
                    unset($matches[$idx]);
389
 
                }
390
 
            }
391
 
        }
392
 
 
393
 
        return $matches;
394
 
    }
395
 
 
396
 
 
397
 
    private function _googie_words($text = null, $is_html=false)
398
 
    {
399
 
        if ($text) {
400
 
            if ($is_html) {
401
 
                $text = $this->html2text($text);
402
 
            }
403
 
 
404
 
            $matches = $this->_googie_check($text);
405
 
        }
406
 
        else {
407
 
            $matches = $this->matches;
408
 
            $text    = $this->content;
409
 
        }
410
 
 
411
 
        $result = array();
412
 
 
413
 
        foreach ($matches as $m) {
414
 
            $result[] = mb_substr($text, $m[1], $m[2], RCMAIL_CHARSET);
415
 
        }
416
 
 
417
 
        return $result;
418
 
    }
419
 
 
420
 
 
421
 
    private function _googie_suggestions($word)
422
 
    {
423
 
        if ($word) {
424
 
            $matches = $this->_googie_check($word);
425
 
        }
426
 
        else {
427
 
            $matches = $this->matches;
428
 
        }
429
 
 
430
 
        if ($matches[0][4]) {
431
 
            $suggestions = explode("\t", $matches[0][4]);
432
 
            if (sizeof($suggestions) > self::MAX_SUGGESTIONS) {
433
 
                $suggestions = array_slice($suggestions, 0, MAX_SUGGESTIONS);
434
 
            }
435
 
 
436
 
            return $suggestions;
437
 
        }
438
 
 
439
 
        return array();
440
 
    }
441
 
 
442
 
 
443
 
    private function html2text($text)
444
 
    {
445
 
        $h2t = new html2text($text, false, true, 0);
446
 
        return $h2t->get_text();
447
 
    }
448
 
 
449
 
 
450
 
    /**
451
 
     * Check if the specified word is an exception accoring to 
452
 
     * spellcheck options.
453
 
     *
454
 
     * @param string  $word  The word
455
 
     *
456
 
     * @return bool True if the word is an exception, False otherwise
457
 
     */
458
 
    public function is_exception($word)
459
 
    {
460
 
        // Contain only symbols (e.g. "+9,0", "2:2")
461
 
        if (!$word || preg_match('/^[0-9@#$%^&_+~*=:;?!,.-]+$/', $word))
462
 
            return true;
463
 
 
464
 
        // Contain symbols (e.g. "g@@gle"), all symbols excluding separators
465
 
        if (!empty($this->options['ignore_syms']) && preg_match('/[@#$%^&_+~*=-]/', $word))
466
 
            return true;
467
 
 
468
 
        // Contain numbers (e.g. "g00g13")
469
 
        if (!empty($this->options['ignore_nums']) && preg_match('/[0-9]/', $word))
470
 
            return true;
471
 
 
472
 
        // Blocked caps (e.g. "GOOGLE")
473
 
        if (!empty($this->options['ignore_caps']) && $word == mb_strtoupper($word))
474
 
            return true;
475
 
 
476
 
        // Use exceptions from dictionary
477
 
        if (!empty($this->options['dictionary'])) {
478
 
            $this->load_dict();
479
 
 
480
 
            // @TODO: should dictionary be case-insensitive?
481
 
            if (!empty($this->dict) && in_array($word, $this->dict))
482
 
                return true;
483
 
        }
484
 
 
485
 
        return false;
486
 
    }
487
 
 
488
 
 
489
 
    /**
490
 
     * Add a word to dictionary
491
 
     *
492
 
     * @param string  $word  The word to add
493
 
     */
494
 
    public function add_word($word)
495
 
    {
496
 
        $this->load_dict();
497
 
 
498
 
        foreach (explode(' ', $word) as $word) {
499
 
            // sanity check
500
 
            if (strlen($word) < 512) {
501
 
                $this->dict[] = $word;
502
 
                $valid = true;
503
 
            }
504
 
        }
505
 
 
506
 
        if ($valid) {
507
 
            $this->dict = array_unique($this->dict);
508
 
            $this->update_dict();
509
 
        }
510
 
    }
511
 
 
512
 
 
513
 
    /**
514
 
     * Remove a word from dictionary
515
 
     *
516
 
     * @param string  $word  The word to remove
517
 
     */
518
 
    public function remove_word($word)
519
 
    {
520
 
        $this->load_dict();
521
 
 
522
 
        if (($key = array_search($word, $this->dict)) !== false) {
523
 
            unset($this->dict[$key]);
524
 
            $this->update_dict();
525
 
        }
526
 
    }
527
 
 
528
 
 
529
 
    /**
530
 
     * Update dictionary row in DB
531
 
     */
532
 
    private function update_dict()
533
 
    {
534
 
        if (strcasecmp($this->options['dictionary'], 'shared') != 0) {
535
 
            $userid = (int) $this->rc->user->ID;
536
 
        }
537
 
 
538
 
        $plugin = $this->rc->plugins->exec_hook('spell_dictionary_save', array(
539
 
            'userid' => $userid, 'language' => $this->lang, 'dictionary' => $this->dict));
540
 
 
541
 
        if (!empty($plugin['abort'])) {
542
 
            return;
543
 
        }
544
 
 
545
 
        if ($this->have_dict) {
546
 
            if (!empty($this->dict)) {
547
 
                $this->rc->db->query(
548
 
                    "UPDATE ".get_table_name('dictionary')
549
 
                    ." SET data = ?"
550
 
                    ." WHERE user_id " . ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL")
551
 
                        ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
552
 
                    implode(' ', $plugin['dictionary']), $plugin['language']);
553
 
            }
554
 
            // don't store empty dict
555
 
            else {
556
 
                $this->rc->db->query(
557
 
                    "DELETE FROM " . get_table_name('dictionary')
558
 
                    ." WHERE user_id " . ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL")
559
 
                        ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
560
 
                    $plugin['language']);
561
 
            }
562
 
        }
563
 
        else if (!empty($this->dict)) {
564
 
            $this->rc->db->query(
565
 
                "INSERT INTO " .get_table_name('dictionary')
566
 
                ." (user_id, " . $this->rc->db->quoteIdentifier('language') . ", data) VALUES (?, ?, ?)",
567
 
                $plugin['userid'], $plugin['language'], implode(' ', $plugin['dictionary']));
568
 
        }
569
 
    }
570
 
 
571
 
 
572
 
    /**
573
 
     * Get dictionary from DB
574
 
     */
575
 
    private function load_dict()
576
 
    {
577
 
        if (is_array($this->dict)) {
578
 
            return $this->dict;
579
 
        }
580
 
 
581
 
        if (strcasecmp($this->options['dictionary'], 'shared') != 0) {
582
 
            $userid = (int) $this->rc->user->ID;
583
 
        }
584
 
 
585
 
        $plugin = $this->rc->plugins->exec_hook('spell_dictionary_get', array(
586
 
            'userid' => $userid, 'language' => $this->lang, 'dictionary' => array()));
587
 
 
588
 
        if (empty($plugin['abort'])) {
589
 
            $dict = array();
590
 
            $this->rc->db->query(
591
 
                "SELECT data FROM ".get_table_name('dictionary')
592
 
                ." WHERE user_id ". ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL")
593
 
                    ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
594
 
                $plugin['language']);
595
 
 
596
 
            if ($sql_arr = $this->rc->db->fetch_assoc($sql_result)) {
597
 
                $this->have_dict = true;
598
 
                if (!empty($sql_arr['data'])) {
599
 
                    $dict = explode(' ', $sql_arr['data']);
600
 
                }
601
 
            }
602
 
 
603
 
            $plugin['dictionary'] = array_merge((array)$plugin['dictionary'], $dict);
604
 
        }
605
 
 
606
 
        if (!empty($plugin['dictionary']) && is_array($plugin['dictionary'])) {
607
 
            $this->dict = $plugin['dictionary'];
608
 
        }
609
 
        else {
610
 
            $this->dict = array();
611
 
        }
612
 
 
613
 
        return $this->dict;
614
 
    }
615
 
 
616
 
}