Extension:CCM
From MediaWiki.org
For further details, see Security issues with authorization extensions
|
CCM Release status: experimental |
|
|---|---|
| Implementation | Special page |
| Description | provides a collaborative content management system integrated into mediawiki |
| Author(s) | Artem Kaznatcheev (DFRussia Talk) |
| Version | 0.0.2 (2008-03-25) |
| MediaWiki | various |
| Download | from page |
The goal of the CCM extension, is to provide a secure file sharing and collaboration tool inside of mediawiki. The basic principle is to have a specialpage that has a relatively secure JavaScript interface with the a part of the filesystem the wiki sits on. Certain users will be allowed to access this page, and inside the page they will be deligated into certain groups which limits the files they can access and how they can edit them. Currently the code is incomplete and unstable. I am developing on MediaWiki 1.10 and I do not recomend using it until it goes into beta.
Contents |
[edit] Code
The code consists of 8 main files; CCM.php, CCM_Ajax.php and CCM_body.php are responsible for the server back-end; CCM.js provides the user interface functionality; CCM.css allows customization of the look and feel; dbConfig.sql sets up the needed database structure; and CCM.i18n.php is for future internationalization.
[edit] CCM.php
<?php /* * CCM 0.0.2 - a collaborative content management system for MediaWiki * Copyright (C) 2008 Artem Kaznatcheev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ # Not a valid entry point, skip unless MEDIAWIKI is defined if (!defined('MEDIAWIKI')) {exit( 1 );} ### Bring in Special Page ### //main body that contains the special page $wgAutoloadClasses['CCM'] = dirname(__FILE__) . '/CCM_body.php'; $wgSpecialPages['CCM'] = 'CCM'; $wgHooks['LoadAllMessages'][] = 'CCM::loadMessages'; ### Define and Create CCMUser ### /* hook needed to avoid trying to use database calls before * GlobalFunctions.php is loaded */ $wgHooks['AuthPluginSetup'][] = 'efCCMClassLoad'; function efCCMClassLoad() { global $egCCMUser; $egCCMUser = new CCMUser(); } class CCMUser { public $name; //user name public $id; //user id public $currentGroup; public $groups = array(); public $privelages = array(); function groupVerify($group_id) { foreach ($this->groups as $g) { if ($g == $group_id) { return $g; } } return $this->currentGroup; } function __construct() { global $wgUser, $wgDBprefix; ### Set basics of $egCCMUser ### $this->id = $wgUser->getId(); $this->name = $wgUser->getName(); $dbr =& wfGetDB( DB_SLAVE ); //load the database object $res = $dbr->query(" SELECT ccm_ug_group_id,ccm_ug_privelage FROM ".$wgDBprefix. "ccm_user_groups WHERE ccm_ug_user_id=".$this->id."; "); //SQL query for groups the user belongs to while ($row = $dbr->fetchObject( $res ) ){ //add groups to array of user groups $this->groups[] = $row->ccm_ug_group_id; //create assosiative array of privelages $this->privelages[$row->ccm_ug_group_id] = $row->ccm_ug_privelage; } if ($this->groups){ //set the current (default) group $this->currentGroup = $this->groups[0]; } $dbr->close(); //close the database } } ### Bring in AJAX ### //file that contains AJAX functions require_once(dirname(__FILE__) . '/CCM_Ajax.php'); $wgAjaxExportList[] = 'efCCM_Ajax'; //register AJAX function ?>
[edit] CCM_Ajax.php
<?php /* * CCM 0.0.2 - a collaborative content management system for MediaWiki * Copyright (C) 2008 Artem Kaznatcheev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * This is main AJAX function that is registered with MediaWiki and thus * serves as the entry point for AJAX. All AJAX requests for CCM go through * it */ function efCCM_Ajax($action, $group, $data, $new){ global $egCCMUser; /*this case statement handles all the actions that can be taken from the *JavaScript*/ switch ($action) { case "fetch_files": $response = efCCMFetchFiles($egCCMUser->groupVerify($group)); break; case "fetch_gmem": $response = efCCMFetchGMem($egCCMUser->groupVerify($group)); break; case "fetch_gdis": $response = efCCMFetchGDis($egCCMUser->groupVerify($group)); break; case "fetch_users": $response = efCCMFetchUsers($egCCMUser->groupVerify($group)); break; case "change_group": $response = new AjaxResponse($egCCMUser->groupVerify($group)); break; case "change_priv": $response = efCCMChangePriv($egCCMUser->groupVerify($group), $data, $new); break; case "add_gmem": $response = efCCMAddGMem($egCCMUser->groupVerify($group), $data); break; case "add_gdis": $response = efCCMAddGDis($egCCMUser->groupVerify($group), $egCCMUser->id, $data); break; default: //if action not found, return error $response = new AjaxResponse("<error>no such action</error>"); break; } return $response; } /* * This function returns all the files in the group (to populate the file * list) */ function efCCMFetchFiles( $group ) { if (!$group) { //if not member of group return error and don't do action $response = new AjaxResponse("<error>you are not a member of this group</error>"); return $response; } ###Database Operations### $dbr =& wfGetDB( DB_SLAVE ); //load the database object $res = $dbr->select( "ccm_files", //table name array("ccm_f_file_id", "ccm_f_file_name"), //array of fields 'ccm_f_group_id='.(int)$group, //for this group __METHOD__ ); //request list of files from the ccm_files table /*create array of files*/ while ($row = $dbr->fetchObject( $res ) ) { $files[$row->ccm_f_file_id] = $row->ccm_f_file_name; } $dbr->close(); //close the database ### Response Creation ## $out = '<?xml version="1.0" encoding="ISO-8859-1"?>'; $out .= "<filelist>"; foreach ($files as $f_id => $f_name) { /* FIXME: causes warning when no files are present, fix by initializing * ahead of time */ $out .= "<file id=".$f_id." >"; $out .= $f_name; $out .= "</file>"; } $out .= "</filelist>"; $response = new AjaxResponse($out); return $response; } /* * This function returns the group members of the current group to the * JavaScript */ function efCCMFetchGMem( $group ) { if (!$group) { //if not member of group return error and don't do action $response = new AjaxResponse("<error>you are not a member of this group</error>"); return $response; } global $wgDBprefix, $wgUser; ### Database Operations ### $dbr =& wfGetDB( DB_SLAVE ); //load the database object $res = $dbr->query(" SELECT ccm_ug_user_id,ccm_ug_privelage FROM " .$wgDBprefix."ccm_user_groups WHERE ccm_ug_group_id=".$group ); //query for members of group /*create array of members*/ while ($row = $dbr->fetchObject( $res ) ){ $u_id = $row->ccm_ug_user_id; $u_priv = $row->ccm_ug_privelage; $u = $wgUser->newFromId($u_id); $u_name = $u->getName(); $members[$u_id] = $u_name; $privelages[$u_id] = $u_priv; } $dbr->close(); //close the database ### Response Creation ## $out = '<?xml version="1.0" encoding="ISO-8859-1"?>'; $out .= "<userlist>"; foreach ($members as $u_id => $u_name){ $out .= "<user id='".$u_id."'>"; $out .= "<name>".$u_name."</name>"; $out .= "<privelage>".$privelages[$u_id]."</privelage>"; $out .= "</user>"; } $out .= "</userlist>"; $response = new AjaxResponse(); $response->addText($out); return $response; } /* * This function returns the group discussion for the current group to the * JavaScript */ function efCCMFetchGDis($group) { global $wgDBprefix; if (!$group) { //if not member of group return error and don't do action $response = new AjaxResponse("<error>you are not a member of this group</error>"); return $response; } ### Database Operations ### $dbr =& wfGetDB( DB_SLAVE ); //load the database object $res = $dbr->query(" SELECT ccm_gd_discussion_user_id,ccm_gd_discussion_text FROM " .$wgDBprefix."ccm_group_discussion WHERE ccm_gd_group_id=".$group." ORDER BY ccm_gd_discussion_number "); //query for discussions /*create array of discussion*/ $i = 0; while ($row = $dbr->fetchObject( $res ) ){ $u_id = $row->ccm_gd_discussion_user_id; $text = $row->ccm_gd_discussion_text; $disItems[$i] = Array($u_id, $text); $i += 1; } $dbr->close(); //close the database ### Response Creation ## $out = '<?xml version="1.0" encoding="ISO-8859-1"?>'; $out .= '<disItems>'; foreach ($disItems as $item) { $out .= '<discussion user="'.$item[0].'">'.$item[1].'</discussion>'; } $out .= '</disItems>'; $response = new AjaxResponse(); $response->addText($out); return $response; } /* * This function fetches all the users in the database */ function efCCMFetchUsers($group) { global $egCCMUser; //if not high enough privelage return error and don't do action if ($egCCMUser->privelages[$group] != 3) { $response = new AjaxResponse( "<error>you are not authorized to fetch users</error>" ); return $response; } ### Database Operations ### $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( "user", //table name array( //array of fields "user_id", "user_name", "user_real_name", "user_email" ), '', //no WHERE statements __METHOD__ ); //request user data from the user table if( $dbr->numRows( $res ) > 0 ) { /*create array of users*/ while( $row = $dbr->fetchObject( $res ) ) { $users[$row->user_id] = array( "name" => $row->user_name, "realname" => $row->user_real_name, "email" => $row->user_email ); /* instanstiate a $user_groups for each user, otherwise errors come * up later for users with no groups */ $user_groups[$row->user_id] = array(); } $dbr->freeResult( $res ); } //request user data from the user_groups table $res = $dbr->select('user_groups', '*', '',__METHOD__ ); if( $dbr->numRows( $res ) > 0 ) { /*create array of user_groups and group_users*/ while( $row = $dbr->fetchObject( $res ) ) { $user_groups[$row->ug_user][] = $row->ug_group; $group_users[$row->ug_group][] = $row->ug_user; } $dbr->freeResult( $res ); } $dbr->close(); ### Response Creation ## $out = '<?xml version="1.0" encoding="ISO-8859-1"?>'; $out .= '<ug>'; /*create a list of users*/ $out .= '<userlist>'; foreach ($users as $u_id => $u_fields) { $out .= "<user id='".$u_id."'>"; foreach ($u_fields as $field => $value) { $out .= "<".$field.">".$value."</".$field.">"; } $out .= "<usergroups>"; foreach ($user_groups[$u_id] as $group) { $out .= "<group>".$group."</group>"; } $out .= "</usergroups>"; $out .= "</user>"; } $out .= '</userlist>'; /*create a list of groups*/ $out .= '<grouplist>'; foreach ($group_users as $group => $u_list) { $out .= "<group>"; $out .= "<name>".$group."</name>"; $out .= "<userlist>"; foreach ($u_list as $u_id) { $out .= "<user id='".$u_id."'>".$users[$u_id]["name"]."</user>"; } $out .= "</userlist>"; $out .= "</group>"; } $out .= '</grouplist>'; $out .= '</ug>'; $response = new AjaxResponse($out); return $response; } /* * This function changes the proper users privelage and does verification */ function efCCMChangePriv($group, $user, $newpriv) { global $egCCMUser, $wgDBprefix; if (!$group) { //if not member of group return error and don't do action $response = new AjaxResponse("<error>you are not a member of this group</error>"); return $response; } //if not high enough privelage return error and don't do action if ($egCCMUser->privelages[$group] != 3) { $response = new AjaxResponse( "<error>you are not authorized to change privelages</error>" ); return $response; } //if newpriv is not an int return error and don't do action if (!is_numeric($newpriv)) { $response = new AjaxResponse( "<error>the new privelage specified is not of valid type</error>" ); return $response; } /* * This statement checks if the newpriv is in the proper range. * It allows for a newpriv to be 3, which isn't possible input from * standard JavaScript but should be allowed from else-where. * Mostly this is allowed for code reusability purposes */ //if newpriv is out of range return error and don't do action if (((int)$newpriv < 0) || ((int)$newpriv > 3)) { $response = new AjaxResponse( "<error>the new privelage specified is out of range</error>" ); return $response; } ### Database Operations ### $dbw =& wfGetDB( DB_MASTER ); //load the database object $res = $dbw->query(" SELECT ccm_ug_privelage FROM " .$wgDBprefix."ccm_user_groups WHERE ccm_ug_user_id=".$user." and ccm_ug_group_id=".$group ); //query for privelages of group member //if user being modified is a PI, then do not let user be changed if ($dbw->fetchObject( $res )->ccm_ug_privelage == 3) { $response = new AjaxResponse("3"); $dbw->close(); //close database to avoid a leak return $response; } $res = $dbw->query(" UPDATE ".$wgDBprefix."ccm_user_groups SET ccm_ug_privelage = ".(int)$newpriv." WHERE ccm_ug_user_id=".$user." and ccm_ug_group_id=".$group ); //update the privelage of the specified user $dbw->close(); $response = new AjaxResponse($newpriv); return $response; } /* * This function adds a new user to the group */ function efCCMAddGMem($group, $newUser) { global $egCCMUser, $wgDBprefix; if (!$group) { //if not member of group return error and don't do action $response = new AjaxResponse("<error>you are not a member of this group</error>"); return $response; } //if not high enough privelage return error and don't do action if ($egCCMUser->privelages[$group] != 3) { $response = new AjaxResponse( "<error>you are not authorized to add users</error>" ); return $response; } /*check if user is already in group*/ $dbr =& wfGetDB( DB_SLAVE ); //load the database object $res = $dbr->select( "ccm_user_groups", //table name "ccm_ug_user_id", //field to fetch 'ccm_ug_group_id='.(int)$group, //WHERE group is current group __METHOD__ ); //request user data from the user table while ($row = $dbr->fetchObject($res)) { //check against each user //if user already a member return error and don't do action if ($row->ccm_ug_user_id == $newUser) { //user already exists $response = new AjaxResponse( "<error>this user is already a group member</error>" ); return $response; } } $dbr->close(); //close the database unset($dbr); //destroy dbr /*add user to group*/ $dbw =& wfGetDB( DB_MASTER ); //load the database object $res = $dbw->query(" INSERT INTO ".$wgDBprefix. "ccm_user_groups(ccm_ug_user_id,ccm_ug_group_id) VALUES (".(int)$newUser.",".(int)$group.") "); //insert the specific user-group combo, with default privelages. //could switch DB to MyISAM and try to use INSERT DELAYED for optimization $dbw->close(); $response = new AjaxResponse("yes"); return $response; } /* * This function adds to the group discussion */ function efCCMAddGDis($group, $user, $data) { global $wgDBprefix; ### Database Operations ### $dbw =& wfGetDB( DB_MASTER ); //load the database object /* adds the data to the database. Use mysql_real_escape_string to avoid insertion attacks, as recomeneded by Tbleher*/ $res = $dbw->query(" INSERT INTO ".$wgDBprefix. "ccm_group_discussion ". "(ccm_gd_group_id, ccm_gd_discussion_user_id, ccm_gd_discussion_text) VALUES (".$group.", ".$user.', "'. mysql_real_escape_string('"','\"', $data).'") '); $dbw->close(); $response = new AjaxResponse("yes"); return $response; } ?>
[edit] CCM_body.php
<?php /* * CCM 0.0.2 - a collaborative content management system for MediaWiki * Copyright (C) 2008 Artem Kaznatcheev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ class CCM extends SpecialPage { function CCM() { SpecialPage::SpecialPage("CCM"); self::loadMessages(); } function execute( $par ) { global $wgRequest, $wgOut, $wgUser, $wgDBprefix, $egCCMUser; $egCCMUser = new CCMUser(); $this->setHeaders(); # Get request data from, e.g. $param = $wgRequest->getText('param'); $dbr =& wfGetDB( DB_SLAVE ); //load the database object //if the user belongs to no groups, then there is nothing to display if (!$egCCMUser->groups) { $wgOut->showErrorPage('error','nogroups'); return; } ### FIX ME: Needs to be not confined to only this server ### $wgOut->addScript( '<link rel="stylesheet" type="text/css" href="/testwiki/extensions/CCM/CCM.css" />' ); //add CSS $wgOut->addScript( '<script type="text/javascript" src="/testwiki/extensions/CCM/CCM.js"></script>' ); //add JavaScript /* Expanding on the JavaScript to include all the proper data to * manipulate later */ $output .= ' <script type="text/javascript"> CCMData.name = "'.$egCCMUser->name.'" CCMData.currentGroup = '.$egCCMUser->groups[0].'; CCMData.groups = new Object; '; foreach ($egCCMUser->groups as $g) { /*Creating an object for each group*/ /* create the property in data that stores the object that represents * a given group */ $output .= ' CCMData.groups.g_'.$g.' = new Object;'; $output .= ' CCMData.groups.g_'.$g.'.privelage = '.$egCCMUser->privelages[$g]. ';'; /*Getting the name of each group*/ $res = $dbr->query(" SELECT ccm_g_group_name FROM ".$wgDBprefix."ccm_group WHERE ccm_g_group_id=".$g." "); //database query for the name of each group $row = $dbr->fetchObject( $res ); //create and set the name property $output .= ' CCMData.groups.g_'.$g.'.name = "'.$row->ccm_g_group_name.'";'; //create and set the cached property $output .= ' CCMData.groups.g_'.$g.'.cached = false;'; } $output .= ' </script>'; $dbr->close(); //close the database $wgOut->addHTML($output); //add output to page ### Rendering the page ### $output = ' <div id="CCM-body"> <ul> <li class="floatright" onclick="Files_Click();">Files</li> <li class="floatright" onclick="Group_Click();">Group</li> '; foreach ($egCCMUser->groups as $g) { //create each placeholder tab to be filled in by JavaScript $output .= '<li id="tab_g_'.$g.'" onclick="Change_Group('.$g. ')">Loading...</li>'; } $output .= "</ul>"; $output .= ' <div class="CCM-main" id="tab-files"> <table id="primarytable" style="width:100%;"> <td valign="top" class="CCM-left"> <div class="floatright">Up</div> Files: <div id="CCM-files" class="textdiv"></div> </td><td valign="top" > File Info: <div id="CCM-info" class="textdiv"> Name: <table style="width:100%;"> <tr> <td style="padding:0; margin:0;">Created by:</td> <td style="padding:0;">At:</td> </tr><tr> <td style="padding:0;">Edited by:</td> <td style="padding:0;">At:</td> </tr> </table> Description: <div id="CCM-description" style="border: none;"> File info stuff </div> </div><ul> <li class="floatright">History</li> <li class="floatright">Permission</li> <li>Download</li> <li>Update</li> </ul> </td> </table> <div id="CCM-extra"> Discussion: <div id="CCM-fileDiscussion" class="textdiv"> </div> <div class="floatright">Reply</div> </div> <br /> </div> <div class="CCM-main" id="tab-group"> <table id="primarytable" style="width:100%;"> <td valign="top" id="CCM-memCol" class="CCM-left"> <div id="CCM-addMember" class="floatright" onclick="addMember_Click();" style="display:none;">Add</div> Members: <div id="CCM-members" class="textdiv"> </div> </td><td valign="top" id="CCM-gdis" > Discussion: <div id="CCM-discussion" class="textdiv"> </div> <div class="floatright" id="CCM_gDis_Reply" onclick="Reply_G_Click();">Reply</div> <br /> </td> </table> </div> </div> '; //this string represents the physical structure of the page //add the data to the page $output .= '<script type="text/javascript">renderPage();</script>'; $wgOut->addHTML($output); //add output to page } function loadMessages() { static $messagesLoaded = false; global $wgMessageCache; if ( $messagesLoaded ) return; $messagesLoaded = true; require( dirname( __FILE__ ) . '/CCM.i18n.php' ); foreach ( $allMessages as $lang => $langMessages ) { $wgMessageCache->addMessages( $langMessages, $lang ); } } } ?>
[edit] CCM.js
/* * CCM 0.0.2 - a collaborative content management system for MediaWiki * Copyright (C) 2008 Artem Kaznatcheev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** * Unobtrusive event handeling from Peter-Paul Kock available at: * http://www.quirksmode.org/js/eventSimple.html; Function for adding and * removing events available. Used by the dragDrop code mostly */ function addEventSimple(obj,evt,fn) { if (obj.addEventListener) obj.addEventListener(evt,fn,false); else if (obj.attachEvent) obj.attachEvent('on'+evt,fn); } function removeEventSimple(obj,evt,fn) { if (obj.removeEventListener) obj.removeEventListener(evt,fn,false); else if (obj.detachEvent) obj.detachEvent('on'+evt,fn); } /** * This object was originally created by Peter-Paul Kock availabe at: * http://www.quirksmode.org/js/dragdrop.html code has been modified from * original to better suit it's new goal most significant modification is * the removal of keyboard control. Also, the dragBar option was added, * where you can define by which element the item is dragged around by (for * instance, the title bar). This was done to allow typeable inputs inside * the dragged element. * To create object, use one of two methods: * dragDrop.initElement("elementID", "dragBarId"); * dragDrop.initElement(element, dragBar); * - can be used for: * "dragDrop.initElement(document.getElementById("elementID"), "dragBarId");" * it is possible to combine the element reference or name and dragBar * reference or name in any way. */ dragDrop = { initialMouseX: undefined, initialMouseY: undefined, startX: undefined, startY: undefined, draggedObject: undefined, element: undefined, //the element being moved around //the part of the element that has to be clicked on to move it dragBar: undefined, initElement: function (obj, bar) { if (typeof obj == 'string') obj = document.getElementById(obj); if (typeof bar == 'string') bar = document.getElementById(bar); dragDrop.element = obj; //define element dragDrop.dragBar = bar; //define dragBar //the dragBar is the one that actually should be clicked on dragDrop.dragBar.onmousedown = dragDrop.startDragMouse; }, startDragMouse: function (e) { dragDrop.startDrag(this); var evt = e || window.event; dragDrop.initialMouseX = evt.clientX; dragDrop.initialMouseY = evt.clientY; addEventSimple(document,'mousemove',dragDrop.dragMouse); addEventSimple(document,'mouseup',dragDrop.releaseElement); return false; }, startDrag: function (obj) { if (dragDrop.draggedObject) dragDrop.releaseElement(); dragDrop.startX = dragDrop.element.offsetLeft; dragDrop.startY = dragDrop.element.offsetTop; dragDrop.draggedObject = obj; dragDrop.element.className += ' dragged'; }, dragMouse: function (e) { var evt = e || window.event; var dX = evt.clientX - dragDrop.initialMouseX; var dY = evt.clientY - dragDrop.initialMouseY; dragDrop.setPosition(dX,dY); return false; }, setPosition: function (dx,dy) { dragDrop.element.style.left = dragDrop.startX + dx + 'px'; dragDrop.element.style.top = dragDrop.startY + dy + 'px'; }, releaseElement: function() { removeEventSimple(document,'mousemove',dragDrop.dragMouse); removeEventSimple(document,'mouseup',dragDrop.releaseElement); dragDrop.element.className = dragDrop.element.className.replace(/dragged/,''); dragDrop.draggedObject = null; } } /** * This is the object used to change all the settings assosiated with * memberss and to create the proper output */ memberObject = function(parentElement, i_name, u_id, g_id){ /*initialize object variables*/ this.elements = { //elements object li: undefined, //the list item in which the user is stored drop: undefined //the div that appears below the user } this.elements.li = parentElement; this.name = i_name this.id = u_id; //stores ID of user for the box is created //get privelages from CCMData (to avoid having to pass it) this.priv = CCMData.groups["g_" + g_id].privelages[u_id]; this.group = g_id; //stores ID of group if ((this.priv != 3) && (CCMData.currentGroup = this.group)) { Out = ' <img id="img_' + this.id + '" '; Out += 'onclick="' + this.name + '.dropRender()" '; Out += 'src="' + wgScriptPath + '/extensions/CCM/images/downarrow.gif'; Out += ' ></div>'; this.elements.li.innerHTML += Out; } /*creates the select box below username to allow changes in privelages*/ this.dropRender = function() { if (!this.elements.drop) { Out = '<div>'; /*add the select statement that list all the privelage options in a *drop down menu*/ Out += '<select>'; //add option, do checking to see if it should be default Out += '<option ' + ((this.priv == 0)?'selected="selected"':'') + ' >' Out += 'Minimal Privelages</option>'; //add option, do checking to see if it should be default Out += '<option ' + ((this.priv == 1)?'selected="selected"':'') + ' >' Out += 'Standard Privelages</option>'; //add option, do checking to see if it should be default Out += '<option ' + ((this.priv == 2)?'selected="selected"':'') + ' >' Out += 'Full Privelages</option>'; /*no option is made for making PIs... since we don't want those *created in excess*/ Out += '</select>'; //add the checkmark submit button Out += ' <img id="img_submit" src="' + wgScriptPath + '/extensions/CCM/images/checkmark.gif" onclick="' + this.name + '.submitPriv()" />'; Out += '</div>'; this.elements.li.innerHTML += Out; document.getElementById('img_' + this.id).src = wgScriptPath + '/extensions/CCM/images/uparrow.gif'; this.elements.drop = this.elements.li.lastChild; } else { /*if drop alread exists and someone clicks on the arrow, that means *they want it to dissappear*/ //remove the div that holds all the dropdown content this.elements.li.removeChild(this.elements.drop); //remove the reference to the now non-existent drop area this.elements.drop = null; //resets the arrow image document.getElementById('img_' + this.id).src = wgScriptPath + '/extensions/CCM/images/downarrow.gif'; } } /*sends the request to the server to change privelages*/ this.submitPriv = function() { /*lastChild is the image, change the graphic to the loading screen, so *the person knows stuff is hapening*/ this.elements.drop.lastChild.src = wgScriptPath + '/extensions/CCM/images/loading.gif' //remove the onclick action to avoid the user clicking again by mistake this.elements.drop.lastChild.onclick = ""; //call to server sajax_do_call( "efCCM_Ajax", ["change_priv", CCMData.currentGroup, this.id, this.elements.drop.firstChild.selectedIndex], this.responsePriv ); } /*a hacky way to get around loss of "this" when function is passed to *another function. Explained at *http://w3future.com/html/stories/callbacks.xml*/ me = this; /** * This function acts on the data sent back by the server after a request * to change privelages. Due to the way functions are passed in * JavaScript, and the nature of "this" a small work around has to be * used. Since this function is sent outside of the object, all * references to the parent object should be refered to as "me" as * opposed to as "this". */ this.responsePriv = function(response) { //change users privelage CCMData.groups["g_" + me.group].privelages[me.id] = response.responseText; //changer users privelages inside object me.priv = response.responseText; me.dropRender(); //removes the drop area } } /* * Object that exists for dealing with the manage screen created by PIs on * the group page */ addScreen = { //property to store reference to the floatingDiv element element: undefined, //property to store reference to the editable portion of the element inner: undefined, //property to store an XML DOM that holds the users usersDOM: undefined, //property to store an XML DOM that holds the groups groupsDOM: undefined, /*property to store the XML DOM chunks in its heap form, ready for final *sort and output*/ heapDOM: undefined, tableBody: undefined, //property to store the HTML DOM of the table body /*creates the object, making it visible and rendering the defaul *elements of it */ create: function() { if (!addScreen.element) { //checks if items has already been created /*creates the structure of the element and the start page*/ Out = '<div id="CCM-floatDiv">'; Out += '<div id="CCM-floatDivHead"><b style="padding-left:0.5em;">Add User</b>'; Out += '<b style="position:absolute;right:0;padding-right:0.5em;" onclick="addScreen.destroy()">x</b></div>'; Out += '<div id="CCM-AddDiv">'; Out += '</div>'; Out += '</div>'; //document.getElementById("CCM-body").innerHTML += Out; document.getElementById("globalWrapper").innerHTML += Out; //sets the property of element to point to the object addScreen.element = document.getElementById("CCM-floatDiv"); addScreen.inner = document.getElementById("CCM-AddDiv"); //sets the element as dragable and defines the dragBar dragDrop.initElement(addScreen.element, "CCM-floatDivHead"); sajax_do_call("efCCM_Ajax", ["fetch_users", CCMData.currentGroup, null, null], addScreen.populateArrays ); } }, /* destroys the object and floatingDiv */ destroy: function() { //document.getElementById("CCM-body").removeChild(addScreen.element); document.getElementById("globalWrapper").removeChild(addScreen.element); addScreen.element = null; }, /* renders all the pages visible inside the element */ renderPage: function() { /*output a table with all the values*/ Out = "<table><thead>"; /*output table header*/ Out += "<tr>"; Out += "<th>User Name</th>"; Out += "<th>Real Name</th>"; Out += "<th>Email</th>"; Out += "<th>Groups</th>"; Out += "<th>Actions</th>"; Out += "</tr>"; /*output search fields*/ Out += "<tr>"; Out += '<td><input type="text" autocomplete="off" onkeyup="addScreen.search(this, \'name\')"/></td>'; Out += '<td><input type="text" autocomplete="off" onkeyup="addScreen.search(this, \'realname\')"/></td>'; Out += '<td><input type="text" autocomplete="off" onkeyup="addScreen.search(this, \'email\')"/></td>'; Out += "<td><select>"; Out += '<option selected="selected" />'; for (i=0;i<addScreen.groupsDOM.childNodes.length;i++) { Out += "<option>" + addScreen.groupsDOM.childNodes[i].firstChild.firstChild.nodeValue + "</option>"; } Out += "</select></td>"; Out += "<td></td>"; Out += "</tr>"; Out += "</thead><tfoot></tfoot><tbody></tbody><table>"; addScreen.inner.innerHTML = Out; //add to page pickChild = function(parentIndex, leftIndex, rightIndex) { maxIndex = addScreen.heapDOM.length; pValue = addScreen.heapDOM[parentIndex].firstChild.firstChild.nodeValue.toUpperCase(); lValue = (leftIndex<maxIndex)? addScreen.heapDOM[leftIndex].firstChild.firstChild.nodeValue.toUpperCase():false; rValue = (rightIndex<maxIndex)? addScreen.heapDOM[rightIndex].firstChild.firstChild.nodeValue.toUpperCase():false; if (lValue && rValue) { if ((pValue > lValue)&&(rValue >= lValue)) return leftIndex; else if ((pValue > rValue)&&(lValue >= rValue)) return rightIndex; else return parentIndex; } else if (lValue) { if (pValue > lValue) return leftIndex; else return parentIndex; } else { return parentIndex; } } //firstChild.lastChild since we want to be inside the tbody addScreen.tableBody = addScreen.inner.firstChild.lastChild addScreen.heapRender(addScreen.tableBody, pickChild); }, /** * This function takes the heap and outputs parts of it as it does the * final sorting. This rellies on the heap in addScreen.heapDOM being * already built and assumes that it is a proper binary-heap. Custome * compare pickChild is to be provided. Arguments are given to pickChild * as follows: pickChild(parentIndex, leftIndex, rightIndex). The return * must be the child that should replace the parent or the parent if no * replacement is to be made. The function should account for the chance * that rightChild will be missing. */ heapRender: function(obj, pickChild) { /*this function removes and returns the root and starts the bubbleDown *for new root*/ function pullRoot(){ if (addScreen.heapDOM.length > 2){ //if there is more than one item that fetch leaf farLeaf = addScreen.heapDOM.pop(); root = addScreen.heapDOM[1]; addScreen.heapDOM[1] = farLeaf; /* this is the bubble down for new root */ function bubbleDown(indexNode) { indexLeft = indexNode*2; indexRight = indexNode*2 + 1; newIndex = pickChild(indexNode, indexLeft, indexRight); //parent should remain as parent, then you are done if (newIndex == indexNode) { } else if (newIndex == indexLeft) { tempDOM = addScreen.heapDOM[indexLeft]; addScreen.heapDOM[indexLeft] = addScreen.heapDOM[indexNode]; addScreen.heapDOM[indexNode] = tempDOM; bubbleDown(indexLeft); //recurse on left branch } else if (newIndex == indexRight) { tempDOM = addScreen.heapDOM[indexRight]; addScreen.heapDOM[indexRight] = addScreen.heapDOM[indexNode]; addScreen.heapDOM[indexNode] = tempDOM; bubbleDown(indexRight); //recurse on right branch } } bubbleDown(1); //bubble down the root if need be return root; } else { //if only one item left (length 2) then just return the last item return addScreen.heapDOM.pop(); } } //for each item in array (remember that array[0]=undefined, hence the >1 while (addScreen.heapDOM.length>1){ Out = ""; //initialize/clear Out //initialize/clear Out, used for creating the inner content of the tr OutInner = ""; user = pullRoot(); Out += "<tr "; for (i=0;i<user.childNodes.length;i++) { OutInner += "<td>"; //if item text, then only one data field in it if (user.childNodes[i].firstChild == "[object Text]") { if (i==0) u_id = user.getAttribute("id"); //fetch single field OutInner += user.childNodes[i].firstChild.nodeValue; //set attribute Out += user.childNodes[i].tagName + " = '" + user.childNodes[i].firstChild.nodeValue + "'"; } else if (user.childNodes[i].firstChild =="[object Element]") { for (j=0;j<user.childNodes[i].childNodes.length;j++) { //output group OutInner += user.childNodes[i].childNodes[j].firstChild.nodeValue; //set attribute Out += user.childNodes[i].tagName + "_" + j + "='" + user.childNodes[i].childNodes[j].firstChild.nodeValue + "' "; //output the comma for all but the last group if (user.childNodes[i].childNodes.length > j+1) OutInner += ", "; } } else {Out += "";} //output a blank if there is no data OutInner += "</td>"; } OutInner += "<td><a onclick='addScreen.addMember(" + u_id + ")'><b>Add</b></a></td>"; //finish the opening tr tag, with all the attributes inside Out += ">"; Out += OutInner; Out += "</tr>"; obj.innerHTML += Out; } }, /** * sorting using heap sort with custom compare "doBubble" function * doBubble function has to return true if node needs to bubble up, or * false if it is to remain. Arguments are given to doBubble as follows: * doBubble(childNode, parentNode) */ customSort: function(nList, doBubble) { heap = new Array(); bubbleUp = function(indexNode){ indexParent = Math.floor(indexNode/2); if (indexParent != 0) { //check if we bubbled all the way to root if (doBubble(heap[indexNode], heap[indexParent])) { temp = heap[indexParent]; heap[indexParent] = heap[indexNode]; heap[indexNode] = temp; bubbleUp(indexParent); //recurse } } } heap[0] = undefined; for (i=0;i<nList.length;i++) { heap[i+1] = nList[i]; bubbleUp(i+1); } addScreen.heapDOM = heap; //set the heapDOM for global use later addScreen.renderPage(); //start the basic rendering }, /*populate user and group arrays*/ populateArrays: function(request) { result = request.responseText; /* * Turn string to XML object for easier navigation * IE and Mozilla, Firefox, and Opera handle it differently * hence need checking code. */ if (window.ActiveXObject) { //IE var xmlResult = new ActiveXObject("Microsoft.XMLDOM"); xmlResult.async = "false"; xmlResult.loadXML(result); } else { //Mozilla, Firefox and Opera var xmlResult = (new DOMParser()).parseFromString(result, "text/xml"); } //get ug->userlist addScreen.usersDOM = xmlResult.firstChild.firstChild; //get ug->grouplist addScreen.groupsDOM = xmlResult.firstChild.lastChild; doBubble = function(childNode, parentNode) { return (childNode.firstChild.firstChild.nodeValue.toUpperCase()<parentNode.firstChild.firstChild.nodeValue.toUpperCase()); } addScreen.customSort(addScreen.usersDOM.childNodes, doBubble); }, /*this function deals with dynamic search of all fields*/ search: function(obj, field){ //the regualar expression for searching the field rE = new RegExp("(.)*" + obj.value + "(.)*" , "i"); for (i=0;i<addScreen.tableBody.childNodes.length;i++) { if (rE.test(addScreen.tableBody.childNodes[i].getAttribute(field))) addScreen.tableBody.childNodes[i].style.display = "table-row"; else addScreen.tableBody.childNodes[i].style.display = "none"; } }, /*this function is used to addMembers*/ addMember: function(u_id) { sajax_do_call("efCCM_Ajax", ["add_gmem", CCMData.currentGroup, u_id, null], addScreen.destroy); } } /** * This function was mostly written by Dustin Diaz and is available at * http://www.dustindiaz.com/getelementsbyclass/; I modified it slightly to * better fit my needs. */ function getElementsByClass(searchClass,node,tag) { var classElements = new Array(); if ( node == null ) node = document; if ( tag == null ) tag = '*'; var els = node.getElementsByTagName(tag); var elsLen = els.length; for (i = 0, j = 0; i < elsLen; i++) { if ( els[i].className == searchClass ) { classElements[j] = els[i]; j++; } } return classElements; } function userDiscussion(user, action){ var gdis = document.getElementById("CCM-gdis"); elements = new Array; elementClass = "dis_" + user.toString(); elements = getElementsByClass(elementClass, gdis, "div"); for (i in elements){ if (action == "hl") { elements[i].id = "discussion-item-hl"; } else if (action == "clear") { elements[i].id = "discussion-item"; } } } function Group_Click(){ document.getElementById("tab-files").style.display = "none"; document.getElementById("tab-group").style.display = "block"; } function Files_Click