diff --git a/mod/wiki/README b/mod/wiki/README
new file mode 100644
index 00000000000..e7eaf50a849
--- /dev/null
+++ b/mod/wiki/README
@@ -0,0 +1,5 @@
+Wiki 2.0
+
+by Jordi Piguillem and Ludo (Marc Alier) 2008 - Universitat Politecnica de
+Catalunya
+http://www.upc.edu - http://dfwikilabs.org
\ No newline at end of file
diff --git a/mod/wiki/TODO.txt b/mod/wiki/TODO.txt
deleted file mode 100644
index d40862b929d..00000000000
--- a/mod/wiki/TODO.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-ToDo
-Mike:
- - Group Handling (Mike)
- > deal with changing group mode on active wikis
- > handle group mode for teacher wikis
- - Rating
- - Commenting
- - Grading
-
-Michael:
- - Upload: There is an error when having a bin html-wiki: File does not show up
- - Log when up- or download
- - Notify when page changes
-
-Unassigned or not ready:
- - Wiki HTML: http://moodle.org/mod/forum/discuss.php?d=8920
- - http://moodle.org/mod/forum/discuss.php?d=7768#36954
- - Image Thumbnails http://moodle.org/mod/forum/discuss.php?d=8351
-
-
-ewiki Preparation:
- - Current Version: 1.01d
diff --git a/mod/wiki/admin.php b/mod/wiki/admin.php
deleted file mode 100644
index 1be7801e36d..00000000000
--- a/mod/wiki/admin.php
+++ /dev/null
@@ -1,366 +0,0 @@
-get_record("course", array("id"=>$cm->course))) {
- print_error('coursemisconf');
- }
-
- if (! $wiki = $DB->get_record("wiki", array("id"=>$cm->instance))) {
- print_error('invalidcoursemodule');
- }
-
- } else {
- if (! $wiki = $DB->get_record("wiki", array("id"=>$a))) {
- print_error('coursemisconf');
- }
- if (! $course = $DB->get_record("course", array("id"=>$wiki->course))) {
- print_error('coursemisconf');
- }
- if (! $cm = get_coursemodule_from_instance("wiki", $wiki->id, $course->id)) {
- print_error('invalidcoursemodule');
- }
- }
-
- $url = new moodle_url('/mod/wiki/admin.php', array('id'=>$cm->id));
- if ($page !== false) {
- $url->param('page', $page);
- }
- if ($confirm !== '') {
- $url->param('confirm', $confirm);
- }
- if ($action !== '') {
- $url->param('action', $action);
- }
- if ($userid !== 0) {
- $url->param('userid', $userid);
- }
- if ($groupid !== 0) {
- $url->param('groupid', $groupid);
- }
- $PAGE->set_url($url);
-
- require_login($course->id, false, $cm);
- $context = get_context_instance(CONTEXT_MODULE, $cm->id);
- require_capability('mod/wiki:manage', $context);
-
- /// Build the ewsiki script constant
- $ewbase = 'view.php?id='.$id;
- if (isset($userid) && $userid!=0) $ewbase .= '&userid='.$userid;
- if (isset($groupid) && $groupid!=0) $ewbase .= '&groupid='.$groupid;
- $ewscript = $ewbase.'&page=';
- define("EWIKI_SCRIPT", $ewscript);
- if($wiki->ewikiacceptbinary) {
- define("EWIKI_UPLOAD_MAXSIZE", get_max_upload_file_size());
- define("EWIKI_SCRIPT_BINARY", $ewbase."&binary=");
- }
-
-
- /// Add the course module 'groupmode' to the wiki object, for easy access.
- $wiki->groupmode = $cm->groupmode;
-
- /// Is an Action given ?
- if(!$action) {
- print_error('noadministrationaction','wiki');
- }
-
- /// Correct Action ?
- if(!in_array($action, array("setpageflags", "removepages", "strippages", "checklinks", "revertpages"))) {
- print_error('unknowaction');
- }
-
-
- /// May the User administrate it ?
- if (($wiki_entry = wiki_get_entry($wiki, $course, $userid, $groupid)) === false || wiki_can_edit_entry($wiki_entry, $wiki, $USER, $course) === false) {
- print_error('notadministratewiki', 'wiki');
- }
-
- $canedit = wiki_can_edit_entry($wiki_entry, $wiki, $USER, $course);
- # Check for dangerous events (hacking) !
- if(in_array($action,array("removepages","strippages","revertpages"))) {
- if(!($wiki->wtype=="student" || ($wiki->wtype=="group" and $canedit) || wiki_is_teacher($wiki))) {
- add_to_log($course->id, "wiki", "hack", "", $wiki->name.": Tried to trick admin.php with action=$action.");
- print_error('hackdetected');
- }
- }
-
- # Database and Binary Handler
- include_once($CFG->dirroot."/mod/wiki/ewikimoodlelib.php");
- include_once($CFG->dirroot."/mod/wiki/ewiki/plugins/moodle/moodle_binary_store.php");
-
- /// The wiki_entry->pagename is set to the specified value of the wiki,
- /// or the default value in the 'lang' file if the specified value was empty.
- define("EWIKI_PAGE_INDEX",$wiki_entry->pagename);
- # The mighty Wiki itself
- include_once($CFG->dirroot."/mod/wiki/ewiki/ewiki.php");
-
- $strwikis = get_string("modulenameplural", "wiki");
- $strwiki = get_string("modulename", "wiki");
-
- /// Validate Form
- if ($form = data_submitted()) {
- switch($action) {
- case "revertpages":
- if(!$form->deleteversions || 0 > $form->deleteversions || $form->deleteversions > 1000) {
- $focus="form.deleteversions";
- $err->deleteversions=get_string("deleteversionserror","wiki");
- }
- if(!$form->changesfield || 0 > $form->changesfield || $form->changesfield > 100000) {
- $focus="form.changesfield";
- $err->changesfield=get_string("changesfielderror","wiki");
- }
- if($form->authorfieldpattern=="") {
- $focus="form.authorfieldpattern";
- $err->authorfieldpattern=get_string("authorfieldpatternerror","wiki");
- }
- break;
- default: break;
- }
- }
-
- $PAGE->navbar->add(get_string("administration","wiki"));
- $PAGE->set_title($wiki_entry->pagename);
- $PAGE->set_focuscontrol($focus);
- $PAGE->set_button($OUTPUT->update_module_button($cm->id, 'wiki'));
- echo $OUTPUT->header();
-
- ////////////////////////////////////////////////////////////
- /// Check if the Form has been submitted and display confirmation
- ////////////////////////////////////////////////////////////
- if ($form = data_submitted()) {
- /// Moodle Log
- /// Get additional info
- $addloginfo="";
- switch($action) {
- case "removepages":
- $addloginfo=@join(", ", $form->pagestodelete);
- break;
- case "strippages":
- $addloginfo=@join(", ", $form->pagestostrip);
- break;
- case "checklinks":
- $addloginfo=$form->pagetocheck;
- break;
- case "setpageflags":
- // No additional info
- break;
- case "revertpages":
- // No additional info
- break;
- }
- add_to_log($course->id, "wiki", $action, "admin.php?action=$action&userid=$userid&groupid=$groupid&id=$id", $wiki->name.($addloginfo?": ".$addloginfo:""));
- $link="admin.php?action=$action".($userid?"&userid=".$userid:"").($groupid?"&groupid=".$groupid:"")."&id=$id&page=$page";
- switch($action) {
- case "removepages":
- if($form->proceed) {
- if(!$confirm && $form->pagestodelete) {
- echo $OUTPUT->confirm(get_string("removepagecheck", "wiki")."
".join(", ", $form->pagestodelete),
- $link."&confirm=".urlencode(join(" ",$form->pagestodelete)), $link);
- echo $OUTPUT->footer();
- exit;
- }
- }
- break;
- case "strippages":
- if($form->proceed) {
- if(!$confirm && $form->pagestostrip) {
- $err=array();
- $strippages=wiki_admin_strip_versions($form->pagestostrip,$form->version, $err);
- $confirm="";
- foreach($strippages as $cnfid => $cnfver) {
- $confirm.="&confirm[$cnfid]=".urlencode(join(" ",$cnfver));
- }
- if(count($err)==0) {
- $pagestostrip=array();
- foreach($form->pagestostrip as $pagetostrip) {
- $pagestostrip[]=htmlspecialchars(urldecode($pagetostrip));
- }
- echo $OUTPUT->confirm(get_string("strippagecheck", "wiki")."
".join(", ", $pagestostrip), $link.$confirm, $link);
- echo $OUTPUT->footer();
- exit;
- }
- }
- }
- break;
- case "checklinks":
- if($form->proceed) {
- if(!$confirm && $form->pagetocheck) {
- $confirm="&confirm=".$form->pagetocheck;
- echo $OUTPUT->confirm(get_string("checklinkscheck", "wiki").$form->pagetocheck, $link.$confirm, $link);
- echo $OUTPUT->footer();
- exit;
- }
- }
- break;
- case "setpageflags":
- // pageflagstatus is used in setpageflags.html
- $pageflagstatus=wiki_admin_setpageflags($form->flags);
- break;
- case "revertpages":
- if(!$err) {
- if(!$confirm) {
- $confirm="&confirm[changesfield]=".urlencode($form->changesfield).
- "&confirm[authorfieldpattern]=".urlencode($form->authorfieldpattern).
- "&confirm[howtooperate]=".urlencode($form->howtooperate).
- "&confirm[deleteversions]=".urlencode($form->deleteversions);
- $revertedpages=wiki_admin_revert("", $form->authorfieldpattern, $form->changesfield, $form->howtooperate, $form->deleteversions);
- if($revertedpages) {
- echo $OUTPUT->confirm(get_string("revertpagescheck", "wiki")."
".$revertedpages, $link.$confirm, $link);
- echo $OUTPUT->footer();
- exit;
- } else {
- $err->remark=get_string("nochangestorevert","wiki");
- }
- }
- }
- break;
- default: print_error('unknowaction');
- break;
- }
- }
-
- /// Actions which need a confirmation. If confirmed, do the action
- $redirect="view.php?".($groupid?"&groupid=".$groupid:"").($userid?"&userid=".$userid:"")."&id=$id&page=$page";
- if($confirm && !$err) {
- switch($action) {
- case "removepages":
- $ret=wiki_admin_remove(split(" ",$confirm), $course, $wiki, $userid, $groupid);
- if(!$ret) {
- redirect($redirect, get_string("pagesremoved","wiki"), 1);
- } else {
- print_error('invalidaction');
- }
- exit;
- case "strippages":
- $strippages=array();
- foreach($confirm as $pageid => $versions) {
- $strippages[$pageid]=split(" ",$versions);
- }
- $ret=wiki_admin_strip($strippages);
- if(!$ret) {
- redirect($redirect, get_string("pagesstripped","wiki"), 1);
- } else {
- print_error('invalidaction');
- }
- exit;
- case "checklinks":
- $ret=wiki_admin_checklinks($confirm);
- redirect($redirect, get_string("linkschecked","wiki")."
".$ret, 5);
- exit;
- case "revertpages":
- $revertedpages=wiki_admin_revert(1, $confirm["authorfieldpattern"], $confirm["changesfield"], $confirm["howtooperate"], $confirm["deleteversions"]);
- redirect($redirect, get_string("pagesreverted","wiki"), 1);
- exit;
- case "setpageflags":
- # No confirmation needed
- break;
- default: print_error('unknowaction');
- }
- }
-
-
- /// The top row contains links to other wikis, if applicable.
- if ($wiki_list = wiki_get_other_wikis($wiki, $USER, $course, $wiki_entry->id)) {
- if (isset($wiki_list['selected'])) {
- $selected = $wiki_list['selected'];
- unset($wiki_list['selected']);
- }
- echo '
| '; -* $specialpages=array("SearchPages", "PageIndex","NewestPages","MostVisitedPages","MostOftenChangedPages","UpdatedPages","FileDownload","FileUpload","OrphanedPages","WantedPages"); -* wiki_print_page_actions($cm->id, $specialpages, $ewiki_id, $ewiki_action, $wiki->ewikiacceptbinary, $canedit); -* echo ' | ';*/ - - /// Searchform - echo ''; - wiki_print_search_form($cm->id, $q, $userid, $groupid, false); - echo ' | '; - - /// Internal Wikilinks - - /// TODO: DOES NOT WORK !!!! - echo ''; - wiki_print_wikilinks_block($cm->id, $wiki->ewikiacceptbinary); - echo ' | '; - - /// Administrative Links - echo ''; - wiki_print_administration_actions($wiki, $cm->id, $userid, $groupid, $page, $wiki->htmlmode!=2, $course); - echo ' | '; - -/** if($wiki->htmlmode!=2) { -* echo ''; -* helpbutton('formattingrules', get_string('formattingrules', 'wiki'), 'wiki'); -* echo get_string("formattingrules","wiki"); -* echo ' | '; -* }*/ - - echo '
"; print_r($uu); print ""; global $ewiki_links, $ewiki_plugins, $ewiki_config, $ewiki_id; @@ -2319,7 +2318,8 @@ function ewiki_link_regex_callback($uu, $force_noimg=0) { #-- explicit title given via [ title | WikiLink ] $href = $title = strtok($str, "|"); if ($uu = strtok("|")) { - $href = $uu; + $parts = explode('|', $str); + $title = $parts[1] . ' | ' .$parts[0]; $states["titled"] = 1; } #-- title and href swapped: swap back @@ -2395,8 +2395,7 @@ function ewiki_link_regex_callback($uu, $force_noimg=0) { #### BEGIN MOODLE CHANGES global $ewiki_link_case; $href_realcase=array_key_exists($href_i,$ewiki_link_case) ? $ewiki_link_case[$href_i] : $href; - $str = '' . $title . ''; + $str = '[[' . $title . ']]'; #### END MOODLE CHANGES } #-- guess for mail@addresses, convert to URI if @@ -2418,8 +2417,9 @@ function ewiki_link_regex_callback($uu, $force_noimg=0) { #-- (QuestionMarkLink to edit/ action) if (!$str) { $type = array("notfound"); - $str = '' . $title . '?'; + //$str = '' . $title . '?'; + $str = '[['. $title . ']]'; } } @@ -2446,8 +2446,15 @@ function ewiki_link_regex_callback($uu, $force_noimg=0) { if (EWIKI_SCRIPT_BINARY && ((strpos($href, EWIKI_IDF_INTERNAL)===0) || EWIKI_IMAGE_MAXSIZE && EWIKI_CACHE_IMAGES && $img && !$nocache) ) { - $type = array("binary"); - $href = ewiki_script_binary("", $href); + $type = array("binary"); + //$href = ewiki_script_binary("", $href); + //##### BEGIN MOODLE ADDITION ##### + $pattern = 'internal://'; + $matches = array(); + $filename = str_replace($pattern, '', $href); + $filename = clean_param($filename, PARAM_FILE); + $href = "@@PLUGINFILE@@/$filename"; + //##### END MOODLE ADDITION ##### } #-- output html reference @@ -2501,12 +2508,11 @@ function ewiki_link_regex_callback($uu, $force_noimg=0) { function ewiki_interwiki($href, &$type) { global $ewiki_config, $ewiki_plugins; - if (strpos($href, ":") and !strpos($href, "//") - and ($p1 = strtok($href, ":"))) { + if (strpos($href, ":") and !strpos($href, "//") and ($p1 = strtok($href, ":"))) { $page = strtok("\000"); - if (($p1 = ewiki_array($ewiki_config["interwiki"], $p1)) !== NULL) { + if (!empty($ewiki_config["interwiki"]) && (($p1 = ewiki_array($ewiki_config["interwiki"], $p1)) !== NULL)) { $type = array("interwiki", $uu); while ($p1_alias = $ewiki_config["interwiki"][$p1]) { $type[] = $p1; @@ -2877,7 +2883,7 @@ function ewiki_localization() { global $ewiki_t, $ewiki_plugins; $deflangs = ','.@$_ENV["LANGUAGE"] . ','.@$_ENV["LANG"] - . ",".EWIKI_DEFAULT_LANG . ",en,C"; + . ",".wiki_get_define('EWIKI_DEFAULT_LANG') . ",en,C"; foreach (explode(",", @$_SERVER["HTTP_ACCEPT_LANGUAGE"].$deflangs) as $l) { diff --git a/mod/wiki/ewiki/plugins/moodle/moodle_rescue_html.php b/mod/wiki/db/migration/wiki/ewiki/plugins/moodle/moodle_rescue_html.php similarity index 97% rename from mod/wiki/ewiki/plugins/moodle/moodle_rescue_html.php rename to mod/wiki/db/migration/wiki/ewiki/plugins/moodle/moodle_rescue_html.php index 591c7d1198d..64de6712e73 100644 --- a/mod/wiki/ewiki/plugins/moodle/moodle_rescue_html.php +++ b/mod/wiki/db/migration/wiki/ewiki/plugins/moodle/moodle_rescue_html.php @@ -1,4 +1,4 @@ - @@ -23,11 +23,11 @@ function ewiki_moodle_rescue_html(&$wiki_source) { $rescue_html = array( "br", "tt", "b", "i", "strong", "em", "s", "kbd", "var", "xmp", "sup", "sub", - "pre", "q", "h1", "h2", "h3", "h4", "h5", "h6", "cite", "code", "u", + "pre", "q", "h1", "h2", "h3", "h4", "h5", "h6", "cite", "code", "u", ); - - + + #-- unescape allowed html if ($safe_html) { /* @@ -43,4 +43,4 @@ function ewiki_moodle_rescue_html(&$wiki_source) { } - +?> diff --git a/mod/wiki/ewikimoodlelib.php b/mod/wiki/db/migration/wiki/ewikimoodlelib.php similarity index 97% rename from mod/wiki/ewikimoodlelib.php rename to mod/wiki/db/migration/wiki/ewikimoodlelib.php index f74307ae884..9d0d1c7f9c0 100644 --- a/mod/wiki/ewikimoodlelib.php +++ b/mod/wiki/db/migration/wiki/ewikimoodlelib.php @@ -9,13 +9,13 @@ $ewiki_plugins["database"][0] = "ewiki_database_moodle"; /// #-- predefine some of the configuration constants -define("EWIKI_NAME", $wiki_entry->pagename); +//define("EWIKI_NAME", $wiki_entry->pagename); COMMENTED BY PIGUI BECOUSE OF MIGRATION define("EWIKI_CONTROL_LINE", 0); define("EWIKI_LIST_LIMIT", 25); -define("EWIKI_DEFAULT_LANG", current_language()); +//define("EWIKI_DEFAULT_LANG", current_language()); COMMENTED BY PIGUI BECOUSE OF MIGRATION define("EWIKI_HTML_CHARS", 1); -define("EWIKI_DB_TABLE_NAME", "wiki_pages"); +define("EWIKI_DB_TABLE_NAME", "wiki_pages_old"); function ewiki_database_moodle($action, &$args, $sw1, $sw2) { diff --git a/mod/wiki/db/upgrade.php b/mod/wiki/db/upgrade.php index 25d5d0b702f..532150d56d6 100644 --- a/mod/wiki/db/upgrade.php +++ b/mod/wiki/db/upgrade.php @@ -1,74 +1,316 @@ . + +/** + * This file keeps track of upgrades to the wiki module + * + * Sometimes, changes between versions involve + * alterations to database structures and other + * major things that may break installations. + * + * The upgrade function in this file will attempt + * to perform all the necessary actions to upgrade + * your older installtion to the current version. + * + * @package mod-wiki-2.0 + * @copyrigth 2009 Marc Alier, Jordi Piguillem marc.alier@upc.edu + * @copyrigth 2009 Universitat Politecnica de Catalunya http://www.upc.edu + * + * @author Jordi Piguillem + * + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + * + */ + +/** + * + * TODO LIST: + * + * 1. Add needed fields to wiki table. DONE + * 2. Rename other wiki tables. DONE + * 3. Create new wiki tables. DONE BUT NOT FINISHED, WATING FOR NEW TABLES + * 4. Move/Adapt/Transform configurations info to new structure + * 5. Migrate wiki entries to subwikis. DONE + * 6. Fill pages table with latest versions of every page. DONE + * 7. Migrate page history to new table (transforming formats). DONE, BUT STILL WORKING + * 8. Fill links table + * 9. Drop useless information + * + * ADITIONAL THINGS AFTER CHAT WITH ELOY: + * + * 1. addField is deprecated. DONE + * 2. Fix SQL error at block 3. DONE + * 3. Merge set_field_select with previous update sentence. DONE + * 4. Don't insert id fields on database (it won't work on mssql, oracle, pg). DONE. + * 5. Use upgrade_set_timeout function. + * 6. Use grafic of progess + * + * OTHER THINGS: + * + * 1. Use recordset instead of record when migrating historic + * 2. Select only usefull data on block 06 + * + */ function xmldb_wiki_upgrade($oldversion) { - global $CFG, $DB; + global $CFG, $DB, $OUTPUT; $dbman = $DB->get_manager(); $result = true; -//===== 1.9.0 upgrade line ======// + // Step 0: Add new fields to main wiki table + if ($result && $oldversion < 2010040100) { + require_once(dirname(__FILE__) . '/upgradelib.php'); + echo $OUTPUT->notification('Adding new fields to wiki table', 'notifysuccess'); + wiki_add_wiki_fields(); - if ($result && $oldversion < 2009042000) { - - /// Rename field summary on table wiki to intro - $table = new xmldb_table('wiki'); - $field = new xmldb_field('summary', XMLDB_TYPE_TEXT, 'small', null, XMLDB_NOTNULL, null, null, 'name'); - - /// Launch rename field summary - $dbman->rename_field($table, $field, 'intro'); - - /// wiki savepoint reached - upgrade_mod_savepoint($result, 2009042000, 'wiki'); + upgrade_mod_savepoint($result, 2010040100, 'wiki'); } - if ($result && $oldversion < 2009042001) { + // Step 1: Rename old tables + if ($result && $oldversion < 2010040101) { + $tables = array('wiki_pages', 'wiki_locks', 'wiki_entries'); - /// Define field introformat to be added to wiki - $table = new xmldb_table('wiki'); - $field = new xmldb_field('introformat', XMLDB_TYPE_INTEGER, '4', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'intro'); - - /// Launch add field introformat - $dbman->add_field($table, $field); - - /// wiki savepoint reached - upgrade_mod_savepoint($result, 2009042001, 'wiki'); + echo $OUTPUT->notification('Renaming old wiki module tables', 'notifysuccess'); + foreach ($tables as $tablename) { + $table = new xmldb_table($tablename); + if ($dbman->table_exists($table)) { + if ($dbman->table_exists($table)) { + $dbman->rename_table($table, $tablename . '_old'); + } + } + } + upgrade_mod_savepoint($result, 2010040101, 'wiki'); } -/// Dropping all enums/check contraints from core. MDL-18577 - if ($result && $oldversion < 2009042700) { + // Step 2: Creating new tables + if ($result && $oldversion < 2010040102) { + require_once(dirname(__FILE__) . '/upgradelib.php'); + echo $OUTPUT->notification('Installing new wiki module tables', 'notifysuccess'); + wiki_upgrade_install_20_tables(); + upgrade_mod_savepoint($result, 2010040102, 'wiki'); + } - /// Changing list of values (enum) of field wtype on table wiki to none + // Step 3: migrating wiki instances + if ($result && $oldversion < 2010040103) { + upgrade_set_timeout(); + + // Setting up wiki configuration + $sql = 'UPDATE {wiki} w ' . + 'SET w.intro = w.summary, ' . + 'w.firstpagetitle = w.pagename, ' . + 'w.defaultformat = "html"'; + + $DB->execute($sql); + + $sql = 'UPDATE {wiki} w ' . + 'SET w.wikimode = "collaborative" ' . + 'WHERE w.wtype = "group"'; + $DB->execute($sql); + + $sql = 'UPDATE {wiki} w ' . + 'SET w.wikimode = "individual" ' . + 'WHERE w.wtype != "group"'; + $DB->execute($sql); + + // Removing edit & create capability to students in old teacher wikis + $studentroles = $DB->get_records('role', array('archetype' => 'student')); + $wikis = $DB->get_records('wiki'); + foreach ($wikis as $wiki) { + echo $OUTPUT->notification('Migrating '.$wiki->wtype.' type wiki instance: '.$wiki->name, 'notifysuccess'); + if ($wiki->wtype == 'teacher') { + $cm = get_coursemodule_from_instance('wiki', $wiki->id); + $context = get_context_instance(CONTEXT_MODULE, $cm->id); + foreach ($studentroles as $studentrole) { + role_change_permission($studentrole->id, $context, 'mod/wiki:editpage', CAP_PROHIBIT); + role_change_permission($studentrole->id, $context, 'mod/wiki:createpage', CAP_PROHIBIT); + } + } + } + + echo $OUTPUT->notification('Migrating old wikis to new wikis', 'notifysuccess'); + upgrade_mod_savepoint($result, 2010040103, 'wiki'); + } + + // Step 4: migrating wiki entries to new subwikis + if ($result && $oldversion < 2010040104) { + /** + * Migrating wiki entries to new subwikis + */ + $sql = 'INSERT into {wiki_subwikis} (wikiid, groupid, userid) ' . + 'SELECT e.wikiid, e.groupid, e.userid ' . + 'FROM {wiki_entries_old} e '; + echo $OUTPUT->notification('Migrating old entries to new subwikis', 'notifysuccess'); + + $DB->execute($sql, array()); + + upgrade_mod_savepoint($result, 2010040104, 'wiki'); + } + + // Step 5: Migrating pages + if ($result && $oldversion < 2010040105) { + /** + * Filling pages table with latest versions of every page. + * + * @TODO: Ensure that ALL versions of every page are always in database and + * they can be removed or cleaned. + * That fact could let us rewrite the subselect to execute a count(*) to avoid + * the order by and it would be much faster. + */ + + $sql = 'INSERT into {wiki_pages} (subwikiid, title, cachedcontent, timecreated, timemodified, userid, pageviews) ' . + 'SELECT s.id, p.pagename, "**reparse needed**", p.created, p.lastmodified, p.userid, p.hits ' . + 'FROM {wiki_pages_old} p '. + 'LEFT OUTER JOIN {wiki_entries_old} e ON e.id = p.wiki ' . + 'LEFT OUTER JOIN {wiki_subwikis} s ' . + 'ON s.wikiid = e.wikiid AND s.groupid = e.groupid AND s.userid = e.userid ' . + 'WHERE p.version = (' . + ' SELECT po.version ' . + ' FROM {wiki_pages_old} po ' . + ' WHERE p.pagename = po.pagename and ' . + ' p.wiki = po.wiki ' . + ' ORDER BY p.version DESC ' . + ' LIMIT 1)'; + echo $OUTPUT->notification('Migrating old pages to new pages', 'notifysuccess'); + + $DB->execute($sql, array()); + + upgrade_mod_savepoint($result, 2010040105, 'wiki'); + } + + // Step 6: Migrating versions + if ($result && $oldversion < 2010040106) { + require_once(dirname(__FILE__) . '/upgradelib.php'); + echo $OUTPUT->notification('Migrating old history to new history', 'notifysuccess'); + wiki_upgrade_migrate_versions(); + upgrade_mod_savepoint($result, 2010040106, 'wiki'); + } + + // Step 7: refresh cachedcontent and fill wiki links table + if ($result && $oldversion < 2010040107) { + require_once($CFG->dirroot. '/mod/wiki/locallib.php'); + upgrade_set_timeout(); + + $pages = $DB->get_recordset('wiki_pages'); + + while ($pages->valid()) { + $page = $pages->current(); + wiki_refresh_cachedcontent($page); + $pages->next(); + } + + $pages->close(); + + echo $OUTPUT->notification('Caching content', 'notifysuccess'); + upgrade_mod_savepoint($result, 2010040107, 'wiki'); + } + // Step 8, migrating files + if ($result && $oldversion < 2010040108) { + $fs = get_file_storage(); + $sql = "SELECT DISTINCT po.pagename, w.id AS wikiid, po.userid, + po.meta AS filemeta, eo.id AS entryid, eo.groupid, s.id AS subwiki, + w.course AS courseid, cm.id AS cmid + FROM {wiki_pages_old} po + LEFT OUTER JOIN {wiki_entries_old} eo + ON eo.id=po.wiki + LEFT OUTER JOIN {wiki} w + ON w.id = eo.wikiid + LEFT OUTER JOIN {wiki_subwikis} s + ON s.groupid = eo.groupid AND s.wikiid = eo.wikiid AND eo.userid = s.userid + JOIN {modules} m ON m.name = 'wiki' + JOIN {course_modules} cm ON (cm.module = m.id AND cm.instance = w.id) + "; + + $rs = $DB->get_recordset_sql($sql); + foreach ($rs as $r) { + if (strpos($r->pagename, 'internal://') !== false) { + // Found a file resource! + $pattern = 'internal://'; + $matches = array(); + $filename = str_replace($pattern, '', $r->pagename); + $orgifilename = $filename = clean_param($filename, PARAM_FILE); + $context = get_context_instance(CONTEXT_MODULE, $r->cmid); + $filemeta = unserialize($r->filemeta); + $filesection = $filemeta['section']; + // When attach a file to wiki page, user can customize the file name instead of original file name + // if user did, old wiki will create two pages, internal://original_pagename and internal://renamed_pagename + // internal://original_pagename record has renamed pagename in meta field + // but all file have this field + // old wiki will rename file names to filter space and special character + if (!empty($filemeta['Content-Location'])) { + $orgifilename = urldecode($filemeta['Content-Location']); + $orgifilename = str_replace(' ', '_', $orgifilename); + } + $thefile = $CFG->dataroot . '/' . $r->courseid . '/moddata/wiki/' . $r->wikiid .'/' . $r->entryid . '/'. $filesection .'/'. $filename; + + if (is_readable($thefile)) { + $filerecord = array('contextid' => $context->id, + 'filearea' => 'wiki_attachments', + 'itemid' => $r->subwiki, + 'filepath' => '/', + 'filename' => $orgifilename, + 'userid' => $r->userid); + if (!$fs->file_exists($context->id, 'wiki_attachments', $r->subwiki, '/', $orgifilename)) { + echo $OUTPUT->notification('Migrating file '.$orgifilename, 'notifysuccess'); + $storedfile = $fs->create_file_from_pathname($filerecord, $thefile); + } + // we have to create another file here to make sure interlinks work + if (!$fs->file_exists($context->id, 'wiki_attachments', $r->subwiki, '/', $filename)) { + $filerecord['filename'] = $filename; + echo $OUTPUT->notification('Migrating file '.$filename, 'notifysuccess'); + $storedfile = $fs->create_file_from_pathname($filerecord, $thefile); + } + } + } + } + $rs->close(); + upgrade_mod_savepoint($result, 2010040108, 'wiki'); + } + + // Step 9: clean wiki table + if ($result && $oldversion < 2010040109) { + $fields = array('summary', 'pagename', 'wtype', 'ewikiprinttitle', 'htmlmode', 'ewikiacceptbinary', 'disablecamelcase', 'setpageflags', 'strippages', 'removepages', 'revertchanges', 'initialcontent'); $table = new xmldb_table('wiki'); - $field = new xmldb_field('wtype', XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, 'group', 'pagename'); + foreach ($fields as $fieldname) { + $field = new xmldb_field($fieldname); + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } - /// Launch change of list of values for field wtype - $dbman->drop_enum_from_field($table, $field); + } + echo $OUTPUT->notification('Cleaning wiki table', 'notifysuccess'); + upgrade_mod_savepoint($result, 2010040109, 'wiki'); + } - /// wiki savepoint reached - upgrade_mod_savepoint($result, 2009042700, 'wiki'); + // TODO: Will hold the old tables so we will have chance to fix problems + // Will remove old tables once migrating 100% stable + // Step 10: delete old tables + if ($result && $oldversion < 2010040120) { + //$tables = array('wiki_pages', 'wiki_locks', 'wiki_entries'); + + //foreach ($tables as $tablename) { + //$table = new xmldb_table($tablename . '_old'); + //if ($dbman->table_exists($table)) { + //$dbman->drop_table($table); + //} + //} + //echo $OUTPUT->notification('Droping old tables', 'notifysuccess'); + //upgrade_mod_savepoint($result, 2010040110, 'wiki'); } return $result; } - - diff --git a/mod/wiki/db/upgradelib.php b/mod/wiki/db/upgradelib.php new file mode 100644 index 00000000000..40f667af67b --- /dev/null +++ b/mod/wiki/db/upgradelib.php @@ -0,0 +1,303 @@ +. + +/** + * @package mod-wiki + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +function wiki_add_wiki_fields() { + global $DB; + + upgrade_set_timeout(); + $dbman = $DB->get_manager(); + /// Define table wiki to be created + $table = new xmldb_table('wiki'); + + // Adding fields to wiki table + $wikitable = new xmldb_table('wiki'); + + // in MOODLE_20_SABLE branch, summary field is renamed as intro + // so we renamed it back to summary to keep upgrade going as moodle 1.9 + $field = new xmldb_field('intro', XMLDB_TYPE_TEXT, 'medium', null, null, null, null, null); + if ($dbman->field_exists($wikitable, $field)) { + $dbman->rename_field($wikitable, $field, 'summary'); + } + $dbman->add_field($wikitable, $field); + + $field = new xmldb_field('introformat', XMLDB_TYPE_INTEGER, '4', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', null); + if (!$dbman->field_exists($wikitable, $field)) { + $dbman->add_field($wikitable, $field); + } + + $field = new xmldb_field('firstpagetitle', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, 'First Page', null); + $dbman->add_field($wikitable, $field); + + $field = new xmldb_field('wikimode', XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, 'collaborative', null); + $dbman->add_field($wikitable, $field); + + $field = new xmldb_field('defaultformat', XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, 'creole', null); + $dbman->add_field($wikitable, $field); + + $field = new xmldb_field('forceformat', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '1', null); + $dbman->add_field($wikitable, $field); + + $field = new xmldb_field('scaleid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', null); + $dbman->add_field($wikitable, $field); + + $field = new xmldb_field('editbegin', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', null); + $dbman->add_field($wikitable, $field); + + $field = new xmldb_field('editend', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, '0', null); + $dbman->add_field($wikitable, $field); + +} + +/** + * Install wiki 2.0 tables + */ +function wiki_upgrade_install_20_tables() { + global $DB; + upgrade_set_timeout(); + $dbman = $DB->get_manager(); + + /// Define table wiki_subwikis to be created + $table = new xmldb_table('wiki_subwikis'); + + /// Adding fields to table wiki_subwikis + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('wikiid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('groupid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + + /// Adding keys to table wiki_subwikis + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('wikiidgroupiduserid', XMLDB_KEY_UNIQUE, array('wikiid', 'groupid', 'userid')); + $table->add_key('wikifk', XMLDB_KEY_FOREIGN, array('wikiid'), 'wiki', array('id')); + + /// Conditionally launch create table for wiki_subwikis + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + /// Define table wiki_pages to be created + $table = new xmldb_table('wiki_pages'); + + /// Adding fields to table wiki_pages + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('subwikiid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('title', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, 'title'); + $table->add_field('cachedcontent', XMLDB_TYPE_TEXT, 'medium', null, XMLDB_NOTNULL, null, null); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('timerendered', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('pageviews', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('readonly', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + + /// Adding keys to table wiki_pages + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('subwikititleuser', XMLDB_KEY_UNIQUE, array('subwikiid', 'title', 'userid')); + $table->add_key('subwikifk', XMLDB_KEY_FOREIGN, array('subwikiid'), 'wiki_subwiki', array('id')); + + /// Conditionally launch create table for wiki_pages + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + /// Define table wiki_versions to be created + $table = new xmldb_table('wiki_versions'); + + /// Adding fields to table wiki_versions + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('pageid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('content', XMLDB_TYPE_TEXT, 'medium', null, XMLDB_NOTNULL, null, null); + $table->add_field('contentformat', XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, 'creole'); + $table->add_field('version', XMLDB_TYPE_INTEGER, '5', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + + /// Adding keys to table wiki_versions + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('pagefk', XMLDB_KEY_FOREIGN, array('pageid'), 'wiki_pages', array('id')); + + /// Conditionally launch create table for wiki_versions + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + /// Define table wiki_synonyms to be created + $table = new xmldb_table('wiki_synonyms'); + + /// Adding fields to table wiki_synonyms + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('subwikiid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('pageid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('pagesynonym', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, 'Pagesynonym'); + + /// Adding keys to table wiki_synonyms + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('pageidsyn', XMLDB_KEY_UNIQUE, array('pageid', 'pagesynonym')); + + /// Conditionally launch create table for wiki_synonyms + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + /// Define table wiki_links to be created + $table = new xmldb_table('wiki_links'); + + /// Adding fields to table wiki_links + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('subwikiid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('frompageid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('topageid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('tomissingpage', XMLDB_TYPE_CHAR, '255', null, null, null, null); + + /// Adding keys to table wiki_links + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('frompageidfk', XMLDB_KEY_FOREIGN, array('frompageid'), 'wiki_pages', array('id')); + $table->add_key('subwikifk', XMLDB_KEY_FOREIGN, array('subwikiid'), 'wiki_subwiki', array('id')); + + /// Conditionally launch create table for wiki_links + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + /// Define table wiki_locks to be created + $table = new xmldb_table('wiki_locks'); + + /// Adding fields to table wiki_locks + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('pageid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('sectionname', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('lockedat', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + + /// Adding keys to table wiki_locks + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + + /// Conditionally launch create table for wiki_locks + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } +} + +/** + * Migrating wiki pages history + */ +function wiki_upgrade_migrate_versions() { + global $DB, $CFG; + upgrade_set_timeout(); + require_once($CFG->dirroot . '/mod/wiki/db/migration/lib.php'); + $sql = 'SELECT po.id as oldpage_id, po.pagename as oldpage_pagename, po.version, po.flags, po.content, po.author, po.userid as oldpage_userid, po.created, po.lastmodified, po.refs, po.meta, po.hits, po.wiki, ' . + 'p.id as newpage_id, p.subwikiid, p.title, p.cachedcontent, p.timecreated, p.timemodified as newpage_timemodified, p.timerendered, p.userid as newpage_userid, p.pageviews, p.readonly, ' . + 'e.id as entry_id, e.wikiid, e.course as entrycourse, e.groupid, e.userid as entry_userid, e.pagename as entry_pagename, e.timemodified as entry_timemodified, ' . + 'w.id as wiki_id, w.course as wiki_course, w.name, w.summary as summary, w.pagename as wiki_pagename, w.wtype, w.ewikiprinttitle, w.htmlmode, w.ewikiacceptbinary, w.disablecamelcase, w.setpageflags, w.strippages, w.removepages, w.revertchanges, w.initialcontent, w.timemodified as wiki_timemodified ' . + 'FROM {wiki_pages_old} po LEFT OUTER JOIN {wiki_entries_old} e ' . + 'ON e.id = po.wiki ' . + 'LEFT OUTER JOIN {wiki} w ' . + 'ON w.id = e.wikiid ' . + 'LEFT OUTER JOIN {wiki_subwikis} s ' . + 'ON e.groupid = s.groupid AND e.wikiid = s.wikiid AND e.userid = s.userid ' . + 'LEFT OUTER JOIN {wiki_pages} p ' . + 'ON po.pagename = p.title AND p.subwikiid = s.id'; + $pagesinfo = $DB->get_recordset_sql($sql, array()); + + while ($pagesinfo->valid()) { + $pageinfo = $pagesinfo->current(); + + $oldpage = new StdClass(); + $oldpage->id = $pageinfo->oldpage_id; + $oldpage->pagename = $pageinfo->oldpage_pagename; + $oldpage->version = $pageinfo->version; + $oldpage->flags = $pageinfo->flags; + $oldpage->content = $pageinfo->content; + $oldpage->author = $pageinfo->author; + $oldpage->userid = $pageinfo->oldpage_userid; + $oldpage->created = $pageinfo->created; + $oldpage->lastmodified = $pageinfo->lastmodified; + $oldpage->refs = $pageinfo->refs; + $oldpage->meta = $pageinfo->meta; + $oldpage->hits = $pageinfo->hits; + $oldpage->wiki = $pageinfo->wiki; + + $page = new StdClass(); + $page->id = $pageinfo->newpage_id; + $page->subwikiid = $pageinfo->subwikiid; + $page->title = $pageinfo->title; + $page->cachedcontent = $pageinfo->cachedcontent; + $page->timecreated = $pageinfo->timecreated; + $page->timemodified = $pageinfo->newpage_timemodified; + $page->timerendered = $pageinfo->timerendered; + $page->userid = $pageinfo->newpage_userid; + $page->pageviews = $pageinfo->pageviews; + $page->readonly = $pageinfo->readonly; + + $entry = new StdClass(); + $entry->id = $pageinfo->entry_id; + $entry->wikiid = $pageinfo->wikiid; + $entry->course = $pageinfo->entrycourse; + $entry->groupid = $pageinfo->groupid; + $entry->userid = $pageinfo->entry_userid; + $entry->pagename = $pageinfo->entry_pagename; + $entry->timemodified = $pageinfo->entry_timemodified; + + $wiki = new StdClass(); + $wiki->id = $pageinfo->wiki_id; + $wiki->course = $pageinfo->wiki_course; + $wiki->name = $pageinfo->name; + $wiki->summary = $pageinfo->summary; + $wiki->pagename = $pageinfo->wiki_pagename; + $wiki->wtype = $pageinfo->wtype; + $wiki->ewikiprinttitle = $pageinfo->ewikiprinttitle; + $wiki->htmlmode = $pageinfo->htmlmode; + $wiki->ewikiacceptbinary = $pageinfo->ewikiacceptbinary; + $wiki->disablecamelcase = $pageinfo->disablecamelcase; + $wiki->setpageflags = $pageinfo->setpageflags; + $wiki->strippages = $pageinfo->strippages; + $wiki->removepages = $pageinfo->removepages; + $wiki->revertchanges = $pageinfo->revertchanges; + $wiki->initialcontent = $pageinfo->initialcontent; + $wiki->timemodified = $pageinfo->wiki_timemodified; + + $version = new StdClass(); + $version->pageid = $page->id; + $version->content = wiki_ewiki_2_html($entry, $oldpage, $wiki); + $version->contentformat = "html"; + $version->version = $oldpage->version; + $version->timecreated = $oldpage->lastmodified; + $version->userid = $oldpage->userid; + if ($version->version == 1) { + // The oldest version of page in moodle 2.0 is 0 which has empty content + // so we need to insert an extra record + $content = $version->content; + $version->version = 0; + $version->content == ''; + $DB->insert_record('wiki_versions', $version); + $version->version = 1; + $version->content == $content; + $DB->insert_record('wiki_versions', $version); + } else { + $DB->insert_record('wiki_versions', $version); + } + + $pagesinfo->next(); + } + + $pagesinfo->close(); +} diff --git a/mod/wiki/diff.php b/mod/wiki/diff.php new file mode 100644 index 00000000000..6e89f3d7def --- /dev/null +++ b/mod/wiki/diff.php @@ -0,0 +1,83 @@ +. + +/** + * This file contains all necessary code to view a diff page + * + * @package mod-wiki-2.0 + * @copyrigth 2009 Marc Alier, Jordi Piguillem marc.alier@upc.edu + * @copyrigth 2009 Universitat Politecnica de Catalunya http://www.upc.edu + * + * @author Jordi Piguillem + * @author Marc Alier + * @author David Jimenez + * @author Josep Arus + * @author Kenneth Riba + * + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once('../../config.php'); + +require_once($CFG->dirroot . '/mod/wiki/lib.php'); +require_once($CFG->dirroot . '/mod/wiki/locallib.php'); +require_once($CFG->dirroot . '/mod/wiki/pagelib.php'); + +require_once($CFG->dirroot . '/mod/wiki/diff/difflib.php'); +require_once($CFG->dirroot . '/mod/wiki/diff/diff_nwiki.php'); + +$pageid = required_param('pageid', PARAM_TEXT); +$compare = required_param('compare', PARAM_INT); +$comparewith = required_param('comparewith', PARAM_INT); + +if (!$page = wiki_get_page($pageid)) { + print_error('incorrectpageid', 'wiki'); +} + +if (!$subwiki = wiki_get_subwiki($page->subwikiid)) { + print_error('incorrectsubwikiid', 'wiki'); +} + +if (!$wiki = wiki_get_wiki($subwiki->wikiid)) { + print_error('incorrectwikiid', 'wiki'); +} + +if (!$cm = get_coursemodule_from_instance('wiki', $wiki->id)) { + print_error('invalidcoursemodule'); +} + +if (!$course = get_course_by_id($cm->course)) { + print_error('coursemisconf'); +} + +if ($compare >= $comparewith) { + print_error("A page version can only be compared with an older version."); +} + +require_course_login($course->id, true, $cm); +add_to_log($course->id, "wiki", "diff", "diff.php?id=$cm->id", "$wiki->id"); + +$wikipage = new page_wiki_diff($wiki, $subwiki, $cm); + +$wikipage->set_page($page); +$wikipage->set_comparison($compare, $comparewith); + +$wikipage->print_header(); + +$wikipage->print_content(); + +$wikipage->print_footer(); diff --git a/mod/wiki/diff/diff_added_begins.gif b/mod/wiki/diff/diff_added_begins.gif new file mode 100644 index 00000000000..a4e4299bcc4 Binary files /dev/null and b/mod/wiki/diff/diff_added_begins.gif differ diff --git a/mod/wiki/diff/diff_added_ends.gif b/mod/wiki/diff/diff_added_ends.gif new file mode 100644 index 00000000000..a696fedf893 Binary files /dev/null and b/mod/wiki/diff/diff_added_ends.gif differ diff --git a/mod/wiki/diff/diff_nwiki.php b/mod/wiki/diff/diff_nwiki.php new file mode 100644 index 00000000000..3afb0c88736 --- /dev/null +++ b/mod/wiki/diff/diff_nwiki.php @@ -0,0 +1,1093 @@ + +// You may copy this code freely under the conditions of the GPL. +// + +define('USE_ASSERTS_IN_WIKI', function_exists('assert')); + +class _WikiDiffOp { + var $type; + var $orig; + var $closing; + + function reverse() { + trigger_error("pure virtual", E_USER_ERROR); + } + + function norig() { + return $this->orig ? sizeof($this->orig) : 0; + } + + function nclosing() { + return $this->closing ? sizeof($this->closing) : 0; + } +} + +class _WikiDiffOp_Copy extends _WikiDiffOp { + var $type = 'copy'; + + function _WikiDiffOp_Copy ($orig, $closing = false) { + if (!is_array($closing)) + $closing = $orig; + $this->orig = $orig; + $this->closing = $closing; + } + + function reverse() { + return new _WikiDiffOp_Copy($this->closing, $this->orig); + } +} + +class _WikiDiffOp_Delete extends _WikiDiffOp { + var $type = 'delete'; + + function _WikiDiffOp_Delete ($lines) { + $this->orig = $lines; + $this->closing = false; + } + + function reverse() { + return new _WikiDiffOp_Add($this->orig); + } +} + +class _WikiDiffOp_Add extends _WikiDiffOp { + var $type = 'add'; + + function _WikiDiffOp_Add ($lines) { + $this->closing = $lines; + $this->orig = false; + } + + function reverse() { + return new _WikiDiffOp_Delete($this->closing); + } +} + +class _WikiDiffOp_Change extends _WikiDiffOp { + var $type = 'change'; + + function _WikiDiffOp_Change ($orig, $closing) { + $this->orig = $orig; + $this->closing = $closing; + } + + function reverse() { + return new _WikiDiffOp_Change($this->closing, $this->orig); + } +} + + +/** + * Class used internally by Diff to actually compute the diffs. + * + * The algorithm used here is mostly lifted from the perl module + * Algorithm::Diff (version 1.06) by Ned Konz, which is available at: + * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip + * + * More ideas are taken from: + * http://www.ics.uci.edu/~eppstein/161/960229.html + * + * Some ideas are (and a bit of code) are from from analyze.c, from GNU + * diffutils-2.7, which can be found at: + * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz + * + * closingly, some ideas (subdivision by NCHUNKS > 2, and some optimizations) + * are my own. + * + * @author Geoffrey T. Dairiki + * @access private + */ +class _WikiDiffEngine +{ + function diff ($from_lines, $to_lines) { + $n_from = sizeof($from_lines); + $n_to = sizeof($to_lines); + + $this->xchanged = $this->ychanged = array(); + $this->xv = $this->yv = array(); + $this->xind = $this->yind = array(); + unset($this->seq); + unset($this->in_seq); + unset($this->lcs); + + // Skip leading common lines. + for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { + if ($from_lines[$skip] != $to_lines[$skip]) + break; + $this->xchanged[$skip] = $this->ychanged[$skip] = false; + } + // Skip trailing common lines. + $xi = $n_from; $yi = $n_to; + for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { + if ($from_lines[$xi] != $to_lines[$yi]) + break; + $this->xchanged[$xi] = $this->ychanged[$yi] = false; + } + + // Ignore lines which do not exist in both files. + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) + $xhash[$from_lines[$xi]] = 1; + for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { + $line = $to_lines[$yi]; + if ( ($this->ychanged[$yi] = empty($xhash[$line])) ) + continue; + $yhash[$line] = 1; + $this->yv[] = $line; + $this->yind[] = $yi; + } + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { + $line = $from_lines[$xi]; + if ( ($this->xchanged[$xi] = empty($yhash[$line])) ) + continue; + $this->xv[] = $line; + $this->xind[] = $xi; + } + + // Find the LCS. + $this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv)); + + // Merge edits when possible + $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged); + $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged); + + // Compute the edit operations. + $edits = array(); + $xi = $yi = 0; + while ($xi < $n_from || $yi < $n_to) { + USE_ASSERTS_IN_WIKI && assert($yi < $n_to || $this->xchanged[$xi]); + USE_ASSERTS_IN_WIKI && assert($xi < $n_from || $this->ychanged[$yi]); + + // Skip matching "snake". + $copy = array(); + while ( $xi < $n_from && $yi < $n_to + && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { + $copy[] = $from_lines[$xi++]; + ++$yi; + } + if ($copy) + $edits[] = new _WikiDiffOp_Copy($copy); + + // Find deletes & adds. + $delete = array(); + while ($xi < $n_from && $this->xchanged[$xi]) + $delete[] = $from_lines[$xi++]; + + $add = array(); + while ($yi < $n_to && $this->ychanged[$yi]) + $add[] = $to_lines[$yi++]; + + if ($delete && $add) + $edits[] = new _WikiDiffOp_Change($delete, $add); + elseif ($delete) + $edits[] = new _WikiDiffOp_Delete($delete); + elseif ($add) + $edits[] = new _WikiDiffOp_Add($add); + } + return $edits; + } + + + /* Divide the Largest Common Subsequence (LCS) of the sequences + * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally + * sized segments. + * + * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an + * array of NCHUNKS+1 (X, Y) indexes giving the diving points between + * sub sequences. The first sub-sequence is contained in [X0, X1), + * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note + * that (X0, Y0) == (XOFF, YOFF) and + * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). + * + * This function assumes that the first lines of the specified portions + * of the two files do not match, and likewise that the last lines do not + * match. The caller must trim matching lines from the beginning and end + * of the portions it is going to specify. + */ + function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) { + $flip = false; + + if ($xlim - $xoff > $ylim - $yoff) { + // Things seems faster (I'm not sure I understand why) + // when the shortest sequence in X. + $flip = true; + list ($xoff, $xlim, $yoff, $ylim) + = array( $yoff, $ylim, $xoff, $xlim); + } + + if ($flip) + for ($i = $ylim - 1; $i >= $yoff; $i--) + $ymatches[$this->xv[$i]][] = $i; + else + for ($i = $ylim - 1; $i >= $yoff; $i--) + $ymatches[$this->yv[$i]][] = $i; + + $this->lcs = 0; + $this->seq[0]= $yoff - 1; + $this->in_seq = array(); + $ymids[0] = array(); + + $numer = $xlim - $xoff + $nchunks - 1; + $x = $xoff; + for ($chunk = 0; $chunk < $nchunks; $chunk++) { + if ($chunk > 0) + for ($i = 0; $i <= $this->lcs; $i++) + $ymids[$i][$chunk-1] = $this->seq[$i]; + + $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks); + for ( ; $x < $x1; $x++) { + $line = $flip ? $this->yv[$x] : $this->xv[$x]; + if (empty($ymatches[$line])) + continue; + $matches = $ymatches[$line]; + reset($matches); + while (list ($junk, $y) = each($matches)) + if (empty($this->in_seq[$y])) { + $k = $this->_lcs_pos($y); + USE_ASSERTS_IN_WIKI && assert($k > 0); + $ymids[$k] = $ymids[$k-1]; + break; + } + while (list ($junk, $y) = each($matches)) { + if ($y > $this->seq[$k-1]) { + USE_ASSERTS_IN_WIKI && assert($y < $this->seq[$k]); + // Optimization: this is a common case: + // next match is just replacing previous match. + $this->in_seq[$this->seq[$k]] = false; + $this->seq[$k] = $y; + $this->in_seq[$y] = 1; + } + else if (empty($this->in_seq[$y])) { + $k = $this->_lcs_pos($y); + USE_ASSERTS_IN_WIKI && assert($k > 0); + $ymids[$k] = $ymids[$k-1]; + } + } + } + } + + $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); + $ymid = $ymids[$this->lcs]; + for ($n = 0; $n < $nchunks - 1; $n++) { + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); + $y1 = $ymid[$n] + 1; + $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); + } + $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); + + return array($this->lcs, $seps); + } + + function _lcs_pos ($ypos) { + $end = $this->lcs; + if ($end == 0 || $ypos > $this->seq[$end]) { + $this->seq[++$this->lcs] = $ypos; + $this->in_seq[$ypos] = 1; + return $this->lcs; + } + + $beg = 1; + while ($beg < $end) { + $mid = (int)(($beg + $end) / 2); + if ( $ypos > $this->seq[$mid] ) + $beg = $mid + 1; + else + $end = $mid; + } + + USE_ASSERTS_IN_WIKI && assert($ypos != $this->seq[$end]); + + $this->in_seq[$this->seq[$end]] = false; + $this->seq[$end] = $ypos; + $this->in_seq[$ypos] = 1; + return $end; + } + + /* Find LCS of two sequences. + * + * The results are recorded in the vectors $this->{x,y}changed[], by + * storing a 1 in the element for each line that is an insertion + * or deletion (ie. is not in the LCS). + * + * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. + * + * Note that XLIM, YLIM are exclusive bounds. + * All line numbers are origin-0 and discarded lines are not counted. + */ + function _compareseq ($xoff, $xlim, $yoff, $ylim) { + // Slide down the bottom initial diagonal. + while ($xoff < $xlim && $yoff < $ylim + && $this->xv[$xoff] == $this->yv[$yoff]) { + ++$xoff; + ++$yoff; + } + + // Slide up the top initial diagonal. + while ($xlim > $xoff && $ylim > $yoff + && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { + --$xlim; + --$ylim; + } + + if ($xoff == $xlim || $yoff == $ylim) + $lcs = 0; + else { + // This is ad hoc but seems to work well. + //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); + //$nchunks = max(2,min(8,(int)$nchunks)); + $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; + list ($lcs, $seps) + = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks); + } + + if ($lcs == 0) { + // X and Y sequences have no common subsequence: + // mark all changed. + while ($yoff < $ylim) + $this->ychanged[$this->yind[$yoff++]] = 1; + while ($xoff < $xlim) + $this->xchanged[$this->xind[$xoff++]] = 1; + } + else { + // Use the partitions to split this problem into subproblems. + reset($seps); + $pt1 = $seps[0]; + while ($pt2 = next($seps)) { + $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); + $pt1 = $pt2; + } + } + } + + /* Adjust inserts/deletes of identical lines to join changes + * as much as possible. + * + * We do something when a run of changed lines include a + * line at one end and has an excluded, identical line at the other. + * We are free to choose which identical line is included. + * `compareseq' usually chooses the one at the beginning, + * but usually it is cleaner to consider the following identical line + * to be the "change". + * + * This is extracted verbatim from analyze.c (GNU diffutils-2.7). + */ + function _shift_boundaries ($lines, &$changed, $other_changed) { + $i = 0; + $j = 0; + + USE_ASSERTS_IN_WIKI && assert('sizeof($lines) == sizeof($changed)'); + $len = sizeof($lines); + $other_len = sizeof($other_changed); + + while (1) { + /* + * Scan forwards to find beginning of another run of changes. + * Also keep track of the corresponding point in the other file. + * + * Throughout this code, $i and $j are adjusted together so that + * the first $i elements of $changed and the first $j elements + * of $other_changed both contain the same number of zeros + * (unchanged lines). + * Furthermore, $j is always kept so that $j == $other_len or + * $other_changed[$j] == false. + */ + while ($j < $other_len && $other_changed[$j]) + $j++; + + while ($i < $len && ! $changed[$i]) { + USE_ASSERTS_IN_WIKI && assert('$j < $other_len && ! $other_changed[$j]'); + $i++; $j++; + while ($j < $other_len && $other_changed[$j]) + $j++; + } + + if ($i == $len) + break; + + $start = $i; + + // Find the end of this run of changes. + while (++$i < $len && $changed[$i]) + continue; + + do { + /* + * Record the length of this run of changes, so that + * we can later determine whether the run has grown. + */ + $runlength = $i - $start; + + /* + * Move the changed region back, so long as the + * previous unchanged line matches the last changed one. + * This merges with previous changed regions. + */ + while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { + $changed[--$start] = 1; + $changed[--$i] = false; + while ($start > 0 && $changed[$start - 1]) + $start--; + USE_ASSERTS_IN_WIKI && assert('$j > 0'); + while ($other_changed[--$j]) + continue; + USE_ASSERTS_IN_WIKI && assert('$j >= 0 && !$other_changed[$j]'); + } + + /* + * Set CORRESPONDING to the end of the changed run, at the last + * point where it corresponds to a changed run in the other file. + * CORRESPONDING == LEN means no such point has been found. + */ + $corresponding = $j < $other_len ? $i : $len; + + /* + * Move the changed region forward, so long as the + * first changed line matches the following unchanged one. + * This merges with following changed regions. + * Do this second, so that if there are no merges, + * the changed region is moved forward as far as possible. + */ + while ($i < $len && $lines[$start] == $lines[$i]) { + $changed[$start++] = false; + $changed[$i++] = 1; + while ($i < $len && $changed[$i]) + $i++; + + USE_ASSERTS_IN_WIKI && assert('$j < $other_len && ! $other_changed[$j]'); + $j++; + if ($j < $other_len && $other_changed[$j]) { + $corresponding = $i; + while ($j < $other_len && $other_changed[$j]) + $j++; + } + } + } while ($runlength != $i - $start); + + /* + * If possible, move the fully-merged run of changes + * back to a corresponding run in the other file. + */ + while ($corresponding < $i) { + $changed[--$start] = 1; + $changed[--$i] = 0; + USE_ASSERTS_IN_WIKI && assert('$j > 0'); + while ($other_changed[--$j]) + continue; + USE_ASSERTS_IN_WIKI && assert('$j >= 0 && !$other_changed[$j]'); + } + } + } +} + +/** + * Class representing a 'diff' between two sequences of strings. + */ +class WikiDiff +{ + var $edits; + + /** + * Constructor. + * Computes diff between sequences of strings. + * + * @param $from_lines array An array of strings. + * (Typically these are lines from a file.) + * @param $to_lines array An array of strings. + */ + function WikiDiff($from_lines, $to_lines) { + $eng = new _WikiDiffEngine; + $this->edits = $eng->diff($from_lines, $to_lines); + //$this->_check($from_lines, $to_lines); + } + + /** + * Compute reversed WikiDiff. + * + * SYNOPSIS: + * + * $diff = new WikiDiff($lines1, $lines2); + * $rev = $diff->reverse(); + * @return object A WikiDiff object representing the inverse of the + * original diff. + */ + function reverse () { + $rev = $this; + $rev->edits = array(); + foreach ($this->edits as $edit) { + $rev->edits[] = $edit->reverse(); + } + return $rev; + } + + /** + * Check for empty diff. + * + * @return bool True iff two sequences were identical. + */ + function isEmpty () { + foreach ($this->edits as $edit) { + if ($edit->type != 'copy') + return false; + } + return true; + } + + /** + * Compute the length of the Longest Common Subsequence (LCS). + * + * This is mostly for diagnostic purposed. + * + * @return int The length of the LCS. + */ + function lcs () { + $lcs = 0; + foreach ($this->edits as $edit) { + if ($edit->type == 'copy') + $lcs += sizeof($edit->orig); + } + return $lcs; + } + + /** + * Get the original set of lines. + * + * This reconstructs the $from_lines parameter passed to the + * constructor. + * + * @return array The original sequence of strings. + */ + function orig() { + $lines = array(); + + foreach ($this->edits as $edit) { + if ($edit->orig) + array_splice($lines, sizeof($lines), 0, $edit->orig); + } + return $lines; + } + + /** + * Get the closing set of lines. + * + * This reconstructs the $to_lines parameter passed to the + * constructor. + * + * @return array The sequence of strings. + */ + function closing() { + $lines = array(); + + foreach ($this->edits as $edit) { + if ($edit->closing) + array_splice($lines, sizeof($lines), 0, $edit->closing); + } + return $lines; + } + + /** + * Check a WikiDiff for validity. + * + * This is here only for debugging purposes. + */ + function _check ($from_lines, $to_lines) { + if (serialize($from_lines) != serialize($this->orig())) + trigger_error("Reconstructed original doesn't match", E_USER_ERROR); + if (serialize($to_lines) != serialize($this->closing())) + trigger_error("Reconstructed closing doesn't match", E_USER_ERROR); + + $rev = $this->reverse(); + if (serialize($to_lines) != serialize($rev->orig())) + trigger_error("Reversed original doesn't match", E_USER_ERROR); + if (serialize($from_lines) != serialize($rev->closing())) + trigger_error("Reversed closing doesn't match", E_USER_ERROR); + + + $prevtype = 'none'; + foreach ($this->edits as $edit) { + if ( $prevtype == $edit->type ) + trigger_error("Edit sequence is non-optimal", E_USER_ERROR); + $prevtype = $edit->type; + } + + $lcs = $this->lcs(); + trigger_error("WikiDiff okay: LCS = $lcs", E_USER_NOTICE); + } +} + +/** + * FIXME: bad name. + */ + +class MappedWikiDiff +extends WikiDiff +{ + /** + * Constructor. + * + * Computes diff between sequences of strings. + * + * This can be used to compute things like + * case-insensitve diffs, or diffs which ignore + * changes in white-space. + * + * @param $from_lines array An array of strings. + * (Typically these are lines from a file.) + * + * @param $to_lines array An array of strings. + * + * @param $mapped_from_lines array This array should + * have the same size number of elements as $from_lines. + * The elements in $mapped_from_lines and + * $mapped_to_lines are what is actually compared + * when computing the diff. + * + * @param $mapped_to_lines array This array should + * have the same number of elements as $to_lines. + */ + function MappedWikiDiff($from_lines, $to_lines, + $mapped_from_lines, $mapped_to_lines) { + + assert(sizeof($from_lines) == sizeof($mapped_from_lines)); + assert(sizeof($to_lines) == sizeof($mapped_to_lines)); + + $this->WikiDiff($mapped_from_lines, $mapped_to_lines); + + $xi = $yi = 0; + for ($i = 0; $i < sizeof($this->edits); $i++) { + $orig = &$this->edits[$i]->orig; + if (is_array($orig)) { + $orig = array_slice($from_lines, $xi, sizeof($orig)); + $xi += sizeof($orig); + } + + $closing = &$this->edits[$i]->closing; + if (is_array($closing)) { + $closing = array_slice($to_lines, $yi, sizeof($closing)); + $yi += sizeof($closing); + } + } + } +} + +/** + * A class to format WikiDiffs + * + * This class formats the diff in classic diff format. + * It is intended that this class be customized via inheritance, + * to obtain fancier outputs. + */ +class WikiDiffFormatter +{ + /** + * Number of leading context "lines" to preserve. + * + * This should be left at zero for this class, but subclasses + * may want to set this to other values. + */ + var $leading_context_lines = 0; + + /** + * Number of trailing context "lines" to preserve. + * + * This should be left at zero for this class, but subclasses + * may want to set this to other values. + */ + var $trailing_context_lines = 0; + + /** + * Format a diff. + * + * @param $diff object A WikiDiff object. + * @return string The formatted output. + */ + function format($diff) { + + $xi = $yi = 1; + $block = false; + $context = array(); + + $nlead = $this->leading_context_lines; + $ntrail = $this->trailing_context_lines; + + $this->_start_diff(); + + foreach ($diff->edits as $edit) { + if ($edit->type == 'copy') { + if (is_array($block)) { + if (sizeof($edit->orig) <= $nlead + $ntrail) { + $block[] = $edit; + } + else{ + if ($ntrail) { + $context = array_slice($edit->orig, 0, $ntrail); + $block[] = new _WikiWikiDiffOp_Copy($context); + } + $this->_block($x0, $ntrail + $xi - $x0, + $y0, $ntrail + $yi - $y0, + $block); + $block = false; + } + } + $context = $edit->orig; + } + else { + if (! is_array($block)) { + $context = array_slice($context, sizeof($context) - $nlead); + $x0 = $xi - sizeof($context); + $y0 = $yi - sizeof($context); + $block = array(); + if ($context) + $block[] = new _WikiWikiDiffOp_Copy($context); + } + $block[] = $edit; + } + + if ($edit->orig) + $xi += sizeof($edit->orig); + if ($edit->closing) + $yi += sizeof($edit->closing); + } + + if (is_array($block)) + $this->_block($x0, $xi - $x0, + $y0, $yi - $y0, + $block); + + return $this->_end_diff(); + } + + function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) { + $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen)); + foreach ($edits as $edit) { + if ($edit->type == 'copy') + $this->_context($edit->orig); + elseif ($edit->type == 'add') + $this->_added($edit->closing); + elseif ($edit->type == 'delete') + $this->_deleted($edit->orig); + elseif ($edit->type == 'change') + $this->_changed($edit->orig, $edit->closing); + else + trigger_error("Unknown edit type", E_USER_ERROR); + } + $this->_end_block(); + } + + function _start_diff() { + ob_start(); + } + + function _end_diff() { + $val = ob_get_contents(); + ob_end_clean(); + return $val; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) { + if ($xlen > 1) + $xbeg .= "," . ($xbeg + $xlen - 1); + if ($ylen > 1) + $ybeg .= "," . ($ybeg + $ylen - 1); + + return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; + } + + function _start_block($header) { + echo $header; + } + + function _end_block() { + } + + function _lines($lines, $prefix = ' ') { + foreach ($lines as $line) + echo "$prefix $line\n"; + } + + function _context($lines) { + $this->_lines($lines); + } + + function _added($lines) { + $this->_lines($lines, ">"); + } + function _deleted($lines) { + $this->_lines($lines, "<"); + } + + function _changed($orig, $closing) { + $this->_deleted($orig); + echo "---\n"; + $this->_added($closing); + } +} + + +/** + * Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3 + * + */ + +define('NBSP', ' '); // iso-8859-x non-breaking space. + +class _WikiHWLDF_WordAccumulator { + function _WikiHWLDF_WordAccumulator () { + $this->_lines = array(); + $this->_line = ''; + $this->_group = ''; + $this->_tag = ''; + } + + function _flushGroup ($new_tag) { + if ($this->_group !== '') { + if ($this->_tag == 'mark') + $this->_line .= ''.$this->_group.''; + else + $this->_line .= $this->_group; + } + $this->_group = ''; + $this->_tag = $new_tag; + } + + function _flushLine ($new_tag) { + $this->_flushGroup($new_tag); + if ($this->_line != '') + $this->_lines[] = $this->_line; + $this->_line = ''; + } + + function addWords ($words, $tag = '') { + if ($tag != $this->_tag) + $this->_flushGroup($tag); + + foreach ($words as $word) { + // new-line should only come as first char of word. + if ($word == '') + continue; + if ($word[0] == "\n") { + $this->_group .= NBSP; + $this->_flushLine($tag); + $word = substr($word, 1); + } + assert(!strstr($word, "\n")); + $this->_group .= $word; + } + } + + function getLines() { + $this->_flushLine('~done'); + return $this->_lines; + } +} + +class WordLevelWikiDiff extends MappedWikiDiff +{ + function WordLevelWikiDiff ($orig_lines, $closing_lines) { + list ($orig_words, $orig_stripped) = $this->_split($orig_lines); + list ($closing_words, $closing_stripped) = $this->_split($closing_lines); + + + $this->MappedWikiDiff($orig_words, $closing_words, + $orig_stripped, $closing_stripped); + } + + function _split($lines) { + // FIXME: fix POSIX char class. +# if (!preg_match_all('/ ( [^\S\n]+ | [[:alnum:]]+ | . ) (?: (?!< \n) [^\S\n])? /xs', + if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs', + implode("\n", $lines), + $m)) { + return array(array(''), array('')); + } + return array($m[0], $m[1]); + } + + function orig () { + $orig = new _WikiHWLDF_WordAccumulator; + + foreach ($this->edits as $edit) { + if ($edit->type == 'copy') + $orig->addWords($edit->orig); + elseif ($edit->orig) + $orig->addWords($edit->orig, 'mark'); + } + return $orig->getLines(); + } + + function closing () { + $closing = new _WikiHWLDF_WordAccumulator; + + foreach ($this->edits as $edit) { + if ($edit->type == 'copy') + $closing->addWords($edit->closing); + elseif ($edit->closing) + $closing->addWords($edit->closing, 'mark'); + } + return $closing->getLines(); + } +} + +/** + * @TODO: Doc this class + */ +class TableWikiDiffFormatter extends WikiDiffFormatter +{ + var $htmltable = array(); + + function TableWikiDiffFormatter() { + $this->leading_context_lines = 2; + $this->trailing_context_lines = 2; + } + + function _block_header( $xbeg, $xlen, $ybeg, $ylen) { + + } + + function _start_block ($header) { + + } + + function _end_block() { + + } + + function _lines($lines, $prefix=' ', $color="white") { + + } + + function _added($lines) { + global $htmltable; + foreach ($lines as $line) { + $htmltable[] = array('','+','
| - - - | - -
-
-
- |