function forumng_delete_instance($id, $ociskip = true)
{
    require_once dirname(__FILE__) . '/forum.php';
    try {
        $forum = forum::get_from_id($id, forum::CLONE_DIRECT);
        // avoid deleting OCI specific forum if running in upload block
        if ($ociskip) {
            global $restore;
            if (isset($restore) && $restore->restoreto == 0 && strpos($_SERVER['HTTP_REFERER'], 'blocks/versions/upload.php') !== false) {
                if ($forum->get_name() == get_string('newunitforumname', 'createcourse')) {
                    //Unit forum
                    echo ' found forumng ' . $forum->get_id() . ' ' . $forum->get_name();
                    return true;
                }
            }
        }
        $forum->delete_all_data();
        if (forum::search_installed()) {
            $cm = $forum->get_course_module();
            ousearch_document::delete_module_instance_data($cm);
        }
    } catch (Exception $e) {
        return false;
    }
    return delete_records('forumng', 'id', $id);
}
 function test_split_words()
 {
     // Standard usage and caps
     $this->assertEqual(ousearch_document::split_words('Hello I AM a basic test'), array('hello', 'i', 'am', 'a', 'basic', 'test'));
     // Numbers
     $this->assertEqual(ousearch_document::split_words('13 2by2'), array('13', '2by2'));
     // Ignored and accepted punctuation and whitespace
     $this->assertEqual(ousearch_document::split_words('  hello,testing!what\'s&up      there-by   '), array('hello', 'testing', 'what\'s', 'up', 'there', 'by'));
     // Unicode letters and nonletter
     $this->assertEqual(ousearch_document::split_words('café ßåřĉĕļÅ?ņä※tonight'), array('café', 'ßåřĉĕļÅ?ņä', 'tonight'));
     // Unicode caps
     $this->assertEqual(ousearch_document::split_words('ĀĒĪŌŪ'), array('Ä?Ä“Ä«Å?Å«'));
     // Query mode (keeps " + -)
     $this->assertEqual(ousearch_document::split_words('"hello there" +frog -doughnut extra-special', true), array('"hello', 'there"', '+frog', '-doughnut', 'extra-special'));
     // Position mode: normal
     $this->assertEqual(ousearch_document::split_words('hello test', false, true), array(array('hello', 'test'), array(0, 6, 10)));
     // Position mode: whitespace
     $this->assertEqual(ousearch_document::split_words('    hello    test    ', false, true), array(array('hello', 'test'), array(4, 13, 21)));
     // Position mode: unicode
     $this->assertEqual(ousearch_document::split_words('hĕllo tĕst', false, true), array(array('hĕllo', 'tĕst'), array(0, 7, 12)));
     // Positions are in bytes
 }
/**
 * Obtains a search document relating to a particular blog post.
 *
 * @param object $post Post object. Required fields: id (optionally also
 *   groupid, userid save a db query)
 * @param object $cm Course-module object. Required fields: id, course
 * @return ousearch_doument
 */
function oublog_get_search_document($post, $cm)
{
    // Set up 'search document' to refer to this post
    $doc = new ousearch_document();
    $doc->init_module_instance('oublog', $cm);
    if (!isset($post->userid) || !isset($post->groupid)) {
        global $CFG;
        $results = get_record_sql("\nSELECT\n    p.groupid,i.userid\nFROM\n{$CFG->prefix}oublog_posts p\n    INNER JOIN {$CFG->prefix}oublog_instances i ON p.oubloginstancesid=i.id\nWHERE\n    p.id={$post->id}");
        if (!$results) {
            error("Can't find details for blog post {$post->id}");
        }
        $post->userid = $results->userid;
        $post->groupid = $results->groupid;
    }
    if ($post->groupid) {
        $doc->set_group_id($post->groupid);
    }
    $doc->set_user_id($post->userid);
    $doc->set_int_refs($post->id);
    return $doc;
}
/**
 * Update all wiki documents for ousearch.
 * @param bool $feedback If true, prints feedback as HTML list items
 * @param int $courseid If specified, restricts to particular courseid
 */
function ouwiki_ousearch_update_all($feedback = false, $courseid = 0)
{
    global $CFG;
    // Get list of all wikis. We need the coursemodule data plus
    // the type of subwikis
    $coursecriteria = $courseid === 0 ? '' : 'cm.course=' . $courseid . ' AND';
    $coursemodules = get_records_sql("\nSELECT\n    cm.id,cm.course,cm.instance,w.subwikis\nFROM\n    {$CFG->prefix}modules m\n    INNER JOIN {$CFG->prefix}course_modules cm ON cm.module=m.id\n    INNER JOIN {$CFG->prefix}ouwiki w ON cm.instance=w.id\nWHERE\n    {$coursecriteria}\n    m.name='ouwiki'");
    if (!$coursemodules) {
        return;
    }
    if ($feedback) {
        print '<li><strong>' . count($coursemodules) . '</strong> wikis to process.</li>';
        $dotcount = 0;
    }
    $count = 0;
    foreach ($coursemodules as $coursemodule) {
        // This condition is needed because if somebody creates some stuff
        // then changes the wiki type, it actually keeps the old bits
        // in the database. Maybe it shouldn't, not sure.
        switch ($coursemodule->subwikis) {
            case OUWIKI_SUBWIKIS_SINGLE:
                $where = "sw.userid IS NULL AND sw.groupid IS NULL";
                break;
            case OUWIKI_SUBWIKIS_GROUPS:
                $where = "sw.userid IS NULL AND sw.groupid IS NOT NULL";
                break;
            case OUWIKI_SUBWIKIS_INDIVIDUAL:
                $where = "sw.userid IS NOT NULL AND sw.groupid IS NULL";
                break;
        }
        // Get all pages in that wiki
        $rs = get_recordset_sql("\nSELECT\n    p.id,p.title,v.xhtml,v.timecreated,sw.groupid,sw.userid\nFROM\n    {$CFG->prefix}ouwiki_subwikis sw\n    INNER JOIN {$CFG->prefix}ouwiki_pages p ON p.subwikiid=sw.id\n    INNER JOIN {$CFG->prefix}ouwiki_versions v ON v.id=p.currentversionid\nWHERE\n    sw.wikiid={$coursemodule->instance} AND {$where}");
        while ($page = rs_fetch_next_record($rs)) {
            // Update the page for search
            $doc = new ousearch_document();
            $doc->init_module_instance('ouwiki', $coursemodule);
            if ($page->groupid) {
                $doc->set_group_id($page->groupid);
            }
            if ($page->title) {
                $doc->set_string_ref($page->title);
            }
            if ($page->userid) {
                $doc->set_user_id($page->userid);
            }
            $title = $page->title ? $page->title : '';
            if (!$doc->update($title, $page->xhtml, $page->timecreated)) {
                ouwiki_error('Failed to update database record for page ID ' . $page->id);
            }
        }
        rs_close($rs);
        $count++;
        if ($feedback) {
            if ($dotcount == 0) {
                print '<li>';
            }
            print '.';
            $dotcount++;
            if ($dotcount == 20 || $count == count($coursemodules)) {
                print 'done ' . $count . '</li>';
                $dotcount = 0;
            }
            flush();
        }
    }
}
/**
 * Given an ID of an instance of this module, this function will
 * permanently delete the instance and any data that depends on it.
 *
 * @param int $id The ID of the module instance
 * @return boolena true on success, false on failure.
 */
function oublog_delete_instance($oublogid)
{
    if (!($oublog = get_record('oublog', 'id', $oublogid))) {
        return false;
    }
    if ($oublog->global) {
        error('You can\'t delete the global blog');
    }
    if ($instances = get_records('oublog_instances', 'oublogid', $oublog->id)) {
        foreach ($instances as $oubloginstancesid => $bloginstance) {
            // tags
            delete_records('oublog_taginstances', 'oubloginstancesid', $oubloginstancesid);
            if ($posts = get_records('oublog_posts', 'oubloginstancesid', $oubloginstancesid)) {
                foreach ($posts as $postid => $post) {
                    // comments
                    delete_records('oublog_comments', 'postid', $postid);
                    // edits
                    delete_records('oublog_edits', 'postid', $postid);
                }
                // posts
                delete_records('oublog_posts', 'oubloginstancesid', $oubloginstancesid);
            }
        }
    }
    // links
    delete_records('oublog_links', 'oublogid', $oublog->id);
    // instances
    delete_records('oublog_instances', 'oublogid', $oublog->id);
    // Fulltext search data
    require_once dirname(__FILE__) . '/locallib.php';
    if (oublog_search_installed()) {
        $moduleid = get_field('modules', 'id', 'name', 'oublog');
        $cm = get_record('course_modules', 'module', $moduleid, 'instance', $oublog->id);
        if (!$cm) {
            error('Can\'t find coursemodule');
        }
        ousearch_document::delete_module_instance_data($cm);
    }
    // oublog
    return delete_records('oublog', 'id', $oublog->id);
}
$lastreport = 0;
$pos = 0;
$type_none = 0;
$type_user = 0;
$type_group = 0;
$totaltime = 0;
$documents = 0;
while ($pos < $numlines) {
    // Pick how many lines to use for one 'document'
    $doclines = rand(5, 200);
    if ($pos + $doclines > $numlines) {
        $doclines = $numlines - $pos;
    }
    $start = $pos;
    // Set up document
    $document = new ousearch_document();
    $document->init_test('test2');
    // Refs define document and place in it
    $document->set_string_ref($filename);
    $document->set_int_refs($start, $doclines);
    // Type (group.user)
    $type = rand(0, 99);
    if ($type < 50) {
        // 50% None
        $type_none++;
    } else {
        if ($type < 90) {
            // 40% Group
            $document->set_group_id($groups[rand(0, count($groups) - 1)]);
            $type_group++;
        } else {
/**
 * Saves a new version of the given named page within a subwiki. Can create
 * a new page or just add a new version to an existing one. In case of
 * failure, ends up calling error() rather than returning something.
 * @param object $course Course object
 * @param object $cm Course-module object
 * @param object $ouwiki OU wiki object
 * @param object $subwiki Subwiki object
 * @param string $pagename Name of page (NO SLASHES)
 * @param string $content XHTML Content (NO SLASHES)
 * @param int $changestart For section changes. Start position of change. (-1 if not section change)
 * @param int $changesize Size of changed section.
 * @param int $changeprevsize Previous size of changed section
 * @param bool $nouser If true, creates as system
 */
function ouwiki_save_new_version($course, $cm, $ouwiki, $subwiki, $pagename, $content, $changestart = -1, $changesize = -1, $changeprevsize = -1, $nouser = false)
{
    $tw = new transaction_wrapper();
    // Find page if it exists
    $pageversion = ouwiki_get_current_page($subwiki, $pagename, OUWIKI_GETPAGE_CREATE);
    // Analyse content for HTML headings that don't already have an ID.
    // These are all assigned unique, fairly short IDs.
    // Get number of version [guarantees in-page uniqueness of generated IDs]
    $versionnumber = count_records('ouwiki_versions', 'pageid', $pageversion->pageid);
    // Remove any spaces from annotation tags that were added for editing or by users
    // and remove any duplicate annotation tags
    $pattern = '~<span\\b.id=\\"annotation(.+?)\\">.*?</span>~';
    $replace = '<span id="annotation$1"></span>';
    $content = preg_replace($pattern, $replace, $content);
    unset($pattern, $replace, $used);
    // Get rid of any heading tags that only contain whitespace
    $emptypatterns = array();
    for ($i = 1; $i <= 6; $i++) {
        $emptypatterns[] = '~<h' . $i . '[^>]*>\\s*(<br[^>]*>\\s*)*</h' . $i . '>~';
    }
    $content = preg_replace($emptypatterns, '', $content);
    // List all headings that already have IDs, to check for duplicates
    $matches = array();
    preg_match_all('|<h[1-9] id="ouw_s(.*?)">(.*?)</h[1-9]>|', $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
    // Organise list by ID
    $byid = array();
    foreach ($matches as $index => $data) {
        $id = $data[1][0];
        if (!array_key_exists($id, $byid)) {
            $byid[$id] = array();
        }
        $byid[$id][] = $index;
    }
    // Handle any duplicates
    $deletebits = array();
    foreach ($byid as $id => $duplicates) {
        if (count($duplicates) > 1) {
            // We have a duplicate. By default, keep the first one
            $keep = $duplicates[0];
            // See if there is a title entry in the database for it
            $knowntitle = get_field('ouwiki_sections', 'title', 'xhtmlid', addslashes($id), 'pageid', $pageversion->pageid);
            if ($knowntitle) {
                foreach ($duplicates as $duplicate) {
                    $title = ouwiki_get_section_title(null, null, $matches[$duplicate][2][0]);
                    if ($title === $knowntitle) {
                        $keep = $duplicate;
                        break;
                    }
                }
            }
            foreach ($duplicates as $duplicate) {
                if ($duplicate !== $keep) {
                    $deletebits[] = (object) array('startbyte' => $matches[$duplicate][1][1] - 10, 'bytes' => strlen($matches[$duplicate][1][0]) + 11);
                }
            }
        }
    }
    // Were there any?
    if (count($deletebits) > 0) {
        // Sort in reverse order of starting position
        usort($deletebits, 'ouwiki_internal_sort_deletions');
        // Delete each bit
        foreach ($deletebits as $deletebit) {
            $content = substr($content, 0, $deletebit->startbyte) . substr($content, $deletebit->startbyte + $deletebit->bytes);
        }
    }
    // Replace existing empty headings with an ID including version count plus another index
    global $ouwiki_count;
    // Nasty but I can't think of a better way!
    $ouwiki_count = 0;
    global $ouwiki_internal_re;
    $ouwiki_internal_re->version = $versionnumber;
    $ouwiki_internal_re->count = 0;
    $sizebefore = strlen($content);
    $content = preg_replace_callback('/<h([1-9])>/', 'ouwiki_internal_re_headings', $content);
    $sizeafter = strlen($content);
    // Replace wiki links to [[Start page]] with the correct (non
    // language-specific) format [[]]
    $regex = str_replace('.*?', preg_quote(get_string('startpage', 'ouwiki')), OUWIKI_LINKS_SQUAREBRACKETS) . 'ui';
    $content = preg_replace($regex, '[[]]', $content);
    // Create version
    $version = new StdClass();
    $version->pageid = $pageversion->pageid;
    $version->xhtml = addslashes($content);
    $version->timecreated = time();
    if (!$nouser) {
        global $USER;
        $version->userid = $USER->id;
    }
    if ($changestart != -1) {
        $version->changestart = $changestart;
        // In tracking the new size, account for any added headings etc
        $version->changesize = $changesize + ($sizeafter - $sizebefore);
        $version->changeprevsize = $changeprevsize;
    }
    if (!($versionid = insert_record('ouwiki_versions', $version))) {
        $tw->rollback();
        ouwiki_dberror();
    }
    // Update latest version
    if (!set_field('ouwiki_pages', 'currentversionid', $versionid, 'id', $pageversion->pageid)) {
        $tw->rollback();
        ouwiki_dberror();
    }
    // Analyse for links
    $wikilinks = array();
    $externallinks = array();
    // Wiki links: ordinary [[links]]
    $matches = array();
    preg_match_all(OUWIKI_LINKS_SQUAREBRACKETS, $content, $matches, PREG_PATTERN_ORDER);
    foreach ($matches[1] as $match) {
        // Convert to page name (this also removes HTML tags etc)
        $wikilinks[] = ouwiki_get_wiki_link_details($match)->page;
    }
    // Note that we used to support CamelCase links but have removed support because:
    // 1. Confusing: students type JavaScript or MySpace and don't expect it to become a link
    // 2. Not accessible: screenreaders cannot cope with run-together words, and
    //    dyslexic students can have difficulty reading them
    // External links
    preg_match_all('/<a [^>]*href=(?:(?:\'(.*?)\')|(?:"(.*?))")/', $content, $matches, PREG_PATTERN_ORDER);
    foreach ($matches[1] as $match) {
        if ($match) {
            $externallinks[] = html_entity_decode($match);
        }
    }
    foreach ($matches[2] as $match) {
        if ($match) {
            $externallinks[] = html_entity_decode($match);
        }
    }
    // Add link records
    $link = new StdClass();
    $link->fromversionid = $versionid;
    foreach ($wikilinks as $targetpage) {
        if (!empty($targetpage)) {
            $pagerecord = get_record_select('ouwiki_pages', "subwikiid='{$subwiki->id}' AND UPPER(title)=UPPER('" . addslashes($targetpage) . "')");
            if ($pagerecord) {
                $pageid = $pagerecord->id;
            } else {
                $pageid = false;
            }
        } else {
            $pageid = get_field_select('ouwiki_pages', 'id', "subwikiid={$subwiki->id} AND title IS NULL");
        }
        if ($pageid) {
            $link->topageid = $pageid;
            $link->tomissingpage = null;
        } else {
            $link->topageid = null;
            $link->tomissingpage = addslashes(strtoupper($targetpage));
        }
        if (!($link->id = insert_record('ouwiki_links', $link))) {
            $tw->rollback();
            ouwiki_dberror();
        }
    }
    $link->topageid = null;
    $link->tomissingpage = null;
    $tl = textlib_get_instance();
    foreach ($externallinks as $url) {
        // Restrict length of URL
        if ($tl->strlen($url) > 255) {
            $url = $tl->substr($url, 0, 255);
        }
        $link->tourl = addslashes($url);
        if (!($link->id = insert_record('ouwiki_links', $link))) {
            $tw->rollback();
            ouwiki_dberror();
        }
    }
    // Inform search, if installed
    if (ouwiki_search_installed()) {
        $doc = new ousearch_document();
        $doc->init_module_instance('ouwiki', $cm);
        if ($subwiki->groupid) {
            $doc->set_group_id($subwiki->groupid);
        }
        $doc->set_string_ref($pageversion->title === '' ? null : $pageversion->title);
        if ($subwiki->userid) {
            $doc->set_user_id($subwiki->userid);
        }
        $title = is_null($pageversion->title) ? '' : $pageversion->title;
        if (!$doc->update($title, $content)) {
            $tw->rollback();
            ouwiki_dberror();
        }
    }
    // Inform completion system, if available
    if (class_exists('ouflags')) {
        if (completion_is_enabled($course, $cm) && ($ouwiki->completionedits || $ouwiki->completionpages)) {
            completion_update_state($course, $cm, COMPLETION_COMPLETE);
        }
    }
    $tw->commit();
}
 /**
  * Update all documents for ousearch.
  * @param bool $feedback If true, prints feedback as HTML list items
  * @param int $courseid If specified, restricts to particular courseid
  * @param int $cmid If specified, restricts to particular cmid
  */
 static function search_update_all($feedback = false, $courseid = 0, $cmid = 0)
 {
     global $CFG;
     // If cmid is specified, only retrieve that one
     if ($cmid) {
         $cmrestrict = "cm.id = {$cmid} AND";
     } else {
         $cmrestrict = '';
     }
     // Get module-instances that need updating
     $cms = get_records_sql("\nSELECT\n    cm.id, cm.course, cm.instance, f.name\nFROM\n    {$CFG->prefix}forumng f\n    INNER JOIN {$CFG->prefix}course_modules cm ON cm.instance=f.id\nWHERE\n    {$cmrestrict}\n    cm.module = (SELECT id FROM {$CFG->prefix}modules m WHERE name='forumng')" . ($courseid ? " AND f.course={$courseid}" : ''));
     $cms = $cms ? $cms : array();
     // Print count
     if ($feedback && !$cmid) {
         print '<li>' . get_string('search_update_count', 'forumng', '<strong>' . count($cms) . '</strong>') . '</li>';
     }
     // This can take a while, so let's be sure to reset the time limit.
     // Store the existing limit; we will set this existing value again
     // each time around the loop. Note: Despite the name, ini_get returns
     // the most recently set time limit, not the one from php.ini.
     $timelimitbefore = ini_get('max_execution_time');
     // Loop around updating
     foreach ($cms as $cm) {
         forum_utils::start_transaction();
         // Wipe existing search data, if any
         ousearch_document::delete_module_instance_data($cm);
         // Get all discussions for this forum
         $discussions = get_records('forumng_discussions', 'forumid', $cm->instance, '', 'id, postid');
         $discussions = $discussions ? $discussions : array();
         if ($feedback) {
             print '<li><strong>' . $cm->name . '</strong> (' . count($discussions) . '):';
         }
         // Process each discussion
         foreach ($discussions as $discussionrec) {
             // Ignore discussion with no postid
             // (This should not happen, where ther is a $discussionrec->id
             // it also shopuld have a $discussionrec->postid. This if-statement
             // fixes bug 10497 and would not have any side-effect.)
             if (!$discussionrec->postid) {
                 continue;
             }
             set_time_limit($timelimitbefore);
             $discussion = forum_discussion::get_from_id($discussionrec->id, forum::CLONE_DIRECT, -1);
             $root = $discussion->get_root_post();
             $root->search_update();
             $root->search_update_children();
             print '. ';
             flush();
         }
         forum_utils::finish_transaction();
         if ($feedback) {
             print '</li>';
         }
     }
 }
 /**
  * Obtains search document representing this post.
  * @return ousearch_document Document object
  */
 function search_get_document()
 {
     $doc = new ousearch_document();
     $doc->init_module_instance('forumng', $this->get_forum()->get_course_module());
     if ($groupid = $this->discussion->get_group_id()) {
         $doc->set_group_id($groupid);
     }
     $doc->set_int_refs($this->get_id());
     return $doc;
 }
 /**
  * Filters search results to pick out only the ones that match the query.
  * @param array $results Array of results from internal_query
  * @param int $desired Number of desired results
  * @return object ->results containing actual results and ->dbnext containing
  *   database position of next set of results.
  */
 function internal_filter($results, $desired)
 {
     global $CFG;
     $required = array();
     $accepted = array();
     $count = 0;
     $return = new StdClass();
     $return->dbnext = 0;
     $tl = textlib_get_instance();
     foreach ($results as $result) {
         $return->dbnext++;
         if (substr($result->plugin, 0, 4) === 'mod/') {
             // Module plugins
             $module = substr($result->plugin, 4);
             $function = $module . '_ousearch_get_document';
             if (!array_key_exists($module, $required)) {
                 require_once $CFG->dirroot . '/mod/' . $module . '/lib.php';
                 $required[$module] = true;
                 if (!function_exists($function)) {
                     error('Missing module search support ' . $function);
                 }
             }
         } else {
             if (substr($result->plugin, 0, 5) === 'test/') {
                 // Testing code, assumed to already be included
                 $function = substr($result->plugin, 5) . '_ousearch_get_document';
             } else {
                 // Nothing else supported yet
                 error('Unsupported search plugin type ' . $result->plugin);
             }
         }
         // Let's request the document. Note that the 'document' fields of
         // $result are those used by this function to find the right one.
         $page = $function($result);
         // Ignore if we can't find the document
         if (!$page) {
             debugging('Module ' . $result->plugin . ' can\'t find search document');
             ousearch_document::wipe_document($result->id);
             continue;
         }
         // Page option can request that this result is not included
         if (!empty($page->hide)) {
             continue;
         }
         // Strip XHTML from content (need this before phrase scan)
         $textcontent = ousearch_document::strip_xhtml($page->content);
         // Add extra strings to the content after a special don't-show-this
         // marker and with another special marker between each (to prevent
         // phrases)
         if (isset($page->extrastrings) && count($page->extrastrings) > 0) {
             $evilmarker = rand();
             // This means people can't do it on purpose
             $textcontent .= ' xxrealcontentends' . $evilmarker;
             foreach ($page->extrastrings as $string) {
                 $textcontent .= ' ' . $string . ' xxsplit' . $evilmarker;
             }
         }
         // Do quick phrase scan that doesn't deal with Unicode,
         // or word-splitting but just discards results that
         // don't have the phrase words next to each other without
         // ASCII letters in between. This is intended to discard
         // results that (fairly) definitely don't have the phrase.
         // The further check below will make sure they really do
         // have it according to our standard (slow) word-splitting.
         $quickcheckcontent = $page->title . ' ' . $textcontent;
         $ok = true;
         foreach ($this->terms as $term) {
             if (count($term->words) < 2) {
                 continue;
             }
             $gap = '[^A-Za-z0-9]+';
             $pattern = '/(^|' . $gap . ')';
             $first = true;
             foreach ($term->words as $word) {
                 if ($first) {
                     $first = false;
                 } else {
                     $pattern .= $gap;
                 }
                 $pattern .= $word;
             }
             $pattern .= '($|' . $gap . ')/i';
             if (!preg_match($pattern, $quickcheckcontent)) {
                 $ok = false;
                 break;
             }
         }
         if (!$ok) {
             continue;
         }
         // OK, obtain document as linear text
         list($contentwords, $contentpositions) = ousearch_document::split_words($textcontent, false, true);
         list($titlewords, $titlepositions) = ousearch_document::split_words($page->title, false, true);
         $allwords = array_merge($titlewords, $contentwords);
         // Check it for phrases
         $positivewords = array();
         $ok = true;
         $DNIfound = -1;
         foreach ($this->terms as $term) {
             foreach ($term->words as $word) {
                 $positivewords[$word] = true;
             }
             if (count($term->words) < 2) {
                 continue;
             }
             $pos = 0;
             $found = false;
             foreach ($allwords as $word) {
                 if ($word === $term->words[$pos]) {
                     $pos++;
                     if ($pos === count($term->words)) {
                         $found = true;
                         break;
                     }
                 } else {
                     $pos = 0;
                 }
             }
             if (!$found) {
                 $ok = false;
                 break;
             }
         }
         foreach ($this->negativeterms as $term) {
             if (count($term->words) < 2) {
                 continue;
             }
             $pos = 0;
             $found = false;
             foreach ($allwords as $word) {
                 if ($word === $term->words[$pos]) {
                     $pos++;
                     if ($pos === count($term->words)) {
                         $found = true;
                         break;
                     }
                 } else {
                     $pos = 0;
                 }
             }
             if ($found) {
                 $ok = false;
                 break;
             }
         }
         if (!$ok) {
             continue;
         }
         // Result passes! Make structure holding it...
         // We now have list of all positive words, let's mark these
         // in title and summary
         $result->title = self::internal_highlight_words($page->title, $titlewords, $titlepositions, $positivewords);
         // Strip searchable-but-not-displayable content for summary
         if (isset($evilmarker)) {
             $strippedwords = array();
             foreach ($contentwords as $word) {
                 // Do not include extra strings in summary
                 if ($word == 'xxrealcontentends' . $evilmarker) {
                     break;
                 }
                 $strippedwords[] = $word;
             }
             $contentwords = $strippedwords;
         }
         // Pick a section to include in the summary. This algorithm works as follows:
         // * Compute the 'score' (number of highlight words in the previous 20 words
         //   up to and including this one) at each position in the text
         // * Observe where the maximum score is reached and where it is lost.
         // * A nice range that contains the most highlight words in the middle of the
         //   range will end at ($maxstart + $maxlength/2).
         $highlights = array();
         $pos = 0;
         $currentscore = 0;
         $maxscore = -1;
         $maxstart = 0;
         $maxlength = 0;
         $run = true;
         foreach ($contentwords as $word) {
             if (array_key_exists($pos - OUSEARCH_SUMMARYLENGTH, $highlights)) {
                 unset($highlights[$pos - OUSEARCH_SUMMARYLENGTH]);
                 $currentscore--;
             }
             if (array_key_exists($word, $positivewords)) {
                 $highlights[$pos] = true;
                 $currentscore++;
             }
             if ($currentscore > $maxscore) {
                 $maxscore = $currentscore;
                 $maxstart = $pos;
                 $maxlength = 1;
                 $run = true;
             } else {
                 if ($currentscore === $maxscore && $run) {
                     $maxlength++;
                 } else {
                     $run = false;
                 }
             }
             $pos++;
         }
         $start = $maxstart + $maxlength / 2 - OUSEARCH_SUMMARYLENGTH;
         if ($start < 0) {
             $start = 0;
         }
         $end = $start + OUSEARCH_SUMMARYLENGTH;
         if ($end > count($contentwords)) {
             $end = count($contentwords);
         }
         // $contentpositions is in characters
         $result->summary = $tl->substr($textcontent, $contentpositions[$start], $contentpositions[$end] - $contentpositions[$start]) . ($end < count($contentwords) ? '...' : '');
         $offset = -$contentpositions[$start];
         $result->summary = self::internal_highlight_words($result->summary, $contentwords, $contentpositions, $positivewords, $offset, $start, $end);
         if ($start !== 0) {
             $result->summary = '...' . $result->summary;
         }
         $result->summary = trim($result->summary);
         $result->activityname = $page->activityname;
         $result->activityurl = $page->activityurl;
         $result->url = $page->url;
         if (isset($page->data)) {
             $result->data = $page->data;
         }
         // Do user-specified filter if set
         if ($this->filter) {
             $filter = $this->filter;
             if (!$filter($result)) {
                 continue;
             }
         }
         $accepted[] = $result;
         $count++;
         if ($count == $desired) {
             break;
         }
     }
     $return->results = $accepted;
     return $return;
 }