Server-Side Documentation

< Back to About

Sections

The Database
PHP
Ajax

The main difficulties faced in trying to get things set up on the server were getting everything so it could be either public or private, and doing so in a way that created the greatest amount of flexibility possible for the final implementation.

Both of those problems were primarily addressed in the database design. The database contains three tables: users, groups, and items. A user may create an item, and an item may be created as part of a group that may (or may not) be shared. In this way an item may be private or public in multiple ways. In the actual implementation items may only belong to groups, but since groups can be public or private as well the only thing that was lost was the ability to have unsorted, unafiliated items.

A group is shared by being entered in the database multiple times, once for each person who has access to it. A user may create a group that they choose not to share with anyone, so a group may also be a public or private method of sorting data.

The implementation in the database allows groups to be within other groups hierarchically, with some of these shared and some not. This was not implemented in the interface either due to the difficulty the nesting was causing. This is actually the same reason items may only belong to groups. In the future it would be nice to get these features fully implemented.

Other things that are allowed by the database structure but have not yet been fully implemented include images associated with items and groups and group metadata (i.e. descriptions, etc.) The database allows groups to be treated much like the items in them, via adding them to other groups, commenting on them, etc.--and in fact, each person with access to a group could have a completely different title, etc. for that group than does the creator. By losing this functionality I think we ended up with a very different project than we intended at the beginning, with an emphasis on sharing items more than sorting them.

The php that accesses the database is rather more limited than the database itself, owing to time constraints and the difficulty of implementing things like nested groups. This will be described further below in the descriptions of each php page.

I admit that it is not only possible but likely that the php (and indeed, the database itself) could be written more efficiently. However, time constraints and lack of knowledge led to multiple php pages that do much the same thing, with slight differences. This is another thing that needs to cleaned up in the future.

The javascript in the interface interacts with the php pages via xmlhttprequests. The php pages return javascript in the form of a string, which must be evaluated client-side (i.e. eval(request.responseText);), thereby creating javascript variables which may then be used to see if the request succeeded and if not why not.

The database

items

itemid - int(10) - UNSIGNED - Not null - Auto increment
url - varchar(255)
user - int(10) - UNSIGNED - Not null - Default: 0
usertype - enum('person', 'group') - Not null - Default: person
media - blob - BINARY
thumbnail - blob - BINARY
mediatype - varchar(10)
title - varchar(255)
description - text
comment - text
tags - text
creation - date - Not null - Default: 0000-00-00
expiration - date - Default: 0000-00-00
purgatory - enum('false','true') - Not null - Default: false

Most things should be self-explanatory, but some things should be explained:

Indices for the table are url, user, usertype, title, creation, expiration, mediatype. These are the things I suspect will be searched by most often, so this should speed things up if the database gets very large. Itemid is the primary key.

I figured it was ok for most of the things in the table to be null, since you shouldn't have to set all of them for everything--e.g. you shouldn't need to set an expiration date if you don't want something to expire. Things that can't be: itemid, so a specific item can be found again later, user and usertype, so the bookmark (or whatever we're calling these things?) can be associated with a person or group space (it's kinda useless if no one owns it, no?), and creation date, since there's no reason this couldn't be created automatically upon entry into the database.

Itemid is the primary key. Items will generally be selected by user, but to edit an item it will probably be easiest to use an identification number, rather than selecting by user and url or something--especially since a url may not necessarily be required for an entry.

User is a 10-digit identification number that can be associated with either a user or a group workspace. It's a fixed-length number, rather than a varchar name (e.g. username or group name) because 1)the internet tells me it will work faster this way; and 2)a group won't necessarily have a name. The number can be looked up in the users or groups table, depending on what usertype is--or the other way around of course; actually, that's the more probable case: someone requests something that belongs to them or that belongs to a group they have access to, their number or the group number is looked up, then a second query looks up in the items table where user = their number or the group number and usertype = person or group, depending.

Mediatype needs to change from varchar to an enum, with a list of acceptable file types. I'll figure this out when I start doing the php code to deal with those.

Description is a text entry, perhaps a clip from a website. Comment will be the user's comment on it. Or the user could just use one or the other for both.

Currently the tags go in a text entry. This means each tag needs to be separated from each other tag by a comma or some other delimeter. This may not be an acceptable solution, especially since text entries cannot be indexed--meaning very very slow searches. Then again, if all entries that someone has access to are pulled once and the tags are put in some other format to be accessed by javascript, this may work quite well. I really do need to learn more about relational databases.

Purgatory is a boolean entry that says whether an item has been consigned to purgatory (analogous to the windows recycle bin). True means it has, false means it hasn't. The default is of course false.

users

userid - int(10) - UNSIGNED - Not null - Auto increment
username - varchar(25) - Not null
password - varchar(25) - Not null
email - varchar(50) - Not null

Username and email are indices, userid is the primary key. It seems kinda silly to index password, since it seems highly unlikely that I would search by password and not one of the other two columns.

Mostly the only reason email is there is so if people can get their passwords they can retrieve it. There could be fields for things like address, phone number, and whatever else that sites ask you when you sign up for something, but I don't think we actually care about any of those, do we?

There may eventually be other columns with various preferences, but since we haven't decided what those will be yet they aren't in there.

groups

groupid - int(10) - UNSIGNED - Not null - Auto increment
user - int(10) - UNSIGNED - Not null - Default: 0
usertype - enum('person', 'group') - Not null - Default: person
media - blob - BINARY
thumbnail - blob - BINARY
mediatype - varchar(10)
title - varchar(255)
description - text
comment - text
tags - text
creation - date - Not null - Default: 0000-00-00
expiration - date - Default: 0000-00-00

This table is pretty much the same as the items table, since one of the ideas is that someone should be able to treat a group they have access to the same way they would treat an individual item. Hence the media, tags, comments, etc. columns. The indices are the same also, with the exception of url, which is not in this table.

Groupid is also treated as an index, not a primary key, because each group will be in the table more than once. This allows each user with access to it to create their own personal tags, comments, etc. that aren't shared by the other members. When a group is created all users will have those fields set to whatever the group creator puts in them, but the other users can modify those thereafter. Theoretically there could also be comments, etc. visible to the entire group, which would be stored in a row where the group is owned by itself--i.e. the usertype would be "group" and the user would be the same as the groupid.

This brings up a second point: a group can be accessed by users, but also by other groups. So you could have two groups where all the members also have access to a third group workspace. I'm not really sure what this buys you--maybe subcommittee type stuff?--but it's pretty easy to implement and might be useful, so why not?

PHP

All of the php pages require that the request have a POST header. They verify login on all pages except for login.php and logout.php by looking for the session variabled "logged_in". The user name and user id are also written in the session (with variables creatively called "username" and "userid"); these are accessed on functions that require something to be associated with a user but no user has been submitted. This would allow these pages to be more general, but it turns out that they never get used to associate something with another member. Maybe once we get the move/copy functionality in.

Aside from the simple verification that the user is logged in and on some pages that there is an item or group id being posted to it there is next to no validation. The interface constrains what goes to these pages, but if someone were to access them in another way they could send someone else's user id and get all their data back, modify it, etc. This needs to change.

All of the pages return strings of javascript variables and values, including javascript arrays for each column that gets fetched from the item table, etc. (i.e. an array for all item ids, one for all titles, etc.) These are evaluated client-side to create the variables on the browser for later use.

login.php

Input:
username (required)
password (required)
Looks the username up in the database, compares the passwords, and if they match the user is logged in. It creates the session variables "username", "userid", and "logged_in", which is set to true, then returns the string "var success = true;". If the password doesn't match or if the user isn't found it returns "var success = false; var message = 'Bad username or password.';".

logout.php

Input:
none
Destroys the session and returns the string "var success = true; var message = 'You are now logged out.';". It never returns anything else, since if something goes horribly wrong and the user is not logged out it wouldn't get to the point of returning those variables anyway.

signup.php

Input:
username (required)
password (required)
confirm (required)
email (required)
Makes sure the username isn't taken, makes sure the password and password confirmation match, and that the email address has at least one character, an @ symbol, and at least one other character. If everything is fine it enters the username, password, and email into the database, then returns success=true and an array of empty strings called errors. If there are problems it returns success=false and the strings in the array will contain error messages like "Please enter a user name." in the slot in the array corresponding to the field that was wrong. The fields are numerically indexed, so if the username was wrong the error message will always be in errors[0], regardless of how it was passed to the php or where the field appeared in the page. This should probably actually be an enumeration, i.e. errors[username]="Please enter a user name.";

getusername.php

Input:
none
If the user is not logged in, it returns "success=false" and "loginfailure=true". Otherwise it returns "success=false" and a variable "username" with the user name in it.

addgroup.php

Input: user (optional) usertype (optional) title (optional) description (optional) comment (optional) tags (optional) - must be in a comma-separated list with no spaces before or after the commas day (optional) - for the expiration date - day, month, and year must all be present to set an expiration date month (optional) - ditto year (optional) - ditto media (optional) - not implemented yet members (optional) - a comma-separated list of the names of other users If the user is not logged in, it returns "success=false" and "loginfailure=true". If the user is logged in then it is entered into the database for the current user or for the user specified in the "user" variable (this allows groups to have subgroups) and for each user in the members list. When the group is successfully entered into the database it returns "success=true". There is no other error checking, so there are no other points that might return something. All variables that are not set in the POST query are entered into the database as empty strings.

additem.php

Input:
user (optional)
usertype (optional)
url (optional)
title (optional)
description (optional)
comment (optional)
tags (optional) - must be in a comma-separated list with no spaces before or after the commas
day (optional) - for the expiration date - day, month, and year must all be present to set an expiration date
month (optional) - ditto
year (optional) - ditto
media (optional) - not implemented yet
If the user is not logged in, it returns "success=false" and "loginfailure=true". If the user is logged in then it is entered into the database for the current user or for the user specified in the "user" variable (this allows items to be entered for groups or for the current user). When the item is successfully entered into the database it returns "success=true". There is no other error checking, so there are no other points that might return something. All variables that are not set in the POST query are entered into the database as empty strings.

editgroup.php

Input:
groupid (required)
title (optional)
description (optional)
comment (optional)
tags (optional) - must be in a comma-separated list with no spaces before or after the commas
day (optional) - for the expiration date - day, month, and year must all be present to set an expiration date
month (optional) - ditto
year (optional) - ditto
media (optional) - not implemented yet
If the user is not logged in, it returns "success=false" and "loginfailure=true". If if groupid is absent it returns "success=false" and "message='groupid missing'". Takes all fields sent to it and updates them in the database with the new values.

edititem.php

Input:
itemid (required)
user (optional) - this and usertype allow an item to be reassociated with a group
usertype (optional)
url (optional)
title (optional)
description (optional)
comment (optional)
tags (optional) - must be in a comma-separated list with no spaces before or after the commas
day (optional) - for the expiration date - day, month, and year must all be present to set an expiration date
month (optional) - ditto
year (optional) - ditto
media (optional) - not implemented yet
If the user is not logged in, it returns "success=false" and "loginfailure=true". If if itemid is absent it returns "success=false" and "message='itemid missing'". Takes all fields sent to it and updates them in the database with the new values.

sharegroup.php

Input:
groupid (required)
members (required)
- a comma-separated list, as in addgroup.php
If the user is not logged in, it returns "success=false" and "loginfailure=true". If the group id is absent it returns "success=false" and "message='groupid missing'". Adds an entry in the groups table for each of the users in the members list. Does not check if that user already has that group, but probably should.

getgroupbyuser.php

Input:
user (optional)
usertype (optional) - required if user is sent
If the user is not logged in, it returns "success=false" and "loginfailure=true". Takes the userid and usertype (or if none just takes the user id of the current user) and finds all groups associated with them. The data in each column are returned as entries in javascript arrays, so groupid[0] would be the group id of the first entry in the database that matched, groupid[1] would be the second, etc. There is one array for each column of the groups table, but all are prefixed with the word group, so "title" would become "grouptitle"--groupid has no additional prefix.

getitembyuser.php

Input:
user (optional)
usertype (optional) - required if user is sent
If the user is not logged in, it returns "success=false" and "loginfailure=true". Takes the userid and usertype (or if none just takes the user id of the current user) and finds all items associated with them where purgatory is false. The data in each column are returned as entries in javascript arrays, so itemid[0] would be the item id of the first entry in the database that matched, itemid[1] would be the second, etc. There is one array for each column of the items table except for purgatory, but all are prefixed with the word item, so "item" would become "itemtitle"--itemid has no additional prefix.

getpurgatory.php

Input:
user (optional)
usertype (optional) - required if user is sent
If the user is not logged in, it returns "success=false" and "loginfailure=true". Takes the userid and usertype (or if none just takes the user id of the current user) and finds all items associated with them where purgatory is true. The data in each column are returned as entries in javascript arrays, so itemid[0] would be the item id of the first entry in the database that matched, itemid[1] would be the second, etc. There is one array for each column of the items table except for purgatory, but all are prefixed with the word item, so "item" would become "itemtitle"--itemid has no additional prefix.

dismissitem.php

Input:
itemid (required)
If the user is not logged in, it returns "success=false" and "loginfailure=true". If if itemid is absent it returns "success=false" and "message='itemid missing'". Sets the purgatory field of the current item to true.

undismissitem.php

Input:
itemid (required)
If the user is not logged in, it returns "success=false" and "loginfailure=true". If if itemid is absent it returns "success=false" and "message='itemid missing'". Sets the purgatory field of the current item to false.

deleteitem.php

Input:
itemid (required)
If the user is not logged in, it returns "success=false" and "loginfailure=true". If if itemid is absent it returns "success=false" and "message='itemid missing'". Removes the item from the database.

Ajax

Basic code to call a php page via XMLHttpRequest

The php page that's being called returns a string. The javascript in this example code will pop up an alert containing that string.

There are four methods here that will call the testAjax function, which in turn will create an xmlhttprequest to get the response string from the php page, then pop it up: a submit box in a form with an onsubmit method, a button in a form, a button outside of a form with an onclick method, and a link with an onclick method. Note also the "return false" after every event call.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
<head>
<title>Test Page</title>

<script>

function createXMLHttpRequest() {
//create the xmlhttprequest in various browsers
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
try { return new XMLHttpRequest(); } catch(e) {}
alert("XMLHttpRequest not supported");
return null;
}

function testAjax () {
var req = createXMLHttpRequest();
req.open("post", "test.php", true); //request type, page it goes to, and asynchronous
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
//allows php to read variables submitted (below) as it would data submitted through form actions

req.onreadystatechange = function() { //event listener to detect if the php is done running
if (req.readyState != 4) { return; } //4 is done, 1, 2, and 3 mean it's incomplete
alert(req.responseText); //display the response in an alert
//note that it could also be sent to another function for further processing
};

req.send("");//send the request; this would contain POST variables, but none are needed here
//format for these is variable=value&variable=value etc.
}

</script>
</head>

<body onLoad="alert('loaded');">

<form name="testform" onsubmit="testAjax(); return false">
<input type="submit" value="Test Submit"><br>
<button>Test Button 1</button>
</form>
<button onClick="testAjax(); return false">Test Button 2</button><br>
<a href="#" onClick="testAjax(); return false">Link</a>

</body>
</html>