/**
  * Create temporary test tables and entries in the database for these tests.
  * These tests have to work on a brand new site.
  */
 function setUp()
 {
     global $CFG;
     parent::setup();
     $this->switch_to_test_db();
     // All operations until end of test method will happen in test DB
     // additional tables required if ousearch module is present
     if (ouwiki_search_installed()) {
         $this->tables['local/ousearch'] = array('local_ousearch_documents', 'local_ousearch_words', 'local_ousearch_occurrences');
     }
     foreach ($this->tables as $dir => $tables) {
         $this->create_test_tables($tables, $dir);
         // Create tables
         foreach ($tables as $table) {
             // Fill them if load_xxx method is available
             $function = "load_{$table}";
             if (method_exists($this, $function)) {
                 $this->{$function}();
             }
         }
     }
 }
function ouwiki_delete_instance($id)
{
    global $DB, $CFG;
    require_once $CFG->dirroot . '/mod/ouwiki/locallib.php';
    $cm = get_coursemodule_from_instance('ouwiki', $id, 0, false, MUST_EXIST);
    // Delete associated template data.
    $context = context_module::instance($cm->id);
    $fs = get_file_storage();
    $fs->delete_area_files($context->id, 'mod_ouwiki', 'template', $id);
    // Delete search data
    if (ouwiki_search_installed()) {
        local_ousearch_document::delete_module_instance_data($cm);
    }
    // Delete grade
    $ouwiki = $DB->get_record('ouwiki', array('id' => $cm->instance));
    ouwiki_grade_item_delete($ouwiki);
    // Subqueries that find all versions and pages associated with this wiki
    // and delete them all bottom up
    $versions = $DB->get_records_sql("SELECT DISTINCT v.id\n                        FROM {ouwiki_subwikis} s\n                        INNER JOIN {ouwiki_pages} p ON p.subwikiid = s.id\n                        INNER JOIN {ouwiki_versions} v ON v.pageid = p.id\n                        WHERE s.wikiid = ?", array($id));
    if (!empty($versions)) {
        list($vsql, $vparams) = $DB->get_in_or_equal(array_keys($versions));
        $DB->delete_records_select('ouwiki_links', "fromversionid {$vsql}", $vparams);
    }
    $pages = $DB->get_records_sql("SELECT p.id\n                    FROM {ouwiki_subwikis} s\n                    INNER JOIN {ouwiki_pages} p ON p.subwikiid = s.id\n                    WHERE s.wikiid = ?", array($id));
    if (!empty($pages)) {
        list($psql, $pparams) = $DB->get_in_or_equal(array_keys($pages));
        $DB->delete_records_select('ouwiki_versions', "pageid {$psql}", $pparams);
        $DB->delete_records_select('ouwiki_locks', "pageid {$psql}", $pparams);
        $DB->delete_records_select('ouwiki_sections', "pageid {$psql}", $pparams);
    }
    $subwikis = $DB->get_records_sql("SELECT s.id\n                        FROM {ouwiki_subwikis} s\n                        WHERE s.wikiid = ?", array($id));
    if (!empty($subwikis)) {
        list($swsql, $swparams) = $DB->get_in_or_equal(array_keys($subwikis));
        $DB->delete_records_select('ouwiki_pages', "subwikiid {$swsql}", $swparams);
    }
    $DB->delete_records_select('ouwiki_subwikis', 'wikiid = ?', array($id));
    $DB->delete_records('ouwiki', array('id' => $id));
    return true;
}
 protected function after_execute()
 {
     global $DB, $CFG;
     $transaction = $DB->start_delegated_transaction();
     $ouwikiid = $this->get_task()->get_activityid();
     // Flush out any unsaved versions.
     $this->flush_versions();
     // Add ouwiki related files, no need to match by itemname (just internally handled context).
     $this->add_related_files('mod_ouwiki', 'intro', null);
     $this->add_related_files('mod_ouwiki', 'template', 'ouwiki');
     // Add post related files.
     $this->add_related_files('mod_ouwiki', 'attachment', 'ouwiki_version');
     $this->add_related_files('mod_ouwiki', 'content', 'ouwiki_version');
     // Update firstversionid.
     $sql = 'SELECT v.pageid,
                 (SELECT MIN(id)
                 FROM {ouwiki_versions} v3
                 WHERE v3.pageid = p.id AND v3.deletedat IS NULL)
                 AS firstversionid
             FROM
                 {ouwiki} o
                 JOIN {ouwiki_subwikis} s ON s.wikiid = o.id
                 JOIN {ouwiki_pages} p ON p.subwikiid = s.id
                 JOIN {ouwiki_versions} v ON v.pageid = p.id
             WHERE
                 o.id = ?
             GROUP BY v.pageid, p.id
             ORDER BY v.pageid';
     $rs = $DB->get_recordset_sql($sql, array($ouwikiid));
     foreach ($rs as $entry) {
         if ($entry->firstversionid) {
             $DB->set_field('ouwiki_pages', 'firstversionid', $entry->firstversionid, array('id' => $entry->pageid));
         }
     }
     $rs->close();
     // Update previousversionid.
     $sql = 'SELECT v.id AS versionid,
                 (SELECT MAX(v2.id)
                 FROM {ouwiki_versions} v2
                 WHERE v2.pageid = p.id AND v2.id < v.id)
                 AS previousversionid
             FROM
                 {ouwiki} o
                 JOIN {ouwiki_subwikis} s ON s.wikiid = o.id
                 JOIN {ouwiki_pages} p ON p.subwikiid = s.id
                 JOIN {ouwiki_versions} v ON v.pageid = p.id
             WHERE
                 o.id = ?';
     $rs = $DB->get_recordset_sql($sql, array($ouwikiid));
     foreach ($rs as $entry) {
         if ($entry->previousversionid) {
             $DB->set_field('ouwiki_versions', 'previousversionid', $entry->previousversionid, array('id' => $entry->versionid));
         }
     }
     $rs->close();
     // Update all the page ids for links.
     $sql = 'SELECT l.id AS linkid, l.topageid
             FROM
                 {ouwiki} o
                 JOIN {ouwiki_subwikis} s ON s.wikiid = o.id
                 JOIN {ouwiki_pages} p ON p.subwikiid = s.id
                 JOIN {ouwiki_versions} v ON v.pageid = p.id
                 JOIN {ouwiki_links} l ON l.fromversionid = v.id
             WHERE
                 o.id = ? AND l.topageid IS NOT NULL';
     $rs = $DB->get_recordset_sql($sql, array($ouwikiid));
     $errors = array();
     foreach ($rs as $entry) {
         $newpageid = $this->get_mappingid('ouwiki_page', $entry->topageid, null);
         if (!$newpageid && empty($errors[$entry->topageid])) {
             $errors[$entry->topageid] = true;
             $this->get_logger()->process('OU wiki: link to missing pageid ' . $entry->topageid . ' not restored properly.', backup::LOG_WARNING);
         }
         $DB->set_field('ouwiki_links', 'topageid', $newpageid, array('id' => $entry->linkid));
     }
     $rs->close();
     // Update xhtml field with correct annotations.
     // Get all table entries in ouwiki_versions table for this wiki.
     $sql = "SELECT v.id, v.xhtml\n                    FROM {ouwiki_subwikis} s\n                    JOIN {ouwiki_pages} p ON p.subwikiid = s.id\n                    JOIN {ouwiki_versions} v ON v.pageid = p.id\n                    WHERE s.wikiid = ?\n                    ORDER BY v.id";
     $rs = $DB->get_recordset_sql($sql, array($ouwikiid));
     // Go through annotation elements ids replacing old annotation ids with new annotation ids in xhtml field of result set.
     foreach ($rs as $entry) {
         $matches = array();
         // Check to see whether this contains any annotations to be replaced.
         $found = preg_match_all('~(span id=")(annotation[0-9]+)(")~', $entry->xhtml, $matches, PREG_SET_ORDER);
         if ($found) {
             foreach ($matches as $arr) {
                 // Check to see whether an old array key exist.
                 if (array_key_exists($arr[2], $this->elementsids)) {
                     // Do the replace.
                     $replacestr = 'span id="' . $this->elementsids[$arr[2]] . '"';
                     $entry->xhtml = str_replace($arr[0], $replacestr, $entry->xhtml);
                 }
             }
             // Set the xhtml field if any annotation ids replaced.
             $DB->set_field('ouwiki_versions', 'xhtml', $entry->xhtml, array('id' => $entry->id));
         }
     }
     // Close the result set.
     $rs->close();
     $transaction->allow_commit();
     require_once $CFG->dirroot . '/mod/ouwiki/locallib.php';
     // Create search index if user data restored.
     if ($this->get_setting_value('userinfo') && ouwiki_search_installed()) {
         ouwiki_ousearch_update_all(false, $this->get_courseid());
     }
 }
/**
 * Returns html for a search form for the nav bar
 * @param object $subwiki wiki to be searched
 * @param int $cmid wiki to be searched
 * @return string html
 */
function ouwiki_get_search_form($subwiki, $cmid)
{
    if (!ouwiki_search_installed()) {
        return '';
    }
    global $OUTPUT, $CFG;
    $queryhtml = ($query = optional_param('query', '', PARAM_RAW)) ? htmlspecialchars($query) : '';
    $out = html_writer::start_tag('form', array('action' => 'search.php', 'method' => 'get'));
    $out .= html_writer::start_tag('div');
    $out .= html_writer::tag('label', get_string('search', 'ouwiki'), array('for' => 'ouwiki_searchquery'));
    $out .= $OUTPUT->help_icon('search', 'ouwiki');
    $out .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'id', 'value' => $cmid));
    if (!$subwiki->defaultwiki) {
        if ($subwiki->groupid) {
            $out .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'group', 'value' => $subwiki->groupid));
        }
        if ($subwiki->userid) {
            $out .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'user', 'value' => $subwiki->userid));
        }
    }
    $out .= html_writer::empty_tag('input', array('type' => 'text', 'name' => 'query', 'id' => 'ouwiki_searchquery', 'value' => $queryhtml));
    $out .= html_writer::empty_tag('input', array('type' => 'submit', 'id' => 'ousearch_searchbutton', 'value' => '', 'alt' => get_string('search'), 'title' => get_string('search')));
    $out .= html_writer::end_tag('div');
    $out .= html_writer::end_tag('form');
    return $out;
}
function ouwiki_delete_instance($id)
{
    if (ouwiki_search_installed()) {
        $moduleid = get_field('modules', 'id', 'name', 'ouwiki');
        $cm = get_record('course_modules', 'module', $moduleid, 'instance', $id);
        if (!$cm) {
            error('Can\'t find coursemodule');
        }
        ousearch_document::delete_module_instance_data($cm);
    }
    global $CFG;
    // Subqueries that find all versions and pages associated with this wiki
    $versionquery = "\nSELECT\n    v.id\nFROM\n    {$CFG->prefix}ouwiki_subwikis s\n    INNER JOIN {$CFG->prefix}ouwiki_pages p ON p.subwikiid=s.id\n    INNER JOIN {$CFG->prefix}ouwiki_versions v ON v.pageid=p.id\nWHERE\n    s.wikiid={$id}";
    $sectionquery = "\nSELECT\n    sc.id\nFROM\n    {$CFG->prefix}ouwiki_subwikis s\n    INNER JOIN {$CFG->prefix}ouwiki_pages p ON p.subwikiid=s.id\n    INNER JOIN {$CFG->prefix}ouwiki_sections sc ON sc.pageid=p.id\nWHERE\n    s.wikiid={$id}";
    $pagequery = "\nSELECT\n    p.id\nFROM\n    {$CFG->prefix}ouwiki_subwikis s\n    INNER JOIN {$CFG->prefix}ouwiki_pages p ON p.subwikiid=s.id\nWHERE\n    s.wikiid={$id}";
    $subwikiquery = "\nSELECT\n    s.id\nFROM\n    {$CFG->prefix}ouwiki_subwikis s\nWHERE\n    s.wikiid={$id}";
    // Delete everything, bottom-up
    $ok = true;
    $ok = delete_records_select('ouwiki_links', "fromversionid IN ({$versionquery})") && $ok;
    $ok = delete_records_select('ouwiki_comments', "sectionid IN ({$sectionquery})") && $ok;
    $ok = delete_records_select('ouwiki_versions', "pageid IN ({$pagequery})") && $ok;
    $ok = delete_records_select('ouwiki_locks', "pageid IN ({$pagequery})") && $ok;
    $ok = delete_records_select('ouwiki_sections', "pageid IN ({$pagequery})") && $ok;
    $ok = delete_records_select('ouwiki_pages', "subwikiid IN ({$subwikiquery})") && $ok;
    $ok = delete_records_select('ouwiki_subwikis', "wikiid={$id}") && $ok;
    $ok = delete_records("ouwiki", "id", "{$id}") && $ok;
    return $ok;
}
function ouwiki_decode_content_links_caller($restore)
{
    // Get all the items that might have links in, from the relevant new course
    try {
        global $CFG, $db;
        // 1. Summaries
        if ($summaries = get_records_select('ouwiki', 'course=' . $restore->course_id . ' AND summary IS NOT NULL', '', 'id,summary')) {
            foreach ($summaries as $summary) {
                $newsummary = restore_decode_content_links_worker($summary->summary, $restore);
                if ($newsummary != $summary->summary) {
                    if (!set_field('ouwiki', 'summary', addslashes($newsummary), 'id', $summary->id)) {
                        throw new Exception("Failed to set summary for wiki {$summary->id}: " . $db->ErrorMsg());
                    }
                }
            }
        }
        // 2. Actual content
        $rs = get_recordset_sql("\nSELECT\n    v.id,v.xhtml     \nFROM\n    {$CFG->prefix}ouwiki w\n    INNER JOIN {$CFG->prefix}ouwiki_subwikis s ON w.id=s.wikiid\n    INNER JOIN {$CFG->prefix}ouwiki_pages p ON s.id=p.subwikiid\n    INNER JOIN {$CFG->prefix}ouwiki_versions v ON p.id=v.pageid     \nWHERE\n    w.course={$restore->course_id} \n");
        if (!$rs) {
            throw new Exception("Failed to query for wiki data: " . $db->ErrorMsg());
        }
        while (!$rs->EOF) {
            $newcontent = restore_decode_content_links_worker($rs->fields['xhtml'], $restore);
            if ($newcontent != $rs->fields['xhtml']) {
                if (!set_field('ouwiki_versions', 'xhtml', addslashes($newcontent), 'id', $rs->fields['id'])) {
                    throw new Exception("Failed to update content {$rs->fields['id']}: " . $db->ErrorMsg());
                }
            }
            $rs->MoveNext();
        }
        // 3. This is a bit crappy, as it isn't directly to do with content links, but
        //    we can't do it until we have a course-module so it can't happen earlier.
        if (ouwiki_search_installed()) {
            ouwiki_ousearch_update_all(false, $restore->course_id);
        }
        return true;
    } catch (Exception $e) {
        ouwiki_handle_backup_exception($e, 'restore');
        return false;
    }
}
/**
 * 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();
}