xref: /dokuwiki/inc/Search/Indexer.php (revision 4027a91aac0d8a078226f0e5beb2158d508a1897)
16225b270SMichael Große<?php
26225b270SMichael Große
36225b270SMichael Großenamespace dokuwiki\Search;
46225b270SMichael Große
56225b270SMichael Großeuse dokuwiki\Extension\Event;
6*4027a91aSSatoshi Saharause dokuwiki\Search\PagewordIndex;
7*4027a91aSSatoshi Saharause dokuwiki\Search\MetadataIndex;
8*4027a91aSSatoshi Sahara
9*4027a91aSSatoshi Sahara// Version tag used to force rebuild on upgrade
10*4027a91aSSatoshi Saharaconst INDEXER_VERSION = 8;
116225b270SMichael Große
126225b270SMichael Große/**
13*4027a91aSSatoshi Sahara * Class DokuWiki Indexer (Singleton)
146225b270SMichael Große *
15*4027a91aSSatoshi Sahara * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
166225b270SMichael Große * @author     Andreas Gohr <andi@splitbrain.org>
17*4027a91aSSatoshi Sahara * @author Tom N Harris <tnharris@whoopdedo.org>
186225b270SMichael Große */
19*4027a91aSSatoshi Saharaclass Indexer extends AbstractIndex
20*4027a91aSSatoshi Sahara{
21*4027a91aSSatoshi Sahara    /** @var Indexer $instance */
22*4027a91aSSatoshi Sahara    protected static $instance = null;
236225b270SMichael Große
24*4027a91aSSatoshi Sahara    /**
25*4027a91aSSatoshi Sahara     * Get new or existing singleton instance of the Indexer
26*4027a91aSSatoshi Sahara     *
27*4027a91aSSatoshi Sahara     * @return Indexer
28*4027a91aSSatoshi Sahara     */
29*4027a91aSSatoshi Sahara    public static function getInstance()
30*4027a91aSSatoshi Sahara    {
31*4027a91aSSatoshi Sahara        if (is_null(static::$instance)) {
32*4027a91aSSatoshi Sahara            static::$instance = new static();
336225b270SMichael Große        }
34*4027a91aSSatoshi Sahara        return static::$instance;
356225b270SMichael Große    }
366225b270SMichael Große
376225b270SMichael Große    /**
38*4027a91aSSatoshi Sahara     * Dispatch Indexing request for the page, called by TaskRunner::runIndexer()
396225b270SMichael Große     *
40*4027a91aSSatoshi Sahara     * @param string        $page   name of the page to index
41*4027a91aSSatoshi Sahara     * @param bool          $verbose    print status messages
42*4027a91aSSatoshi Sahara     * @param bool          $force  force reindexing even when the index is up to date
43*4027a91aSSatoshi Sahara     * @return bool  If the function completed successfully
446225b270SMichael Große     *
456225b270SMichael Große     * @author Tom N Harris <tnharris@whoopdedo.org>
46*4027a91aSSatoshi Sahara     * @author Satoshi Sahara <sahara.satoshi@gmail.com>
476225b270SMichael Große     */
48*4027a91aSSatoshi Sahara    public function dispatch($page, $verbose = false, $force = false)
49*4027a91aSSatoshi Sahara    {
50*4027a91aSSatoshi Sahara        // check if page was deleted but is still in the index
51*4027a91aSSatoshi Sahara        if (!page_exists($page)) {
52*4027a91aSSatoshi Sahara            $result = $this->deletePage($page, $verbose, $force);
53*4027a91aSSatoshi Sahara            return $result;
546225b270SMichael Große        } else {
55*4027a91aSSatoshi Sahara            // update search index
56*4027a91aSSatoshi Sahara            $result = $this->addPage($page, $verbose, $force);
57*4027a91aSSatoshi Sahara            return $result;
586225b270SMichael Große        }
596225b270SMichael Große    }
606225b270SMichael Große
616225b270SMichael Große    /**
62*4027a91aSSatoshi Sahara     * Version of the indexer taking into consideration the external tokenizer.
63*4027a91aSSatoshi Sahara     * The indexer is only compatible with data written by the same version.
646225b270SMichael Große     *
65*4027a91aSSatoshi Sahara     * @triggers INDEXER_VERSION_GET
66*4027a91aSSatoshi Sahara     * Plugins that modify what gets indexed should hook this event and
67*4027a91aSSatoshi Sahara     * add their version info to the event data like so:
68*4027a91aSSatoshi Sahara     *     $data[$plugin_name] = $plugin_version;
696225b270SMichael Große     *
706225b270SMichael Große     * @author Tom N Harris <tnharris@whoopdedo.org>
716225b270SMichael Große     * @author Michael Hamann <michael@content-space.de>
72*4027a91aSSatoshi Sahara     *
73*4027a91aSSatoshi Sahara     * @return int|string
746225b270SMichael Große     */
75*4027a91aSSatoshi Sahara    public function getVersion()
76*4027a91aSSatoshi Sahara    {
77*4027a91aSSatoshi Sahara        static $indexer_version = null;
78*4027a91aSSatoshi Sahara        if ($indexer_version == null) {
79*4027a91aSSatoshi Sahara            $version = INDEXER_VERSION;
80*4027a91aSSatoshi Sahara
81*4027a91aSSatoshi Sahara            // DokuWiki version is included for the convenience of plugins
82*4027a91aSSatoshi Sahara            $data = array('dokuwiki' => $version);
83*4027a91aSSatoshi Sahara            Event::createAndTrigger('INDEXER_VERSION_GET', $data, null, false);
84*4027a91aSSatoshi Sahara            unset($data['dokuwiki']); // this needs to be first
85*4027a91aSSatoshi Sahara            ksort($data);
86*4027a91aSSatoshi Sahara            foreach ($data as $plugin => $vers) {
87*4027a91aSSatoshi Sahara                $version .= '+'.$plugin.'='.$vers;
88*4027a91aSSatoshi Sahara            }
89*4027a91aSSatoshi Sahara            $indexer_version = $version;
90*4027a91aSSatoshi Sahara        }
91*4027a91aSSatoshi Sahara        return $indexer_version;
926225b270SMichael Große    }
936225b270SMichael Große
94*4027a91aSSatoshi Sahara    /**
95*4027a91aSSatoshi Sahara     * Adds/updates the search index for the given page
96*4027a91aSSatoshi Sahara     *
97*4027a91aSSatoshi Sahara     * Locking is handled internally.
98*4027a91aSSatoshi Sahara     *
99*4027a91aSSatoshi Sahara     * @param string        $page   name of the page to index
100*4027a91aSSatoshi Sahara     * @param bool          $verbose    print status messages
101*4027a91aSSatoshi Sahara     * @param bool          $force  force reindexing even when the index is up to date
102*4027a91aSSatoshi Sahara     * @return bool  If the function completed successfully
103*4027a91aSSatoshi Sahara     *
104*4027a91aSSatoshi Sahara     * @author Tom N Harris <tnharris@whoopdedo.org>
105*4027a91aSSatoshi Sahara     * @author Satoshi Sahara <sahara.satoshi@gmail.com>
106*4027a91aSSatoshi Sahara     */
107*4027a91aSSatoshi Sahara    public function addPage($page, $verbose = false, $force = false)
108*4027a91aSSatoshi Sahara    {
109*4027a91aSSatoshi Sahara        // check if indexing needed for the existing page (full text and/or metadata indexing)
110*4027a91aSSatoshi Sahara        $idxtag = metaFN($page,'.indexed');
111*4027a91aSSatoshi Sahara        if (!$force && file_exists($idxtag)) {
112*4027a91aSSatoshi Sahara            if (trim(io_readFile($idxtag)) == $this->getVersion()) {
113*4027a91aSSatoshi Sahara                $last = @filemtime($idxtag);
114*4027a91aSSatoshi Sahara                if ($last > @filemtime(wikiFN($page))) {
115*4027a91aSSatoshi Sahara                    if ($verbose) dbglog("Indexer: index for {$page} up to date");
116*4027a91aSSatoshi Sahara                    return true;
117*4027a91aSSatoshi Sahara                }
118*4027a91aSSatoshi Sahara            }
119*4027a91aSSatoshi Sahara        }
1206225b270SMichael Große
121*4027a91aSSatoshi Sahara        // register the page to the page.idx
122*4027a91aSSatoshi Sahara        $pid = $this->getPID($page);
1236225b270SMichael Große        if ($pid === false) {
124*4027a91aSSatoshi Sahara            if ($verbose) dbglog("Indexer: getting the PID failed for {$page}");
125*4027a91aSSatoshi Sahara            trigger_error("Failed to get PID for {$page}", E_USER_ERROR);
1266225b270SMichael Große            return false;
1276225b270SMichael Große        }
1286225b270SMichael Große
129*4027a91aSSatoshi Sahara        // prepare metadata indexing
130*4027a91aSSatoshi Sahara        $metadata = array();
131*4027a91aSSatoshi Sahara        $metadata['title'] = p_get_metadata($page, 'title', METADATA_RENDER_UNLIMITED);
1326225b270SMichael Große
133*4027a91aSSatoshi Sahara        $references = p_get_metadata($page, 'relation references', METADATA_RENDER_UNLIMITED);
134*4027a91aSSatoshi Sahara        $metadata['relation_references'] = ($references !== null) ?
135*4027a91aSSatoshi Sahara                array_keys($references) : array();
1366225b270SMichael Große
137*4027a91aSSatoshi Sahara        $media = p_get_metadata($page, 'relation media', METADATA_RENDER_UNLIMITED);
138*4027a91aSSatoshi Sahara        $metadata['relation_media'] = ($media !== null) ?
139*4027a91aSSatoshi Sahara                array_keys($media) : array();
1406225b270SMichael Große
141*4027a91aSSatoshi Sahara        // check if full text indexing allowed
142*4027a91aSSatoshi Sahara        $indexenabled = p_get_metadata($page, 'internal index', METADATA_RENDER_UNLIMITED);
143*4027a91aSSatoshi Sahara        if ($indexenabled !== false) $indexenabled = true;
144*4027a91aSSatoshi Sahara        $metadata['internal_index'] = $indexenabled;
1456225b270SMichael Große
146*4027a91aSSatoshi Sahara        $body = '';
147*4027a91aSSatoshi Sahara        $data = compact('page', 'body', 'metadata', 'pid');
148*4027a91aSSatoshi Sahara        $event = new Event('INDEXER_PAGE_ADD', $data);
149*4027a91aSSatoshi Sahara        if ($event->advise_before()) $data['body'] = $data['body'].' '.rawWiki($page);
150*4027a91aSSatoshi Sahara        $event->advise_after();
151*4027a91aSSatoshi Sahara        unset($event);
152*4027a91aSSatoshi Sahara        extract($data);
153*4027a91aSSatoshi Sahara        $indexenabled = $metadata['internal_index'];
154*4027a91aSSatoshi Sahara        unset($metadata['internal_index']);
1556225b270SMichael Große
156*4027a91aSSatoshi Sahara        // Access to Metadata Index
157*4027a91aSSatoshi Sahara        $MetadataIndex = MetadataIndex::getInstance();
158*4027a91aSSatoshi Sahara        $result = $MetadataIndex->addMetaKeys($page, $metadata);
159*4027a91aSSatoshi Sahara        if ($verbose) dbglog("Indexer: addMetaKeys({$page}) ".($result ? 'done' : 'failed'));
160*4027a91aSSatoshi Sahara        if (!$result) {
1616225b270SMichael Große            return false;
1626225b270SMichael Große        }
1636225b270SMichael Große
164*4027a91aSSatoshi Sahara        // Access to Pageword Index
165*4027a91aSSatoshi Sahara        $PagewordIndex = PagewordIndex::getInstance();
166*4027a91aSSatoshi Sahara        if ($indexenabled) {
167*4027a91aSSatoshi Sahara            $result = $PagewordIndex->addPageWords($page, $body);
168*4027a91aSSatoshi Sahara            if ($verbose) dbglog("Indexer: addPageWords({$page}) ".($result ? 'done' : 'failed'));
169*4027a91aSSatoshi Sahara            if (!$result) {
1706225b270SMichael Große                return false;
1716225b270SMichael Große            }
1726225b270SMichael Große        } else {
173*4027a91aSSatoshi Sahara            if ($verbose) dbglog("Indexer: full text indexing disabled for {$page}");
174*4027a91aSSatoshi Sahara            // ensure the page content deleted from the pageword index
175*4027a91aSSatoshi Sahara            $result = $PagewordIndex->deletePageWords($page);
176*4027a91aSSatoshi Sahara            if ($verbose) dbglog("Indexer: deletePageWords({$page}) ".($result ? 'done' : 'failed'));
177*4027a91aSSatoshi Sahara            if (!$result) {
1786225b270SMichael Große                return false;
1796225b270SMichael Große            }
1806225b270SMichael Große        }
1816225b270SMichael Große
182*4027a91aSSatoshi Sahara        // update index tag file
183*4027a91aSSatoshi Sahara        io_saveFile($idxtag, $this->getVersion());
184*4027a91aSSatoshi Sahara        if ($verbose) dbglog("Indexer: finished");
185*4027a91aSSatoshi Sahara
186*4027a91aSSatoshi Sahara        return $result;
1876225b270SMichael Große    }
1886225b270SMichael Große
1896225b270SMichael Große    /**
1906225b270SMichael Große     * Remove a page from the index
1916225b270SMichael Große     *
192*4027a91aSSatoshi Sahara     * Erases entries in all known indexes. Locking is handled internally.
1936225b270SMichael Große     *
194*4027a91aSSatoshi Sahara     * @param string        $page   name of the page to index
195*4027a91aSSatoshi Sahara     * @param bool          $verbose    print status messages
196*4027a91aSSatoshi Sahara     * @param bool          $force  force reindexing even when the index is up to date
197*4027a91aSSatoshi Sahara     * @return bool  If the function completed successfully
1986225b270SMichael Große     *
1996225b270SMichael Große     * @author Tom N Harris <tnharris@whoopdedo.org>
200*4027a91aSSatoshi Sahara     * @author Satoshi Sahara <sahara.satoshi@gmail.com>
2016225b270SMichael Große     */
202*4027a91aSSatoshi Sahara    public function deletePage($page, $verbose = false, $force = false)
203*4027a91aSSatoshi Sahara    {
204*4027a91aSSatoshi Sahara        $idxtag = metaFN($page,'.indexed');
205*4027a91aSSatoshi Sahara        if (!$force && !file_exists($idxtag)) {
206*4027a91aSSatoshi Sahara            if ($verbose) dbglog("Indexer: {$page}.indexed file does not exist, ignoring");
207*4027a91aSSatoshi Sahara            return true;
208*4027a91aSSatoshi Sahara        }
2096225b270SMichael Große
210*4027a91aSSatoshi Sahara        // remove obsoleted content from pageword index
211*4027a91aSSatoshi Sahara        $PagewordIndex = PagewordIndex::getInstance();
212*4027a91aSSatoshi Sahara        $result = $PagewordIndex->deletePageWords($page);
213*4027a91aSSatoshi Sahara        if ($verbose) dbglog("Indexer: deletePageWords({$page}) ".($result ? 'done' : 'failed'));
214*4027a91aSSatoshi Sahara        if (!$result) {
215*4027a91aSSatoshi Sahara            return false;
216*4027a91aSSatoshi Sahara        }
2176225b270SMichael Große
218*4027a91aSSatoshi Sahara        // delete all keys of the page from metadata index
219*4027a91aSSatoshi Sahara        $MetadataIndex = MetadataIndex::getInstance();
220*4027a91aSSatoshi Sahara        $result = $MetadataIndex->deleteMetaKeys($page);
221*4027a91aSSatoshi Sahara        if ($verbose) dbglog("Indexer: deleteMetaKeys({$page}) ".($result ? 'done' : 'failed'));
222*4027a91aSSatoshi Sahara        if (!$result) {
223*4027a91aSSatoshi Sahara            return false;
224*4027a91aSSatoshi Sahara        }
225*4027a91aSSatoshi Sahara
226*4027a91aSSatoshi Sahara        // mark the page as deleted in the page.idx
227*4027a91aSSatoshi Sahara        $pid = $this->getPID($page);
228*4027a91aSSatoshi Sahara        if ($pid !== false) {
229*4027a91aSSatoshi Sahara            if (!$this->lock()) return false;  // set $errors property
230*4027a91aSSatoshi Sahara            $result = $this->saveIndexKey('page', '', $pid, '#deleted:'.$page);
231*4027a91aSSatoshi Sahara            if ($verbose) dbglog("Indexer: update page.idx  ".($result ? 'done' : 'failed'));
2326225b270SMichael Große            $this->unlock();
233*4027a91aSSatoshi Sahara        } else {
234*4027a91aSSatoshi Sahara            if ($verbose) dbglog("Indexer: {$page} not found in the page.idx, ignoring");
235*4027a91aSSatoshi Sahara            $resullt = true;
236*4027a91aSSatoshi Sahara        }
237*4027a91aSSatoshi Sahara
238*4027a91aSSatoshi Sahara        unset(static::$pidCache[$pid]);
239*4027a91aSSatoshi Sahara        @unlink($idxtag);
240*4027a91aSSatoshi Sahara        return $result;
241*4027a91aSSatoshi Sahara    }
242*4027a91aSSatoshi Sahara
243*4027a91aSSatoshi Sahara    /**
244*4027a91aSSatoshi Sahara     * Rename a page in the search index without changing the indexed content.
245*4027a91aSSatoshi Sahara     * This function doesn't check if the old or new name exists in the filesystem.
246*4027a91aSSatoshi Sahara     * It returns an error if the old page isn't in the page list of the indexer
247*4027a91aSSatoshi Sahara     * and it deletes all previously indexed content of the new page.
248*4027a91aSSatoshi Sahara     *
249*4027a91aSSatoshi Sahara     * @param string $oldpage The old page name
250*4027a91aSSatoshi Sahara     * @param string $newpage The new page name
251*4027a91aSSatoshi Sahara     * @return bool           If the page was successfully renamed
252*4027a91aSSatoshi Sahara     */
253*4027a91aSSatoshi Sahara    public function renamePage($oldpage, $newpage)
254*4027a91aSSatoshi Sahara    {
255*4027a91aSSatoshi Sahara        $index = $this->getIndex('page', '');
256*4027a91aSSatoshi Sahara        // check if oldpage found in page.idx
257*4027a91aSSatoshi Sahara        $oldPid = array_search($oldpage, $index, true);
258*4027a91aSSatoshi Sahara        if ($oldPid === false) return false;
259*4027a91aSSatoshi Sahara
260*4027a91aSSatoshi Sahara        // check if newpage found in page.idx
261*4027a91aSSatoshi Sahara        $newPid = array_search($newpage, $index, true);
262*4027a91aSSatoshi Sahara        if ($newPid !== false) {
263*4027a91aSSatoshi Sahara            $result = $this->deletePage($newpage);
264*4027a91aSSatoshi Sahara            if (!$result) return false;
265*4027a91aSSatoshi Sahara            // Note: $index is no longer valid after deletePage()!
266*4027a91aSSatoshi Sahara            unset($index);
267*4027a91aSSatoshi Sahara        }
268*4027a91aSSatoshi Sahara
269*4027a91aSSatoshi Sahara        // update page.idx
270*4027a91aSSatoshi Sahara        if (!$this->lock()) return false;  // set $errors property
271*4027a91aSSatoshi Sahara        $result = $this->saveIndexKey('page', '', $oldPid, $newpage);
272*4027a91aSSatoshi Sahara        $this->unlock();
273*4027a91aSSatoshi Sahara
274*4027a91aSSatoshi Sahara        // reset the pid cache
275*4027a91aSSatoshi Sahara        $this->resetPIDCache();
2766225b270SMichael Große
2776225b270SMichael Große        return $result;
2786225b270SMichael Große    }
2796225b270SMichael Große
2806225b270SMichael Große    /**
281*4027a91aSSatoshi Sahara     * Clear the Page Index
2826225b270SMichael Große     *
283*4027a91aSSatoshi Sahara     * @param bool   $requireLock
2846225b270SMichael Große     * @return bool  If the index has been cleared successfully
2856225b270SMichael Große     */
286*4027a91aSSatoshi Sahara    public function clear($requireLock = true)
287*4027a91aSSatoshi Sahara    {
2886225b270SMichael Große        global $conf;
2896225b270SMichael Große
290*4027a91aSSatoshi Sahara        if ($requireLock && !$this->lock()) return false;
291*4027a91aSSatoshi Sahara
292*4027a91aSSatoshi Sahara        // clear Metadata Index
293*4027a91aSSatoshi Sahara        $MetadataIndex = MetadataIndex::getInstance();
294*4027a91aSSatoshi Sahara        $MetadataIndex->clear(false);
295*4027a91aSSatoshi Sahara
296*4027a91aSSatoshi Sahara        // clear Pageword Index
297*4027a91aSSatoshi Sahara        $PagewordIndex = PagewordIndex::getInstance();
298*4027a91aSSatoshi Sahara        $PagewordIndex->clear(false);
2996225b270SMichael Große
3006225b270SMichael Große        @unlink($conf['indexdir'].'/page.idx');
3016225b270SMichael Große
3026225b270SMichael Große        // clear the pid cache
303*4027a91aSSatoshi Sahara        $this->resetPIDCache();
3046225b270SMichael Große
305*4027a91aSSatoshi Sahara        if ($requireLock) $this->unlock();
3066225b270SMichael Große        return true;
3076225b270SMichael Große    }
3086225b270SMichael Große
3096225b270SMichael Große
3106225b270SMichael Große    /**
3116225b270SMichael Große     * Return a list of words sorted by number of times used
3126225b270SMichael Große     *
3136225b270SMichael Große     * @param int       $min    bottom frequency threshold
3146225b270SMichael Große     * @param int       $max    upper frequency limit. No limit if $max<$min
3156225b270SMichael Große     * @param int       $minlen minimum length of words to count
3166225b270SMichael Große     * @param string    $key    metadata key to list. Uses the fulltext index if not given
3176225b270SMichael Große     * @return array            list of words as the keys and frequency as values
3186225b270SMichael Große     *
3196225b270SMichael Große     * @author Tom N Harris <tnharris@whoopdedo.org>
3206225b270SMichael Große     */
321*4027a91aSSatoshi Sahara    public function histogram($min=1, $max=0, $minlen=3, $key=null)
322*4027a91aSSatoshi Sahara    {
323*4027a91aSSatoshi Sahara        if ($min < 1)    $min = 1;
324*4027a91aSSatoshi Sahara        if ($max < $min) $max = 0;
3256225b270SMichael Große
3266225b270SMichael Große        $result = array();
3276225b270SMichael Große
3286225b270SMichael Große        if ($key == 'title') {
3296225b270SMichael Große            $index = $this->getIndex('title', '');
3306225b270SMichael Große            $index = array_count_values($index);
3316225b270SMichael Große            foreach ($index as $val => $cnt) {
332*4027a91aSSatoshi Sahara                if ($cnt >= $min && (!$max || $cnt <= $max) && strlen($val) >= $minlen) {
3336225b270SMichael Große                    $result[$val] = $cnt;
3346225b270SMichael Große                }
3356225b270SMichael Große            }
336*4027a91aSSatoshi Sahara        } elseif (!is_null($key)) {
337*4027a91aSSatoshi Sahara            $metaname = $this->cleanName($key);
3386225b270SMichael Große            $index = $this->getIndex($metaname.'_i', '');
3396225b270SMichael Große            $val_idx = array();
3406225b270SMichael Große            foreach ($index as $wid => $line) {
3416225b270SMichael Große                $freq = $this->countTuples($line);
342*4027a91aSSatoshi Sahara                if ($freq >= $min && (!$max || $freq <= $max)) {
3436225b270SMichael Große                    $val_idx[$wid] = $freq;
3446225b270SMichael Große                }
345*4027a91aSSatoshi Sahara            }
3466225b270SMichael Große            if (!empty($val_idx)) {
3476225b270SMichael Große                $words = $this->getIndex($metaname.'_w', '');
3486225b270SMichael Große                foreach ($val_idx as $wid => $freq) {
349*4027a91aSSatoshi Sahara                    if (strlen($words[$wid]) >= $minlen) {
3506225b270SMichael Große                        $result[$words[$wid]] = $freq;
3516225b270SMichael Große                    }
3526225b270SMichael Große                }
3536225b270SMichael Große            }
354*4027a91aSSatoshi Sahara        } else {
355*4027a91aSSatoshi Sahara            $PagewordIndex = PagewordIndex::getInstance();
356*4027a91aSSatoshi Sahara            $lengths = $PagewordIndex->listIndexLengths();
3576225b270SMichael Große            foreach ($lengths as $length) {
3586225b270SMichael Große                if ($length < $minlen) continue;
3596225b270SMichael Große                $index = $this->getIndex('i', $length);
3606225b270SMichael Große                $words = null;
3616225b270SMichael Große                foreach ($index as $wid => $line) {
3626225b270SMichael Große                    $freq = $this->countTuples($line);
3636225b270SMichael Große                    if ($freq >= $min && (!$max || $freq <= $max)) {
364*4027a91aSSatoshi Sahara                        if ($words === null) {
3656225b270SMichael Große                            $words = $this->getIndex('w', $length);
366*4027a91aSSatoshi Sahara                        }
3676225b270SMichael Große                        $result[$words[$wid]] = $freq;
3686225b270SMichael Große                    }
3696225b270SMichael Große                }
3706225b270SMichael Große            }
3716225b270SMichael Große        }
3726225b270SMichael Große
3736225b270SMichael Große        arsort($result);
3746225b270SMichael Große        return $result;
3756225b270SMichael Große    }
3766225b270SMichael Große}
377