Index: README
===================================================================
RCS file: /home/cvs/nuug-memberdb/README,v
retrieving revision 1.1.1.3
retrieving revision 1.4
diff -u -3 -p -r1.1.1.3 -r1.4
--- README	9 Mar 2007 11:20:00 -0000	1.1.1.3
+++ README	17 Nov 2007 09:34:28 -0000	1.4
@@ -18,9 +18,14 @@ As of MemberDB 0.4, we require MySQL 5.0
 
 Use the mysql client to connect to the mysql server. Run the following commands (these will create a memberdb database and install the initial data for a new installation).
 
+(This sequence is modified for NUUG)
+
 mysql> \. mysql_prelude.sql
 mysql> \. create_database.sql
+mysql> \. create_database_sp.sql
+mysql> \. create_database_nuug.sql
 mysql> \. initial_data.sql
+mysql> \. initial_data_nuug.sql
 mysql> \. site_messages.sql
 mysql> GRANT ALL ON memberdb.* TO 'memberdb'@'localhost' IDENTIFIED BY 'pants';
 
Index: sql/create_database.sql
===================================================================
RCS file: /home/cvs/nuug-memberdb/sql/create_database.sql,v
retrieving revision 1.1.1.3
retrieving revision 1.13
diff -u -3 -p -r1.1.1.3 -r1.13
--- sql/create_database.sql	9 Mar 2007 11:20:00 -0000	1.1.1.3
+++ sql/create_database.sql	18 Nov 2007 21:57:16 -0000	1.13
@@ -17,6 +17,49 @@
 -- on MySQL 5.0 and above now, nothing else.
 
 
+-- comment_groups table
+-- --------------------
+--
+-- Id for group of immutable comments.
+-- Would have been a sequence in better databases.
+\! echo Creating table comment_groups
+create table comment_groups
+(
+    comment_id			integer		NOT NULL AUTO_INCREMENT,
+--
+    CONSTRAINT "comment_groups_comment_id_pk" PRIMARY KEY (comment_id)
+);
+
+-- comments table
+-- --------------
+--
+-- Immutable comments.
+\! echo Creating table comments
+create table comments
+(
+    comment_id			integer		NOT NULL,
+    created_dtm			timestamp	NOT NULL,
+    created_by			varchar(50)	NOT NULL  DEFAULT '-- unknown --',
+    comm			varchar(200)	NOT NULL,
+--
+    CONSTRAINT "comments_comment_id_fk" FOREIGN KEY (comment_id) REFERENCES comment_groups (comment_id)
+);
+
+
+-- countries table
+-- ---------------
+--
+\! echo Creating table countries
+create table countries
+(
+    code	char(2)		NOT NULL	COMMENT 'ISO 3166',
+    name	varchar(50)	NOT NULL,
+--
+    CONSTRAINT "countries_pk"	   PRIMARY KEY (code),
+    CONSTRAINT "countries_name_un" UNIQUE (name)
+);
+
+
 -- Members can have different qualifications
 -- Useful for:
 -- 	  keeping track of what degrees members are doing
@@ -24,6 +67,7 @@
 --
 -- This may be useful for working out where Alumni is coming
 -- from, so that you can better focus organisation relations
+\! echo Creating table qualifications
 create table qualifications (
 	id integer auto_increment not null,
 	name varchar(100) not null,
@@ -38,6 +82,7 @@ create table qualifications (
 --
 -- Lists the type of organisations. This is largely
 -- up to the site to work out how they want to use this.
+\! echo Creating table org_types
 create table org_types (
 	id integer auto_increment not null,
 	name varchar(50),
@@ -64,6 +109,7 @@ create table org_types (
 --      automatically becoming members of another organization.
 --    - You offer discounts to members of other orgs to an event
 --      and want to track their event_signup_status in this DB.
+\! echo Creating table orgs
 create table orgs (
 	id       integer auto_increment not null,
 	name     varchar(50) not null,
@@ -74,20 +120,22 @@ create table orgs (
 	suburb	 varchar(50) default NULL,
 	postcode varchar(10) default NULL,
 	state	 varchar(50) default NULL,
-	country  varchar(50) default 'Australia',
+	country_code	     char(2)	NOT NULL,
 	website  varchar(100),
 	email    varchar(100),
 	phone    varchar(50),
 	fax      varchar(50),
 	mobile   varchar(50),
 	description text,
-	CONSTRAINT "orgs_pkey" PRIMARY KEY (id)
+	CONSTRAINT "orgs_pkey" PRIMARY KEY (id),
+	CONSTRAINT "orgs_country_code_fk" FOREIGN KEY (country_code) REFERENCES countries (code)
 );
 
 -- org_org_types
 -- -------------
 --
 -- Maps types to orgs
+\! echo Creating table org_org_types
 create table org_org_types (
 	org_id integer,
 	org_type_id integer,
@@ -102,6 +150,7 @@ create table org_org_types (
 -- This is useful for:
 --    - Keeping track of associate organizations
 --    - keeping track of parent/child organizations
+\! echo Creating table org_relation_types
 create table org_relation_types (
 	id integer auto_increment not null,
 	org_id int not null,
@@ -113,6 +162,7 @@ create table org_relation_types (
 
 -- and the actual relations are stored here
 -- the lookup on the relation_type should be done on the org_id
+\! echo Creating table org_relations
 create table org_relations (
 	org_id int not null,
 	related_to_org_id int not null,
@@ -123,6 +173,50 @@ create table org_relations (
 	CONSTRAINT "orgs_relations_org_relation_type_id_fkey" FOREIGN KEY (org_relation_type_id) references org_relation_types(id) on update restrict
 );
 
+
+-- address table
+-- -------------
+--
+\! echo Creating table addresses
+create table addresses (
+    id			integer		NOT NULL AUTO_INCREMENT,
+    address1		varchar(50),
+    address2		varchar(50),
+    address3		varchar(50),
+    suburb		varchar(50),
+    postcode		varchar(10)	NOT NULL,
+    state		varchar(50),
+    country_code	char(2)		NOT NULL,
+--
+    modified_by		varchar(50)	NOT NULL  DEFAULT '-- unknown --',
+    modified_dtm	timestamp	NOT NULL,
+--
+    CONSTRAINT "addresses_pk"		   PRIMARY KEY (id),
+    CONSTRAINT "addresses_country_code_fk" FOREIGN KEY (country_code) REFERENCES countries (code)
+);
+
+-- address history table
+-- ---------------------
+--
+\! echo Creating table address_history
+create table address_history (
+    id			integer		NOT NULL,
+    address1		varchar(50),
+    address2		varchar(50),
+    address3		varchar(50),
+    suburb		varchar(50),
+    postcode		varchar(10)	NOT NULL,
+    state		varchar(50),
+    country_code	char(2)		NOT NULL,
+--
+    modified_by		varchar(50)	NOT NULL,
+    modified_dtm	datetime	NOT NULL,
+--
+    CONSTRAINT "address_history_id_fk"           FOREIGN KEY (id)           REFERENCES addresses (id),
+    CONSTRAINT "address_history_country_code_fk" FOREIGN KEY (country_code) REFERENCES countries (code)
+);
+
+
 -- members table
 -- -------------
 -- Details on members are stored here.
@@ -134,27 +228,44 @@ create table org_relations (
 --
 -- Member info isn't deleted when a member is no longer a member of
 -- an org. They simply won't have a current membership.
+\! echo Creating table members
 create table members (
 	id integer auto_increment not null,
 	date_entered datetime,
+	corporation boolean NOT NULL DEFAULT false,
+	contact_person varchar(100),
 	first_name varchar(50) not null,
 	middle_name varchar(50),
 	last_name varchar(50),
 -- student_id varchar(15) default NULL,
+	student_id_shown		date,
 	DOB date default NULL,
 	sex char(1) default NULL,
+-- member contact address
 	address1 varchar(50) default NULL,
 	address2 varchar(50) default NULL,
+	address3 varchar(50),
 	suburb varchar(50) default NULL,
 	postcode varchar(10) default NULL,
 	state varchar(50) default NULL,
-	country varchar(20) default NULL,
+	country_code		char(2)		NOT NULL,
+-- invoice address
+	invoice_address integer,
+--
 	email varchar(50) default NULL ,
 	phone_home varchar(25) default NULL,
 	phone_mobile varchar(25) default NULL,
-	CONSTRAINT "members_pkey" PRIMARY KEY (id)
+--
+	comment_id		integer,
+	modified_by  varchar(50) NOT NULL  DEFAULT '-- unknown --',
+	modified_dtm timestamp   NOT NULL,
+--
+	CONSTRAINT "members_pkey" PRIMARY KEY (id),
+--,	CONSTRAINT "members_email_unique" UNIQUE (email)
+	CONSTRAINT "members_country_code_fk" FOREIGN KEY (country_code) REFERENCES countries (code)
 );
 
+
 -- members_old_details
 -- -------------------
 --
@@ -164,27 +275,42 @@ create table members (
 -- If this gets large, we could convert the table type to MyISAM or
 -- even archive. We're not (yet) going to be doing any queries on it.
 -- It's more of a 'useful for archival purposes'
+\! echo Creating table members_old_details
 create table members_old_details (
 	id integer not null,
-	date_entered datetime,
+	date_entered datetime NOT NULL,
+	corporation boolean NOT NULL,
+	contact_person varchar(100),
 	first_name varchar(50) not null,
 	middle_name varchar(50),
 	last_name varchar(50),
 	DOB date default NULL,
 	sex char(1) default NULL,
+-- member info address
 	address1 varchar(50) default NULL,
 	address2 varchar(50) default NULL,
+	address3 varchar(50),
 	suburb varchar(50) default NULL,
 	postcode varchar(10) default NULL,
 	state varchar(50) default NULL,
-	country varchar(20) default NULL,
+	country_code		varchar(20)	NOT NULL,
+-- invoice address
+	invoice_address integer,
+--
 	email varchar(50) default NULL ,
 	phone_home varchar(25) default NULL,
 	phone_mobile varchar(25) default NULL,
+--
+	comment_id		integer,
+	modified_by  varchar(50) NOT NULL,
+	modified_dtm datetime    NOT NULL,
+--
 	CONSTRAINT "members_old_details_id_fkey" FOREIGN KEY (id) references members(id) on update restrict,
-	CONSTRAINT "members_old_details_pkey" PRIMARY KEY (id,date_entered)
+--	CONSTRAINT "members_old_details_pkey" PRIMARY KEY (id,date_entered)
+	CONSTRAINT "members_old_details_country_code_fk" FOREIGN KEY (country_code) REFERENCES countries (code)
 );
 
+\! echo Creating table member_qualifications
 create table member_qualifications (
 	member_id	integer,
 	qualification_id integer,
@@ -203,6 +329,7 @@ create table member_qualifications (
 -- salt is 'aoeu56' then the password will be the md5 sum of 'helloaoeu56'
 -- this is so password cracking utilities will have an even harder
 -- time of cracking the passwords :)
+\! echo Creating table passwd
 create table passwd (
 	member_id int not null,
 	salt char(10) not null,
@@ -225,6 +352,7 @@ create table passwd (
 -- 1,0 = provides membership
 -- 0,1 = revokes membership
 -- 1,1 = overrides revoked membership with membership
+\! echo Creating table member_types
 create table member_types (
 	id integer auto_increment not null,
 	org_id int not null,
@@ -243,23 +371,65 @@ create table member_types (
 -- and when their membership expires.
 --
 -- When someone changes membership status (e.g. from pending to full)
--- then they should get a new entry in the org_members table.
+-- then they should get a new entry in the org_members_history table.
 -- I am not sure if the expiry for the old membership should change...
 -- 
+\! echo Creating table org_members
 create table org_members (
+	id int  NOT NULL  AUTO_INCREMENT				COMMENT 'membership id',
+	parent_member_id int,
 	member_id int not null,
 	org_id int not null,
 	member_type_id int not null,
+	invoice_ref		varchar(50)				COMMENT 'Payers invoice reference',
+	invoice_payment_days	integer		NOT NULL  DEFAULT 15	COMMENT 'number of days until payment day',
+	last_invoiced_to_dt	date					COMMENT 'last day of invoiced period',
+	last_paid_to_dt		date					COMMENT 'last day paid for',
 	start_date datetime not null,
 	expiry datetime,
+	comment_id		integer,
+--
+	modified_by  varchar(50) NOT NULL  DEFAULT '-- unknown --',
+	modified_dtm timestamp   NOT NULL,
+--
+	CONSTRAINT "org_members_id_pkey" PRIMARY KEY (id),
+	CONSTRAINT "org_members_org_id_member_id_unique" UNIQUE (org_id,member_id),
+	CONSTRAINT "org_members_parent_member_id_fkey" FOREIGN KEY (parent_member_id) REFERENCES members (id),
 	CONSTRAINT "org_members_member_id_fkey" FOREIGN KEY (member_id) references members(id) on update RESTRICT,
-	CONSTRAINT "org_members__id_fkey" FOREIGN KEY (org_id) references orgs(id) on update RESTRICT,
-	CONSTRAINT "org_members_member_type_id_fkey" FOREIGN KEY (member_type_id) references member_types(id) on update RESTRICT	
+	CONSTRAINT "org_members_org_id_fkey" FOREIGN KEY (org_id) references orgs(id) on update RESTRICT,
+	CONSTRAINT "org_members_member_type_id_fkey" FOREIGN KEY (member_type_id) references member_types(id) on update RESTRICT
+);
+
+\! echo Creating table org_members_history
+create table org_members_history
+(
+    id			int		NOT NULL		COMMENT 'membership id',
+    parent_member_id	int,
+    member_id		int		NOT NULL,
+    org_id		int		NOT NULL,
+    member_type_id	int		NOT NULL,
+    invoice_ref		varchar(50)				COMMENT 'payers invoice reference',
+    invoice_payment_days integer	NOT NULL  DEFAULT 15	COMMENT 'number of days from invoice day to pay day',
+    last_invoiced_to_dt	date					COMMENT 'subscription end day',
+    last_paid_to_dt	date					COMMENT 'day subscription is paid to',
+    start_date		datetime	NOT NULL,
+    expiry		datetime,
+    comment_id		integer,
+--
+    modified_by		varchar(50)	NOT NULL,
+    modified_dtm	datetime	NOT NULL,
+--
+    CONSTRAINT "org_members_history_id_fk"		FOREIGN KEY (id)		REFERENCES org_members (id),
+    CONSTRAINT "org_members_history_parent_member_i_fk"	FOREIGN KEY (parent_member_id)	REFERENCES members (id),
+    CONSTRAINT "org_members_history_member_id_fk"	FOREIGN KEY (member_id)		REFERENCES members(id),
+    CONSTRAINT "org_members_history_org_id_fk"		FOREIGN KEY (org_id)		REFERENCES orgs(id),
+    CONSTRAINT "org_members_history_member_type_id_fk"	FOREIGN KEY (member_type_id)	REFERENCES member_types(id)
 );
 
 -- event_types table
 -- -----------------
 -- there are many types of events
+\! echo Creating table event_types
 create table event_types (
 	id integer auto_increment not null,
 	type varchar(50),
@@ -276,6 +446,7 @@ create table event_types (
 -- we also track organisers of events and a few other little
 -- useful bits of information. Most info should be on the event
 -- page though.
+\! echo Creating table events
 create table events (
 	id integer auto_increment not null,
 	event_type_id integer not null,
@@ -289,6 +460,7 @@ create table events (
 	CONSTRAINT "events_event_type_id_fkey" FOREIGN KEY (event_type_id) references event_types(id) on update restrict
 );
 
+\! echo Creating table event_host_orgs
 create table event_host_orgs (
 	event_id int not null,
 	org_id int not null,
@@ -298,6 +470,7 @@ create table event_host_orgs (
 	CONSTRAINT "event_host_orgs_org_id_fkey" FOREIGN KEY (org_id) references orgs(id) on update restrict
 );
 
+\! echo Creating table event_organisers
 create table event_organisers (
 	event_id int not null,
 	member_id int not null,
@@ -306,6 +479,7 @@ create table event_organisers (
 	CONSTRAINT "event_organisers_member_id_fkey" FOREIGN KEY (member_id) references members(id) on update restrict
 );
 
+\! echo Creating table event_signup_status
 create table event_signup_status (
 	id integer auto_increment not null,
 	event_id int default NULL,
@@ -313,6 +487,7 @@ create table event_signup_status (
 	CONSTRAINT "event_signup_status_pkey" PRIMARY KEY (id)
 );
 
+\! echo Creating table event_signup
 create table event_signup (
 	event_id int not null,
 	member_id int not null,
@@ -323,6 +498,7 @@ create table event_signup (
 	CONSTRAINT "event_signup_event_signup_status_id_fkey" FOREIGN KEY (event_signup_status_id) references event_signup_status(id) on update restrict
 );
 
+\! echo Creating table positions
 create table positions (
 	id integer auto_increment not null,
 	org_id int default NULL,
@@ -332,6 +508,7 @@ create table positions (
 	CONSTRAINT "positions_org_id_fkey" FOREIGN KEY (org_id) references orgs(id) on update restrict
 );
 
+\! echo Creating table positions_held
 create table positions_held (
 	position_id int not null,
 	org_id int not null,
@@ -346,6 +523,7 @@ create table positions_held (
 -- Member_cards
 -- ------------
 
+\! echo Creating table member_cards
 create table member_cards (
 	org_id int not null,
 	member_id int not null,
@@ -376,6 +554,7 @@ create table member_cards (
 --
 -- Is MyISAM - want to have logs after ROLLBACK and speed.
 -- in future, probably ARCHIVE with partitioning.
+\! echo Creating table log
 create table "log" ( -- quotes is for sql_mode=ansi compatibility
         logtime timestamp not null,
         priority integer,
@@ -400,6 +579,7 @@ create table "log" ( -- quotes is for sq
 --
 -- id - referenced in permissions
 -- activity - textual representation of the activity
+\! echo Creating table activities
 create table activities (
 	id integer auto_increment not null,
 	activity varchar(100) unique not null,
@@ -414,6 +594,7 @@ create table activities (
 -- id - group id
 -- group_name - name of group
 -- description -- description
+\! echo Creating table groups
 create table groups (
 	id integer auto_increment not null,
 	org_id int,
@@ -423,6 +604,7 @@ create table groups (
 	CONSTRAINT "groups_org_id_fkey" FOREIGN KEY (org_id) references orgs(id) on update restrict
 );
 
+\! echo Creating table group_members
 create table group_members (
 	group_id int not null,
 	member_id int not null,
@@ -438,6 +620,7 @@ create table group_members (
 --
 -- users or groups are granted the right to perform activities.
 -- i.e. they have permissions!
+\! echo Creating table permissions
 create table permissions (
 	member_id int,
 	group_id int,
@@ -459,6 +642,7 @@ create table permissions (
 --
 -- Provides the ability to hold online elections.
 
+\! echo Creating table elections
 create table election (
         id integer auto_increment not null,
         org_id int not null,  -- election for this org (NULL=all orgs)
@@ -478,6 +662,7 @@ create table election (
         CONSTRAINT "election_org_id_fkey" FOREIGN KEY (org_id) references orgs(id) on update restrict
 );
 
+\! echo Creating table election_position
 create table election_position (
  id integer auto_increment not null primary key,
  election_id int not null,
@@ -487,6 +672,7 @@ create table election_position (
  CONSTRAINT "election_position_election_id_fkey" FOREIGN KEY (election_id) references election(id) on update restrict
 );
 
+\! echo Creating table election_candidate
 create table election_candidate (
      id integer auto_increment not null primary key,
      election_position_id int not null,
@@ -498,6 +684,7 @@ create table election_candidate (
  -- FIXME: need constraint that member is a member of the correct org.
  );
 
+\! echo Creating table election_candidate_nomination
 create table election_candidate_nomination (
      when_nominated datetime not null,
      election_position_id int not null,
@@ -510,6 +697,7 @@ create table election_candidate_nominati
      CONSTRAINT "election_candidate_nomination_election_position_id_fkey" FOREIGN KEY (election_position_id) references election_position(id) on update restrict
 );
 
+\! echo Creating table election_vote
 create table election_vote (
 	member_id int not null,
 	candidate_id int not null,
@@ -527,6 +715,7 @@ create table election_vote (
 -- Replaces the old site-messages infrastructure.
 --
 
+\! echo Creating table site_messages
 create table site_messages (
 	name varchar(50),
 	mail_to text,
@@ -535,6 +724,7 @@ create table site_messages (
 	message text
 );
 
+\! echo Creating index site_messages_name_idx
 CREATE INDEX site_messages_name_idx ON site_messages(name);
 
 --
@@ -542,10 +732,11 @@ CREATE INDEX site_messages_name_idx ON s
 --
 -- This is use by the PEAR Mail Queue module.
 
+\! echo Creating table mail_queue
 CREATE TABLE mail_queue (
   id bigint(20) NOT NULL default '0',
-  create_time datetime NOT NULL default '0000-00-00 00:00:00',
-  time_to_send datetime NOT NULL default '0000-00-00 00:00:00',
+  create_time datetime NOT NULL,
+  time_to_send datetime NOT NULL,
   sent_time datetime default NULL,
   id_user bigint(20) NOT NULL default '0',
   ip varchar(20) NOT NULL default 'unknown',
@@ -565,6 +756,7 @@ CREATE TABLE mail_queue (
 -- `mail_queue_seq`
 --
 
+\! echo Creating table mail_queue_seq
 CREATE TABLE mail_queue_seq (
   id int(10) unsigned NOT NULL auto_increment,
   PRIMARY KEY  (id)
@@ -581,9 +773,11 @@ CREATE TABLE mail_queue_seq (
 --
 -- current memberships of all members of all orgs, 
 -- 	of all types, which provide membership
+\! echo Creating view current_memberships
 CREATE or replace view current_memberships AS
 	select distinct
 	  members.*,
+	  org_members.parent_member_id,
 	  orgs.id as org_id,
 	  orgs.name as org_name,
 	  member_types.id as member_type_id,
@@ -598,50 +792,20 @@ CREATE or replace view current_membershi
 	  AND org_members.org_id = orgs.id
 	  AND org_members.member_type_id = member_types.id
 	  AND member_types.org_id = orgs.id 
-	  and (
-		--
 		-- you are a current member if your membership:
-		-- - period started before now, and ends after now
-		-- - never ends, always existed
-		-- - started before now, never ends
-		-- - started at unknown, ends after now
-			(org_members.start_date < now()
-			  and org_members.expiry > now())
-		     or (org_members.start_date is null
-			  and org_members.expiry is null)
-		     or (org_members.start_date<now()
-			  and org_members.expiry is null)
-		     or (org_members.start_date is null
-			  and org_members.expiry>now())
-		    )
+		-- - period started before now, and
+		-- -  ends after now or never ends
+	  AND  org_members.start_date <= now()
+	  AND (org_members.expiry     >= now() or
+	       org_members.expiry     is null)
 	  AND member_types.validates_membership = true
-	  AND not exists (select member_id from org_members,member_types
-		where org_members.member_type_id = member_types.id
-		and org_members.member_id = members.id
-		and member_types.revokes_membership = true
-		and member_types.validates_membership = false
-		and (
-		--
-		-- you are a current member if your membership:
-		-- - period started before now, and ends after now
-		-- - never ends, always existed
-		-- - started before now, never ends
-		-- - started at unknown, ends after now
-			(org_members.start_date < now()
-			  and org_members.expiry > now())
-		     or (org_members.start_date is null
-			  and org_members.expiry is null)
-		     or (org_members.start_date<now()
-			  and org_members.expiry is null)
-		     or (org_members.start_date is null
-			  and org_members.expiry>now())
-		    )
-		)
 	;
 
+\! echo Creating view pending_memberships
 create or replace view pending_memberships as
 	select distinct
 	  members.*,
+	  org_members.parent_member_id,
 	  orgs.id as org_id,
 	  orgs.name as org_name,
 	  member_types.id as member_type_id,
@@ -656,34 +820,11 @@ create or replace view pending_membershi
 	  AND org_members.org_id = orgs.id
 	  AND org_members.member_type_id = member_types.id
 	  AND member_types.org_id = orgs.id 
-	  AND org_members.start_date < now()
-	  AND org_members.expiry > now()
+	  AND  org_members.start_date <= now()
+	  AND (org_members.expiry     >= now() or
+	       org_members.expiry     is null)
 	  AND member_types.validates_membership = false
 	  AND member_types.revokes_membership = false
-	  AND not exists (select id from current_memberships
-		where id = members.id)
-	  AND not exists (select member_id from org_members,member_types
-		where org_members.member_type_id = member_types.id
-		and org_members.member_id = members.id
-		and member_types.revokes_membership = true
-		and member_types.validates_membership = false
-		and (
-		--
-		-- you are a current member if your membership:
-		-- - period started before now, and ends after now
-		-- - never ends, always existed
-		-- - started before now, never ends
-		-- - started at unknown, ends after now
-			(org_members.start_date < now()
-			  and org_members.expiry > now())
-		     or (org_members.start_date is null
-			  and org_members.expiry is null)
-		     or (org_members.start_date<now()
-			  and org_members.expiry is null)
-		     or (org_members.start_date is null
-			  and org_members.expiry>now())
-		    )
-		)
 	;
 
 -- has_had_membership
@@ -691,9 +832,11 @@ create or replace view pending_membershi
 --
 -- anyone who has, or has had membership to an org
 
+\! echo Creating view has_had_membership
 CREATE or replace view has_had_membership AS
 	select distinct
 	  members.*,
+	  org_members.parent_member_id,
 	  orgs.id as org_id,
 	  orgs.name as org_name,
 	  member_types.id as member_type_id,
@@ -708,44 +851,31 @@ CREATE or replace view has_had_membershi
 	  AND org_members.org_id = orgs.id
 	  AND org_members.member_type_id = member_types.id
 	  AND member_types.org_id = orgs.id 
-	  and (
-		--
-		-- you are a current member if your membership:
-		-- - period started before now, and ends after now
-		-- - never ends, always existed
-		-- - started before now, never ends
-		-- - started at unknown, ends after now
-			(org_members.start_date < now())
-		     or (org_members.start_date is null)
-		     or (org_members.start_date<now())
-		     or (org_members.start_date is null)
-		    )
+		-- you are or have been a member if your membership:
+		-- - period started before now
+	  AND org_members.start_date <= now()
 	  AND member_types.validates_membership = true
-	  AND not exists (select member_id from org_members,member_types
-		where org_members.member_type_id = member_types.id
-		and org_members.member_id = members.id
-		and member_types.revokes_membership = true
-		and member_types.validates_membership = false
-		and (
-		--
-		-- you are a current member if your membership:
-		-- - period started before now, and ends after now
-		-- - never ends, always existed
-		-- - started before now, never ends
-		-- - started at unknown, ends after now
-			(org_members.start_date < now()
-			  and org_members.expiry > now())
-		     or (org_members.start_date is null
-			  and org_members.expiry is null)
-		     or (org_members.start_date<now()
-			  and org_members.expiry is null)
-		     or (org_members.start_date is null
-			  and org_members.expiry>now())
-		    )
-		)
+    UNION
+	SELECT DISTINCT
+	       members.*,
+	       org_members_history.parent_member_id,
+	       orgs.id           AS org_id,
+	       orgs.name         AS org_name,
+	       member_types.id   AS member_type_id,
+	       member_types.type AS member_type
+	FROM
+	       members
+	  JOIN org_members_history ON members.id                 = org_members_history.member_id
+	  JOIN orgs                ON org_members_history.org_id = orgs.id
+	  JOIN member_types        ON org_members_history.member_type_id = member_types.id AND
+                                      member_types.org_id        = orgs.id 
+	WHERE
+	       org_members_history.start_date    <= now()  AND
+	       member_types.validates_membership  = true
 	;
 
 
+\! echo Creating view has_permission
 create or replace view has_permission AS
 	select
 	  permissions.member_id,
@@ -773,9 +903,12 @@ create or replace view has_permission AS
 		    )
 	;
 	  
+\! echo Creating view org_memberships
 create or replace view org_memberships AS
 	select
+		om.id as membership_id,
 		member_id,
+		parent_member_id,
 		om.org_id,
 		mt.type as type,
 		description,
@@ -795,9 +928,10 @@ create or replace view org_memberships A
 --
 -- Creation of indexes to help with common queries
 --
+\! echo Creating indexes
 CREATE INDEX member_types_type_idx ON member_types(type);
 CREATE INDEX member_type_id_idx ON org_members(member_type_id);
-CREATE INDEX member_email_idx ON members(email);
+-- CREATE INDEX member_email_idx ON members(email);
 CREATE INDEX activity_idx ON activities(activity);
 CREATE INDEX group_idx ON groups(group_name);
 CREATE INDEX org_members_startdate_idx ON org_members(start_date);
@@ -811,6 +945,7 @@ CREATE INDEX org_org_types_type_idx ON o
 -- Create the tokendb table
 -- MyISAM table as often changed, short lived values
 
+\! echo Creating table token
 create table token (
 	token varchar(50),	
 	member_id int,
@@ -824,7 +959,12 @@ create table token (
 --
 
 alter table org_relation_types modify org_id int(11);
-create or replace view vendors as select * from orgs where id in (select org_id from org_org_types where org_type_id=(select id from org_types where name='Vendor'));
+\! echo Creating view vendors
+CREATE or replace VIEW vendors AS
+	SELECT * FROM orgs
+	WHERE id IN (SELECT org_id FROM org_org_types
+		     WHERE org_type_id=(SELECT id FROM org_types
+					WHERE name='Vendor'));
 
 --
 -- Fix bug where some people couldn't log in
Index: sql/create_database_nuug.sql
===================================================================
RCS file: sql/create_database_nuug.sql
diff -N sql/create_database_nuug.sql
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ sql/create_database_nuug.sql	19 Nov 2007 23:15:58 -0000	1.5
@@ -0,0 +1,233 @@
+-- Organization Membership Database
+-- --------------------------------
+--
+-- Create Database Script
+--   New tables for NUUG.
+--
+-- (C)2007 Gaute Nessan
+-- This software is licensed under GNU General Public License
+-- 
+-- This script is tested on MySQL 5.0.
+
+
+
+\! echo Creating table mailing_lists
+CREATE TABLE mailing_lists
+(
+    id				integer		NOT NULL AUTO_INCREMENT,
+    org_id			integer		NOT NULL,
+    name			varchar(50)	NOT NULL,
+    description			varchar(100),
+--
+    CONSTRAINT "products_id_pk"		 PRIMARY KEY (id),
+    CONSTRAINT "mailing_lists_org_id_fk" FOREIGN KEY (org_id) REFERENCES orgs (id)
+);
+
+\! echo Creating table mailing_list_members
+CREATE TABLE mailing_list_members
+(
+    mailing_list_id		integer		NOT NULL,
+    membership_id		integer		NOT NULL,
+    reg_dtm			timestamp	NOT NULL,
+    email			varchar(50)		COMMENT 'override members email address',
+--
+    CONSTRAINT "mailing_list_members_mailing_list_id_fk" FOREIGN KEY (mailing_list_id) REFERENCES mailing_lists (id),
+    CONSTRAINT "mailing_list_members_membership_id_fk"   FOREIGN KEY (membership_id)   REFERENCES org_members (id)
+);
+
+
+\! echo Creating table product_types
+CREATE TABLE product_types
+(
+    id				integer		NOT NULL AUTO_INCREMENT,
+    org_id			integer		NOT NULL,
+    name			varchar(50)	NOT NULL,
+--
+    CONSTRAINT "product_types_id_pk"     PRIMARY KEY (id),
+    CONSTRAINT "product_types_org_id_fk" FOREIGN KEY (org_id) REFERENCES orgs (id)
+);
+
+\! echo Creating table products
+CREATE TABLE products
+(
+    id				integer		NOT NULL AUTO_INCREMENT,
+    org_id			integer		NOT NULL,
+    product_type_id		integer		NOT NULL,
+    active			boolean		NOT NULL,
+    name			varchar(50)	NOT NULL,
+--
+    CONSTRAINT "products_id_pk"     PRIMARY KEY (id),
+    CONSTRAINT "products_org_id_fk" FOREIGN KEY (org_id) REFERENCES orgs (id)
+);
+
+\! echo Creating table price_lists
+CREATE TABLE price_lists
+(
+    id				integer		NOT NULL auto_increment,
+    org_id			integer		NOT NULL,
+    name			varchar(50)	NOT NULL,
+    standard			boolean		NOT NULL,
+    start_dt			date		NOT NULL,
+    end_dt			date,
+--
+    CONSTRAINT "price_lists_id_pk"     PRIMARY KEY (id),
+    CONSTRAINT "price_lists_org_id_fk" FOREIGN KEY (org_id) REFERENCES orgs (id)
+);
+
+\! echo Creating table prices
+CREATE TABLE prices
+(
+    price_list_id		integer		NOT NULL,
+    product_id			integer		NOT NULL,
+    price			decimal(7,2) unsigned NOT NULL,
+--
+    CONSTRAINT "prices_price_list_id_fk" FOREIGN KEY (price_list_id) REFERENCES price_lists (id),
+    CONSTRAINT "prices_product_id_fk"    FOREIGN KEY (product_id)    REFERENCES products (id)
+);
+
+
+\! echo Creating table membership_products
+CREATE TABLE membership_products
+(
+    id				integer		NOT NULL auto_increment,
+    membership_id		integer		NOT NULL,
+    product_id			integer		NOT NULL,
+    order_dt			date		NOT NULL,
+    activation_dt		date,
+    termination_order_dt	date,
+    termination_dt		date,
+    first_invoice_dt		date,
+    invoiced_to_dt		date,
+    paid_to_dt			date,
+    invoice_text		varchar(50),
+    comment_id			integer,
+--
+    CONSTRAINT "membership_products_id_pk"	      PRIMARY KEY (id),
+    CONSTRAINT "membership_products_membership_id_fk" FOREIGN KEY (membership_id) REFERENCES org_members (id),
+    CONSTRAINT "membership_products_product_id_fk"    FOREIGN KEY (product_id)    REFERENCES products (id)
+);
+
+\! echo Creating table membership_data
+CREATE TABLE membership_data
+(
+    membership_id		integer		NOT NULL,
+--
+    usenix			integer,
+    sage			integer,
+    unixlogin			varchar(50),
+--
+    CONSTRAINT "membership_data_membership_id_fk" FOREIGN KEY (membership_id) REFERENCES org_members (id)
+);
+
+
+\! echo Creating table discounts
+CREATE TABLE discounts
+(
+    id				integer		NOT NULL auto_increment,
+    discount			decimal(7,2)	NOT NULL,
+    description			varchar(50)	NOT NULL,
+--
+    CONSTRAINT "discounts_id_pk"     PRIMARY KEY (id)
+);
+
+
+\! echo Creating table invoice_pre_headers
+CREATE TABLE invoice_pre_headers
+(
+    id				integer		NOT NULL auto_increment,
+    membership_id		integer		NOT NULL,
+--
+    status			varchar(10)	NOT NULL  DEFAULT 'New',
+    invoice_pre_dt		date		NOT NULL,
+    invoice_payment_days	integer		NOT NULL  DEFAULT 15	COMMENT 'number of days until pay day',
+    debit_credit		tinyint(1)	NOT NULL  DEFAULT  0	COMMENT '0 = debit, 1 = credit',
+    credited_invoice_id		integer					COMMENT 'Only set when credit',
+    invoice_ref			varchar(50),
+    price_list_id		integer,
+    discount_id			integer,
+--
+    name			varchar(150)	NOT NULL,
+    address1			varchar(50),
+    address2			varchar(50),
+    address3			varchar(50),
+    suburb			varchar(50)	NOT NULL,
+    postcode			varchar(10)	NOT NULL,
+    state			varchar(50),
+    country_code		char(2)		NOT NULL,
+--
+    CONSTRAINT "invoice_pre_hdrs_id_pk"		   PRIMARY KEY (id),
+    CONSTRAINT "invoice_pre_hdrs_membership_id_fk" FOREIGN KEY (membership_id) REFERENCES org_members (id),
+    CONSTRAINT "invoice_pre_hdrs_price_list_id_fk" FOREIGN KEY (price_list_id) REFERENCES price_lists (id),
+    CONSTRAINT "invoice_pre_hdrs_country_code_fk"  FOREIGN KEY (country_code)  REFERENCES countries (code)
+);
+
+\! echo Creating table invoice_pre_lines
+CREATE TABLE invoice_pre_lines
+(
+    id				integer		NOT NULL auto_increment,
+    invoice_pre_id		integer		NOT NULL,
+    start_dt			date,
+    end_dt			date,
+    product_id			integer		NOT NULL,
+    product_text		varchar(100)	NOT NULL,
+    unit_count			integer		NOT NULL,
+    unit_type			varchar(20)	NOT NULL,
+    unit_price			decimal(7,2)	NOT NULL,
+--
+    CONSTRAINT "invoice_pre_lines_id_pk"	     PRIMARY KEY (id),
+    CONSTRAINT "invoice_pre_lines_invoice_pre_id_fk" FOREIGN KEY (invoice_pre_id) REFERENCES invoice_pre_headers (id),
+    CONSTRAINT "invoice_pre_lines_product_id_fk"     FOREIGN KEY (product_id)     REFERENCES products (id)
+);
+
+
+\! echo Creating table invoice_headers
+CREATE TABLE invoice_headers
+(
+    id				integer		NOT NULL auto_increment,
+    membership_id		integer		NOT NULL,
+--
+    invoice_dt			date		NOT NULL,
+    due_dt			date		NOT NULL,
+    amount			decimal(7,2)	NOT NULL,
+    debit_credit		tinyint(1)	NOT NULL  DEFAULT  0	COMMENT '0 = debit, 1 = credit',
+    credited_invoice_id		integer					COMMENT 'Only set when credit',
+    invoice_ref			varchar(50),
+--
+    name			varchar(150)	NOT NULL,
+    address1			varchar(50),
+    address2			varchar(50),
+    address3			varchar(50),
+    suburb			varchar(50)	NOT NULL,
+    postcode			varchar(10)	NOT NULL,
+    state			varchar(50),
+    country_code		char(2)		NOT NULL,
+--
+    CONSTRAINT "invoice_hdrs_id_pk"	       PRIMARY KEY (id),
+    CONSTRAINT "invoice_hdrs_membership_id_fk" FOREIGN KEY (membership_id) REFERENCES org_members (id),
+    CONSTRAINT "invoice_hdrs_country_code_fk"  FOREIGN KEY (country_code)  REFERENCES countries (code)
+);
+
+\! echo Creating table invoice_lines
+CREATE TABLE invoice_lines
+(
+    id				integer		NOT NULL auto_increment,
+    invoice_id			integer		NOT NULL,
+    start_dt			date,
+    end_dt			date,
+    product_text		varchar(100)	NOT NULL,
+    unit_count			integer		NOT NULL,
+    unit_type			varchar(20)	NOT NULL,
+    unit_price			decimal(7,2)	NOT NULL,
+--
+    CONSTRAINT "invoice_lines_id_pk"	     PRIMARY KEY (id),
+    CONSTRAINT "invoice_lines_invoice_id_fk" FOREIGN KEY (invoice_id) REFERENCES invoice_headers (id)
+);
+
+\! echo Creating table invoice_status
+CREATE TABLE invoice_status
+(
+    invoice_id			integer		NOT NULL,
+    payment_received_dt		date,
+--
+    CONSTRAINT "invoice_status_invoice_id_fk" FOREIGN KEY (invoice_id) REFERENCES invoice_headers (id)
+);
Index: sql/create_database_sp.sql
===================================================================
RCS file: sql/create_database_sp.sql
diff -N sql/create_database_sp.sql
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ sql/create_database_sp.sql	15 Nov 2007 22:48:47 -0000	1.4
@@ -0,0 +1,164 @@
+-- Organization Membership Database
+-- --------------------------------
+--
+-- Create Database Script
+--   Stored procedures, functions and triggers.
+--
+-- (C)2007 Gaute Nessan
+-- This software is licensed under GNU General Public License
+-- 
+-- This script is designed for MySQL 5.0.
+
+DELIMITER $$
+
+-- app_user accessors
+-- ------------------
+-- Use app_user_get() to get the name of the current user.
+-- Can be used in queries, stored procedures and triggers.
+\! echo Creating function app_user_get
+CREATE FUNCTION app_user_get()
+returns varchar(50)
+no sql
+begin
+    if @app_user is null then
+        return user();
+    else
+        return @app_user;
+    end if;
+end;
+$$
+
+-- Use app_user_set() to set the name of the current user.
+-- Usage: "call app_user_set('name')".
+\! echo Creating function app_user_set
+CREATE PROCEDURE app_user_set(user_name varchar(50))
+begin
+    set @app_user = user_name;
+end;
+$$
+
+
+-- comments triggers
+-- ------------------
+
+\! echo Creating trigger comments_before_insert
+CREATE TRIGGER comments_before_insert BEFORE INSERT ON comments
+for each row
+begin
+    set new.created_by = app_user_get();
+end;
+$$
+
+
+-- addresses triggers
+-- ------------------
+
+\! echo Creating trigger addresses_before_insert
+CREATE TRIGGER addresses_before_insert BEFORE INSERT ON addresses
+for each row
+begin
+    set new.modified_by = app_user_get();
+end;
+$$
+
+\! echo Creating trigger addresses_before_update
+CREATE TRIGGER addresses_before_update BEFORE UPDATE ON addresses
+for each row
+begin
+    set new.modified_by = app_user_get();
+end;
+$$
+
+\! echo Creating trigger addresses_after_update
+CREATE TRIGGER addresses_after_update AFTER UPDATE ON addresses
+for each row
+begin
+    insert into address_history
+        (id, address1, address2, address3, suburb, postcode,
+	 state, country_code, modified_by, modified_dtm)
+    values
+        (old.id, old.address1, old.address2, old.address3, old.suburb, old.postcode,
+	 old.state, old.country_code, old.modified_by, old.modified_dtm);
+end;
+$$
+
+
+-- members triggers
+-- ----------------
+
+\! echo Creating trigger members_before_insert
+CREATE TRIGGER members_before_insert BEFORE INSERT ON members
+for each row
+begin
+    set new.modified_by = app_user_get();
+    if new.date_entered is null then
+        set new.date_entered = now();
+    end if;
+end;
+$$
+
+\! echo Creating trigger members_before_update
+CREATE TRIGGER members_before_update BEFORE UPDATE ON members
+for each row
+begin
+    set new.modified_by = app_user_get();
+end;
+$$
+
+\! echo Creating trigger members_after_update
+CREATE TRIGGER members_after_update AFTER UPDATE ON members
+for each row
+begin
+    insert into members_old_details
+        (id, date_entered, corporation, contact_person,
+	 first_name, middle_name, last_name, DOB, sex,
+	 address1, address2, address3, suburb, postcode,
+	 state, country_code,
+	 invoice_address, email, phone_home, phone_mobile,
+	 modified_by, modified_dtm)
+    values
+        (old.id, old.date_entered, old.corporation, old.contact_person,
+	 old.first_name, old.middle_name, old.last_name, old.DOB, old.sex,
+	 old.address1, old.address2, old.address3, old.suburb, old.postcode,
+	 old.state, old.country_code,
+	 old.invoice_address, old.email, old.phone_home, old.phone_mobile,
+	 old.modified_by, old.modified_dtm);
+end;
+$$
+
+
+-- org_members triggers
+-- --------------------
+
+\! echo Creating trigger org_members_before_insert
+CREATE TRIGGER org_members_before_insert BEFORE INSERT ON org_members
+for each row
+begin
+    set new.modified_by = app_user_get();
+end;
+$$
+
+\! echo Creating trigger org_members_before_update
+CREATE TRIGGER org_members_before_update BEFORE UPDATE ON org_members
+for each row
+begin
+    set new.modified_by = app_user_get();
+end;
+$$
+
+\! echo Creating trigger org_members_after_update
+CREATE TRIGGER org_members_after_update AFTER UPDATE ON org_members
+for each row
+begin
+    insert into org_members_history
+        (id, parent_member_id, member_id, org_id,
+	 member_type_id,
+	 start_date, expiry, modified_by, modified_dtm)
+    values
+        (old.id, old.parent_member_id, old.member_id, old.org_id,
+	 old.member_type_id,
+	 old.start_date, old.expiry, old.modified_by, old.modified_dtm);
+end;
+$$
+
+DELIMITER ;
Index: sql/init_nuug.sql
===================================================================
RCS file: sql/init_nuug.sql
diff -N sql/init_nuug.sql
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ sql/init_nuug.sql	15 Nov 2007 22:48:47 -0000	1.2
@@ -0,0 +1,22 @@
+-- Rules to initiate the NUUG member database
+-- ------------------------------------------
+
+\! echo Dropping database memberdb
+DROP DATABASE memberdb;
+
+\. mysql_prelude.sql
+\. create_database.sql
+\. create_database_nuug.sql
+\. create_database_sp.sql
+\. initial_data.sql
+\. initial_data_nuug.sql
+\. site_messages.sql
+
+\! echo Grant and revoke
+GRANT ALL ON memberdb.* TO 'memberdb'@'localhost' IDENTIFIED BY 'pants';
+
+-- You must grant privileges on the tables before they can be revoked.
+GRANT  ALL            ON memberdb.comment_groups TO   'memberdb'@'localhost';
+REVOKE UPDATE, DELETE ON memberdb.comment_groups FROM 'memberdb'@'localhost';
+GRANT  ALL            ON memberdb.comments       TO   'memberdb'@'localhost';
+REVOKE UPDATE, DELETE ON memberdb.comments       FROM 'memberdb'@'localhost';
Index: sql/initial_data.sql
===================================================================
RCS file: /home/cvs/nuug-memberdb/sql/initial_data.sql,v
retrieving revision 1.1.1.1
retrieving revision 1.4
diff -u -3 -p -r1.1.1.1 -r1.4
--- sql/initial_data.sql	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ sql/initial_data.sql	15 Nov 2007 22:48:47 -0000	1.4
@@ -1,10 +1,3 @@
---
--- Data for `mail_queue_seq`
---
-
-INSERT INTO mail_queue_seq (id) VALUES (1);
-
-
 -- initial_data.sql
 -- ----------------
 --
@@ -15,23 +8,52 @@ INSERT INTO mail_queue_seq (id) VALUES (
 -- NOTE: You should customise this file for your own
 --       organisation before running it!
 
-insert into orgs (id,name,website,description)
+\! echo Inserting initial data into database
+
+--
+-- Data for `mail_queue_seq`
+--
+
+\! echo Inserting into mail_queue_seq
+INSERT INTO mail_queue_seq (id) VALUES (1);
+
+
+
+\! echo Inserting into countries
+insert into countries values
+    ('AU', 'Australia'),
+    ('DE', 'Tyskland'),
+    ('DK', 'Danmark'),
+    ('FI', 'Finland'),
+    ('GB', 'Storbritannia'),
+    ('IS', 'Island'),
+    ('NO', 'Norge'),
+    ('NL', 'Nederland'),
+    ('SE', 'Sverige');
+
+
+\! echo Inserting into orgs
+insert into orgs (id,name,website,description, country_code)
 	values (
 	  1,
-	  'MemberDB Organisation',
-	  'http://www.flamingspork.com/projects/memberdb/',
-	  'An organisation for all the cool people who run MemberDB'
+	  'Norwegian UNIX User Group',
+	  'http://www.nuug.no/',
+	  'An organisation for all the cool people who uses UNIX',
+	  'NO'
 	);
 
+\! echo Inserting into members
 insert into members (id,
 		first_name,
-		email)
+		email, country_code)
 	values (
 	  1,
 	  'Admin',
-	  'admin@localhost'
+	  'admin@localhost',
+	  'NO'
 	);
 
+\! echo Inserting into member_types
 insert into member_types (id,org_id,
 			type,
 			description,
@@ -42,7 +64,49 @@ insert into member_types (id,org_id,
 	  'Ordinary Member',
 	  true,false
 	);
+insert into member_types (id, org_id,
+			type,
+			description,
+			validates_membership, revokes_membership)
+	values (
+	  2, 1,
+	  'Pending',
+	  'Has applied for membership but has not yet received it.',
+	  false, false
+	);
+insert into member_types (id, org_id,
+			type,
+			description,
+			validates_membership, revokes_membership)
+	values (
+	  3, 1,
+	  'Applied',
+	  'Has entered details but not confirmed them.',
+	  false, false
+	);
+insert into member_types (id, org_id,
+			type,
+			description,
+			validates_membership, revokes_membership)
+	values (
+	  4, 1,
+	  'Denied',
+	  'Has been denied membership.',
+	  false, true
+	);
+insert into member_types (id, org_id,
+			type,
+			description,
+			validates_membership, revokes_membership)
+	values (
+	  5, 1,
+	  'Duplicate',
+	  'Has more than one entry, so this marks the duplicate entry.',
+	  false, true
+	);
+
 
+\! echo Inserting into org_members
 insert into org_members (member_id,org_id,member_type_id,
 			 start_date,expiry)
 	values (
@@ -50,7 +114,8 @@ insert into org_members (member_id,org_i
 	  now(),NULL
 	);
 
-insert into passwd (member_id,salt,password) values (1,'5',MD5('5pants'));
+\! echo Inserting into passwd
+insert into passwd (member_id,salt,password) values (1,'5',MD5('5spandex'));
 
 -- create_activities.sql
 -- ---------------------
@@ -61,6 +126,7 @@ insert into passwd (member_id,salt,passw
 --
 -- (C)2004 Stewart Smith
 
+\! echo Inserting into activities
 insert into activities (activity,description) values
 	('admin',
 	'Can administrate anything. Equivilent to root.');
@@ -94,4 +160,5 @@ insert into activities (activity,descrip
 -- Grant permission to the admin user
 --
 
+\! echo Inserting into permissions
 insert into permissions (member_id,org_id,activity_id,can_do,can_grant,start_datetime,end_datetime) values (1,1,(select id from activities where activity='admin'),true,true,now(),null);
Index: sql/initial_data_nuug.sql
===================================================================
RCS file: sql/initial_data_nuug.sql
diff -N sql/initial_data_nuug.sql
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ sql/initial_data_nuug.sql	19 Nov 2007 23:15:58 -0000	1.3
@@ -0,0 +1,62 @@
+
+\! echo Inserting into mailing_lists
+INSERT INTO mailing_lists (id, org_id, name, description) VALUES
+ ( 1, 1, 'medlemmer', 'Kun for medlemmer'),
+ ( 2, 1, 'aktive',    'For aktive medlemmer og ikke-medlemmer');
+
+
+\! echo Inserting into product_types
+INSERT INTO product_types (id, org_id, name) VALUES
+ (1, 1, 'other'),
+ (2, 1, 'membership'),
+ (3, 1, 'additional membership');
+
+
+\! echo Inserting into products
+INSERT INTO products (id, org_id, product_type_id, active, name) VALUES
+ ( 1, 1, 2, true, 'FIRMAMEDLEM'),
+ ( 2, 1, 2, true, 'Medlem under bedrift'),
+ ( 3, 1, 2, true, 'Personlig medlem'),
+ ( 6, 1, 2, true, 'Studenter'),
+ (11, 1, 2, true, 'Æresmedlem'),
+ (12, 1, 2, true, 'Personlig medlem uten USENIX'),
+ (13, 1, 2, true, 'Studentmedlem uten USENIX'),
+ (21, 1, 3, true, 'SAGE tilleggsmedlemskap'),
+ (22, 1, 3, true, 'SAGE tilleggsmedlemskap for studenter');
+
+
+\! echo Inserting into price_lists
+INSERT INTO price_lists (id, org_id, name, standard, start_dt, end_dt)
+VALUES
+ (1,  1, 'NUUG-priser', true, '2007-01-01', '2008-01-01'),
+ (2,  1, 'NUUG-priser', true, '2008-01-01', NULL);
+
+
+\! echo Inserting into prices
+INSERT INTO prices (price_list_id, product_id, price) VALUES
+-- 2007-01-01 - 2008-01-01
+ (1,  1, 4000.00),
+ (1,  2, 1060.00),
+ (1,  3, 1110.00),
+ (1,  6,  260.00),
+ (1, 11,    0.00),
+ (1, 21,  280.00),
+ (1, 22,  175.00),
+-- 2008-01-01 - 2009-01-01
+ (2,  1, 4000.00),
+ (2,  2, 1060.00),
+ (2,  3, 1110.00),
+ (2,  6,  260.00),
+ (2, 11,    0.00),
+ (2, 12,  360.00),
+ (2, 13,   50.00),
+ (2, 21,  280.00),
+ (2, 22,  175.00);
+
+
+\! echo Inserting into discounts
+INSERT INTO discounts (id, discount, description) VALUES
+ (1,  25.0,  'Rabatt ved innmelding mellom 1.12 og 28.02'),
+ (2,  50.0,  'Rabatt ved innmelding mellom 1.03 og 31.05'),
+ (3,  75.0,  'Rabatt ved innmelding mellom 1.06 og 31.08'),
+ (4, 100.0,  'Rabatt ved innmelding mellom 1.09 og 30.11');
Index: sql/mysql_prelude.sql
===================================================================
RCS file: /home/cvs/nuug-memberdb/sql/mysql_prelude.sql,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -u -3 -p -r1.1.1.1 -r1.3
--- sql/mysql_prelude.sql	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ sql/mysql_prelude.sql	15 Nov 2007 22:48:47 -0000	1.3
@@ -1,4 +1,14 @@
-set SQL_MODE=ansi;
+warnings;
+\! echo Setting various options
+set sql_mode             = 'traditional,ansi_quotes';
+set character_set_server = utf8;
+set collation_server     = utf8_danish_ci;
+
+\! echo Create database memberdb
 create database "memberdb";
+
+\! echo Change to database memberdb
 use "memberdb";
+
+\! echo Setting default storage engine to innodb
 set storage_engine=innodb;
Index: sql/site_messages.sql
===================================================================
RCS file: /home/cvs/nuug-memberdb/sql/site_messages.sql,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -3 -p -r1.1.1.1 -r1.2
--- sql/site_messages.sql	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ sql/site_messages.sql	15 Nov 2007 22:48:47 -0000	1.2
@@ -1,3 +1,5 @@
+\! echo Inserting into site_messages
+
 INSERT INTO site_messages (name, mail_to, mail_cc, mail_subject, message) VALUES ('signup/confirm-application', '%MEMBER_EMAIL%', '%MEMBERSHIP_ADMIN%', 'Application for %ORG_NAME% Membership
 ', 'Thank you for applying to become a member of %ORG_NAME%. Attached
 are the details you entered when you signed up.
Index: tools/dnd-dump-import
===================================================================
RCS file: tools/dnd-dump-import
diff -N tools/dnd-dump-import
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tools/dnd-dump-import	11 Mar 2007 09:10:09 -0000	1.2
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+basedir=$(dirname $0)
+
+psql -f $basedir/dnd-tablecreate.sql pere
+$basedir/dnd-import-table ZUsrMedlemsstatus ZUsrMedlemskapStatus.csv
+$basedir/dnd-import-table customer Forbindelser.csv
+$basedir/dnd-import-table CustomerDeliveryAddresses Leveringsadresser.csv
+$basedir/dnd-import-table ZUsrArbeidsgiver ZUsrArbeidsgiver.csv
+$basedir/dnd-import-table ZUsrMedlemskap ZUsrMedlemskap.csv
Index: tools/dnd-import-table
===================================================================
RCS file: tools/dnd-import-table
diff -N tools/dnd-import-table
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tools/dnd-import-table	10 Mar 2007 22:47:11 -0000	1.1
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+#
+# Author: Petter Reinholdtsen
+# Date:   2007-03-10
+#
+# Import semicolon separated CSV file into SQL table.  Useful for
+# importing the NUUG member database dump from the DND database.
+
+use warnings;
+use strict;
+
+use DBI;
+use Text::CSV_XS;
+
+my $tablename = shift;
+my $filename = shift;
+
+my $dbdriver = 'Pg'; # mysql or Pg
+my $dbname   = 'pere';
+my $dbhost   = '';
+my $dbuser   = 'pere';
+my $dbpasswd = '';
+
+my $dbh = DBI->connect("dbi:$dbdriver:dbname=$dbname",
+                       $dbuser, $dbpasswd) # ;host=$dbhost
+    || die "Unable to connect to DB: $DBI::errstr";
+
+open(FILE, "cat $filename|iconv -t utf8|") || die "Unable to read from $filename";
+my $csv = Text::CSV_XS->new({'binary' => 1, sep_char => ";"});
+my $line = <FILE>;
+my $status = $csv->parse($line);
+die unless $status;
+
+my @field_names = $csv->fields();
+my $sql = "INSERT INTO $tablename (";
+$sql .= join(",", @field_names);
+$sql .= ") VALUES (";
+$sql .= join(",", map({"?"} @field_names));
+$sql .= ")";
+print "S: $sql\n";
+my $sth = $dbh->prepare($sql);
+while (<FILE>) {
+    chomp;
+    s/\r//;
+    if ($status = $csv->parse($_) ) {
+        my @field_values = $csv->fields();
+        $sth->execute(map({ $_ ? $_ : undef } @field_values));
+    }
+}
+$dbh->disconnect();
+
+exit 0;
Index: tools/dnd-tablecreate.sql
===================================================================
RCS file: tools/dnd-tablecreate.sql
diff -N tools/dnd-tablecreate.sql
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tools/dnd-tablecreate.sql	11 Mar 2007 09:10:42 -0000	1.2
@@ -0,0 +1,254 @@
+DROP VIEW dnd_memberexport;
+
+DROP TABLE ZUsrMedlemskap;
+DROP TABLE ZUsrArbeidsgiver;
+DROP TABLE CustomerDeliveryAddresses;
+DROP TABLE Customer;
+DROP TABLE ZUsrMedlemsstatus;
+
+CREATE TABLE ZUsrMedlemsstatus ( -- ZUsrMedlemskapStatus.csv
+	ZUsrMedlemstatusNo integer PRIMARY KEY NOT NULL,
+	ZUsrMedlemstatus varchar,
+	ZUsrReskRegion varchar,
+	ZUsrReskAGiver varchar,
+	ZUsrReskASted varchar,
+	ZUsrReskOrganisasjon varchar,
+	ZUsrFakturertPeriode varchar
+);
+
+CREATE TABLE Customer ( -- forbindelser.csv
+	CustomerNo integer PRIMARY KEY NOT NULL,
+	Name varchar,
+	CustomerGrpNo integer,
+	BusinessNo integer,
+	WareHouseNo integer,
+	CustomerTypeNo integer,
+	SortName varchar,
+	CurrencyNo integer,
+	PostAccount varchar,
+	BankAccount varchar,
+	LockedYesNo integer,
+	Balance varchar,
+	DistrictNo integer,
+	PostOffice varchar,
+	AutoGiroAccount varchar,
+	ChainNo integer,
+	CreditLimit varchar,
+	Telefax varchar,
+	Telephone varchar,
+	EmailAddress varchar,
+	LastMovementDate varchar,
+	WWWAddress varchar,
+	FactCustomerNo integer,
+	CompanyNo integer,
+	ChainLeaderYesNo integer,
+	InActiveYesNo integer,
+	MainContactNo integer,
+	ResponsibleEmployeeNo integer,
+	PostCode varchar,
+	CountryNo integer,
+	NoOfEmployees varchar,
+	DeliveryAddressNo integer,
+	Address1 varchar,
+	Address2 varchar,
+	Address3 varchar,
+	TotalDiscountYear varchar,
+	TotalDiscountPeriod varchar,
+	TotalSaleYear varchar,
+	TotalSalePeriod varchar,
+	TotalLoan varchar,
+	TotalOrder varchar,
+	CustomerCat2No integer,
+	CustomerCat1No integer,
+	PhoneNoEndRange varchar,
+	PhoneNoStartRange varchar,
+	VBusActorNo integer,
+	Address4 varchar,
+	DivisionNo integer,
+	MainCustomerNo integer,
+	MobileTelephone varchar,
+	MainCustomerYesNo integer,
+	ConsentToReceiveEmail varchar,
+	ConsentToReceiveTelephone varchar,
+	ConsentToReceiveLetter varchar,
+	ConsentToReceiveSMS varchar,
+	InvoiceCustomerNo integer,
+	RefusedToBeContacted varchar,
+	ZUsrSkiftLokalavdeling varchar,
+	ZUsrFornavn varchar,
+	ZUsrEtternavn varchar,
+	ZUsrFodselsdato varchar,
+	ZUsrPersonnr varchar,
+	ZUsrPartnertall varchar,
+	ZUsrPartnernavn varchar,
+	ZUsrUtdannelseAar varchar,
+	ZUsrGodkjent varchar,
+	ZUsrGodkjentDato varchar,
+	ZUsrTelefonPrivat varchar,
+	ZUsrDodsaar varchar,
+	ZUsrIDKort varchar,
+	ZUsrIDNummer varchar,
+	ZUsrGmlMedlemsnummer varchar,
+	ZUsrMidlAdresse1 varchar,
+	ZUsrMidlAdresse2 varchar,
+	ZUsrMidlAdresse3 varchar,
+	ZUsrMidlAdresse4 varchar,
+	ZUsrMidlPostnr varchar,
+	ZUsrMidlPoststed varchar,
+	ZUsrMidlTilDato varchar,
+	ZUsrSkiftMedlemstatus varchar,
+	ZUsrRegionJN varchar,
+	ZUsrArbeidsgiverJN varchar,
+	ZUsrArbeidsstedJN varchar,
+	ZUsrProsentArbeidsforhold1 varchar,
+	ZUsrProsentArbeidsforhold2 varchar,
+	ZUsrTillitsvalgt varchar,
+	ZUsrVervOrganisasjonJN varchar,
+	ZUsrAvtalegiro varchar,
+	ZUsrAvtalegiroKID varchar,
+	ZUsrAntTidskrift varchar,
+	ZUsrForsteGangInnmeldt varchar,
+	ZUsrInnmeldtDato timestamp,
+	ZUsrUtmeldtDato timestamp,
+	ZUsrMidlFraDato varchar,
+	ZUsreFaktura varchar,
+	ZUsrLonnstrekk varchar,
+	ZUsrGodtarReklame varchar,
+	ZUsrAntFaktMedl varchar,
+	ZUsrsIDNummer varchar,
+	ZUsrReferanse varchar,
+	ZUsrEksamensdato varchar,
+	ZUsrArbeidSkiftDato varchar,
+	ZUsrHistorikkArbForhJN varchar,
+	ZUsrAdresseUkjent varchar,
+	ZUsrEPostUkjent varchar,
+	ZUsrabcPassord varchar,
+	ZUsrTurnusJN1 varchar,
+	ZUsrTurnusJN2 varchar,
+	ZUsrKompetansepoeng varchar,
+	ZUsrabcBrukernavn varchar,
+	ZUsrezBilde varchar,
+	ZUsrOrgCustomerNo integer,
+	ZUsrNyLokalavdelingNo integer,
+	ZUsrKjonnNo integer,
+	ZUsrUtdannelsestedNo integer,
+	ZUsrAkademiskTittelNo integer,
+	ZUsrMidlLandNo integer,
+	ZUsrMedlemstatusNo integer,
+	ZUsrNyMedlemstatusNo integer,
+	ZUsrRegion1No integer,
+	ZUsrArbeidsgiver1No integer,
+	ZUsrArbeidsted1No integer,
+	ZUsrArbeidsektor1No integer,
+	ZUsrVervRegion1No integer,
+	ZUsrVervArbeidsgiver1No integer,
+	ZUsrVervArbeidsted1No integer,
+	ZUsrStilling1No integer,
+	ZUsrRegion2No integer,
+	ZUsrArbeidsgiver2No integer,
+	ZUsrArbeidsted2No integer,
+	ZUsrArbeidsektor2No integer,
+	ZUsrVervRegion2No integer,
+	ZUsrVervArbeidsgiver2No integer,
+	ZUsrVervArbeidsted2No integer,
+	ZUsrStilling2No integer,
+	ZUsrPreferertAdresseNo integer,
+	ZUsrLokalavdelingNo integer,
+	ZUsrUtmeldtArsakNo integer,
+	ZUsrMedlemskategoriNo integer,
+	ZUsrReskontrofores varchar,
+	ZUsrAdresseresTil varchar,
+	ZUsrTerminNo integer,
+
+	CONSTRAINT "Customer_ZUsrMedlemstatusNo" FOREIGN KEY (ZUsrMedlemstatusNo) REFERENCES ZUsrMedlemsstatus(ZUsrMedlemstatusNo)
+);
+
+CREATE TABLE CustomerDeliveryAddresses ( -- Leveringsadresser.csv
+	DeliveryAddressNo integer,
+	DeliveryName varchar,
+	CustomerNo integer,
+	ContactNo integer,
+	DeliveryAddress1 varchar,
+	DeliveryAddress2 varchar,
+	DeliveryAddress3 varchar,
+	DeliveryAddress4 varchar,
+	DeliveryPostCode varchar,
+	DeliveryPostOffice varchar,
+	DeliveryCountryNo integer,
+	VBusActNo integer
+-- Disabled as the table contain references to non-existing customers
+--	CONSTRAINT "CustomerDeliveryAddresses_CustomerNo" FOREIGN KEY (CustomerNo) REFERENCES Customer(CustomerNo)
+);
+
+CREATE TABLE ZUsrArbeidsgiver ( -- ZUsrArbeidsgiver.csv
+	ZUsrArbeidsgiverNo integer PRIMARY KEY NOT NULL,
+	ZUsrArbeidsgiver varchar,
+	ZUsrAdresse1 varchar,
+	ZUsrAdresse2 varchar,
+	ZUsrAdresse3 varchar,
+	ZUsrAdresse4 varchar,
+	ZUsrPostnr varchar,
+	ZUsrPoststed varchar,
+	ZUsrTelefon varchar,
+	ZUsrTelefaks varchar,
+	ZUsrEmail varchar,
+	ZUsrYrkesaktivJN varchar,
+	ZUsrLandNo integer,
+	ZUsrArbeidssektorNo integer,
+	ZUsrArbeidsregionNo integer,
+	ZUsrCustomerNo integer
+-- Disabled as the table contain references to non-existing customers
+--	CONSTRAINT "ZUsrArbeidsgiver_ZUsrCustomerNo" FOREIGN KEY (ZUsrCustomerNo) REFERENCES Customer(CustomerNo)
+);
+
+CREATE TABLE ZUsrMedlemskap ( -- ZUsrMedlemskap.csv
+	ZUsrMedlemskapNo integer,
+	ZUsrInnmeldingJN varchar,
+	ZUsrStartdato timestamp,
+	ZUsrEngangsmoderasjon varchar,
+	ZUsrFakturertPeriode varchar,
+	ZUsrSistFakturert varchar,
+	ZUsrNyMedlemstatusDato varchar,
+	ZUsrUtmeldingJN varchar,
+	ZUsrSluttdato varchar,
+	ZUsrFakturert_tom varchar,
+	ZUsrReferanse varchar,
+	ZUsrCustomerNo integer,
+	ZUsrOrganisasjonNo integer,
+	ZUsrMedlemstatusNo integer,
+	ZUsrTerminNo integer,
+	ZUsrFakturaregelNo integer,
+	ZUsrAdressatNo integer,
+	ZUsrReskontroNo integer,
+	ZUsrNyMedlemstatusNo integer,
+	ZUsrUtmeldingsArsakNo integer,
+-- Disabled as the table contain references to non-existing customers
+--	CONSTRAINT "ZUsrMedlemskap_ZUsrCustomerNo" FOREIGN KEY (ZUsrCustomerNo) REFERENCES Customer(CustomerNo)
+	CONSTRAINT "ZUsrMedlemskap_ZUsrMedlemstatusNo" FOREIGN KEY (ZUsrMedlemstatusNo) REFERENCES ZUsrMedlemsstatus(ZUsrMedlemstatusNo)
+);
+
+-- Based on the info in medlemsregister-200505-projplan-tilgang.pdf
+CREATE or replace view dnd_memberexport AS
+	SELECT Customer.CustomerNo AS CustomerNo,
+	       Customer.Name AS Name,
+	       Customer.EmailAddress AS EmailAddress,
+	       Customer.ZUsrIDNummer AS USENIX,
+	       ZUsrMedlemsstatus.ZUsrMedlemstatus AS ZUsrMedlemstatus,
+	       Customer.MobileTelephone AS MobileTelephone,
+	       Customer.Telephone AS Telephone,
+	       ZUsrMedlemsstatus.ZUsrMedlemstatusNo AS ZUsrMedlemstatusNo,
+	       ZUsrArbeidsgiver.ZUsrArbeidsgiverNo AS ZUsrArbeidsgiverNo,
+	       ZUsrArbeidsgiver.ZUsrArbeidsgiver AS ZUsrArbeidsgiver,
+	       Customer.Address1 AS Address1,
+	       Customer.Address2 AS Address2,
+	       Customer.Address3 AS Address3,
+	       Customer.PostCode AS PostCode,
+	       Customer.PostOffice AS PostOffice,
+	       Customer.ZUsrInnmeldtDato AS ZUsrInnmeldtDato,
+	       Customer.ZUsrUtmeldtDato AS ZUsrUtmeldtDato
+	FROM   Customer LEFT OUTER JOIN
+	       ZUsrMedlemsstatus ON Customer.ZUsrMedlemstatusNo =
+	              ZUsrMedlemsstatus.ZUsrMedlemstatusNo
+	       LEFT OUTER JOIN
+	       ZUsrArbeidsgiver ON Customer.ZUsrArbeidsgiver1No =
+	              ZUsrArbeidsgiver.ZUsrArbeidsgiverNo;
Index: tools/medlemsliste-sql-import-memberdb
===================================================================
RCS file: tools/medlemsliste-sql-import-memberdb
diff -N tools/medlemsliste-sql-import-memberdb
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tools/medlemsliste-sql-import-memberdb	18 Nov 2007 21:57:16 -0000	1.10
@@ -0,0 +1,307 @@
+#!/usr/bin/perl
+#
+# Author: Petter Reinholdtsen <pere@hungry.com>
+# Date:   2002-11-20
+# Adapted for MemberDb by Gaute Nessan <gaute@nessan.no>
+# Date:   2007-02-17
+#
+# Import the CSV-formated member database for NUUG into a MemberDB tables.
+#
+# Dependencies: DBI, DBD-mysql/Pg, libtext-csv-perl
+
+use strict;
+use warnings;
+
+BEGIN {
+    # Add script location to include path
+    my @p = split(m%/%, $0); pop @p; push @INC, join("/", @p);
+    # Add location of medlemsliste.pm
+    push @INC, "/home/pere/src/nuugcvs/nuug/tools";
+    push @INC, "/home/gaute/src/nuug/tools";
+}
+
+use DBI;
+use medlemsliste;
+
+use vars qw($dbdriver $dbname $dbhost $dbuser $dbpasswd $filename $dbh);
+
+$dbdriver = 'mysql'; # mysql or Pg
+$dbname   = 'memberdb';
+$dbhost   = 'localhost';
+$dbuser   = 'memberdb';
+$dbpasswd = 'pants';
+
+#$filename = 'medlemsliste-test.csv';
+$filename = $ARGV[0] || 'medlemsliste.csv';
+
+my $dropped = 0;
+my $added   = 0;
+
+my $sql_timetype;
+if ('Pg' eq $dbdriver) {
+    $sql_timetype = 'timestamp with time zone';
+} elsif ('mysql' eq $dbdriver) {
+    $sql_timetype = 'timestamp';
+} else {
+    die "Unhandled database driver $dbdriver";
+}
+
+
+$dbh = DBI->connect("dbi:$dbdriver:dbname=$dbname", $dbuser, $dbpasswd) # ;host=$dbhost
+    || die "Unable to connect to DB: $DBI::errstr";
+
+if ('Pg' eq $dbdriver) { # How is this done with other databases?
+    $dbh->do("SET CLIENT_ENCODING TO 'ISO_8859_1'");
+} elsif ('mysql' eq $dbdriver) {
+    $dbh->do("SET NAMES      'latin1'");
+    $dbh->do("SET SQL_MODE = 'TRADITIONAL'");
+    $dbh->do("CALL app_user_set('initial import')");
+}
+
+my %country_codes;
+
+load_countries($dbh);
+
+load_memberslist($filename, \&process_companies, $dbh);
+load_memberslist($filename, \&process_persons, $dbh);
+
+$dbh->disconnect();
+
+print "\nImported $added users, skipped $dropped users\n";
+
+exit 0;
+
+
+sub load_countries {
+    my ($dbh) = @_;
+
+    my ($sql, $sth);
+    $sql = <<EoQ;
+        SELECT code, name
+	FROM   countries
+EoQ
+
+    $sth = $dbh->prepare($sql);
+    if (! $sth->execute)
+    {
+	sql_error($dbh, $sql);
+	return 0;
+    }
+
+    my $n = 0;
+
+    while (my @row = $sth->fetchrow_array)
+    {
+	$country_codes{$row[1]} = $row[0];
+	$n++;
+    }
+
+    print "\n$n countries loaded\n";
+
+    return 1;
+}
+
+sub process_companies {
+    my ($memberinforef, $dbh) = @_;
+    return unless $memberinforef->{'ErArbeidsgiver'};
+    if (add_member($dbh, %{$memberinforef})) {
+	    $added++;
+	} else {
+	    $dropped++;
+    }
+}
+
+sub process_persons {
+    my ($memberinforef, $dbh) = @_;
+    return if $memberinforef->{'ErArbeidsgiver'};
+    if (add_member($dbh, %{$memberinforef})) {
+	    $added++;
+	} else {
+	    $dropped++;
+    }
+}
+
+sub add_member_cancel {
+    my ($dbh, $table, $sql, %v) = @_;
+    print "Problem importing into $table:\n  ",
+    join("\n  ", map { "$_\t=> " . (defined $v{$_} ? "'$v{$_}'" : "''") } keys(%v)), "\n";
+    print "$sql\n";
+    $dbh->do('ROLLBACK');
+    $dbh->do('SET AUTOCOMMIT = 1');
+}
+
+# export file field names:
+#   CustomerNo Name EmailAddress USENIX SAGE
+#   ZUsrArbeidsgiverNo ZUsrArbeidsgiver Kontaktperson
+#   ZUsrMedlemstatus ZUsrMedlemstatusNo
+#   MobileTelephone Telephone
+#   Address1 Address2 Address3 PostCode PostOffice Country
+#   unixlogin Informasjon ZUsrFakturertPeriode ZUsrFakturaReferanse ErArbeidsgiver
+#   ZUsrInnmeldtDato ZUsrUtmeldtDato ZBetaltTil
+# Extra from medlemsliste.pm
+#   SAGE
+
+sub add_member
+{
+    my ($dbh, %v) = @_;
+
+    # Convert empty values to null/undef
+    # @f = map { $_ ? $_ : undef } @f;
+
+    my ($first_name, $middle_name, $last_name, $parent_member_id, $address2,
+	$product_id, $membership_id, $country_code, $sage_product_id);
+
+    unless ($v{'Name'})
+    {
+	print "Member without a name:\n  ",
+	    join("\n  ", map { "$_\t=> " . (defined $v{$_} ? "'$v{$_}'" : "''") } keys(%v)), "\n";
+	return 1;
+    }
+
+    if ($v{'ErArbeidsgiver'}) {
+	$first_name       = $v{'Name'};
+	$middle_name      = undef;
+	$last_name        = undef;
+    } else {
+	my @names = split(/ +/, $v{'Name'});
+	$last_name        = shift @names;
+	$first_name       = shift @names;
+	$middle_name      = join(' ', @names);
+    }
+
+    $parent_member_id = $v{'ZUsrArbeidsgiverNo'} ? $v{'ZUsrArbeidsgiverNo'} : undef;
+    $product_id = $v{'ZUsrMedlemstatusNo'};
+
+    if ($v{'ZUsrMedlemstatus'}) {
+        # Only clear this field for members, as it will be stored in
+        # the expired field, and a NULL value there will make
+        # non-members appear to be members.  Not the best way to do it, I guess.
+	if ($v{'ZUsrUtmeldtDato'} eq '1970-01-01 00:00:00') {
+	    $v{'ZUsrUtmeldtDato'} = undef;
+	}
+    }
+
+    # Make sure address fields are filled from the start
+#    ($v{'Address1'}, $v{'Address2'}, $v{'Address3'}) =
+#        grep({ $_ } ($v{'Address1'}, $v{'Address2'}, $v{'Address3'}));
+
+    # Replace country names with country codes
+    unless (exists $country_codes{$v{'Country'}}) {
+	print 'Kunde ', $v{'CustomerNo'}, ': fant ikke landskoden for ', $v{'Country'}, "\n";
+	return 0;
+    }
+    $country_code = $country_codes{$v{'Country'}};
+
+    $v{'ZUsrFakturertPeriode'} = undef  unless $v{'ZUsrFakturertPeriode'};
+
+
+    $dbh->do('SET AUTOCOMMIT = 0');
+    $dbh->do('START TRANSACTION');
+
+    my ($sql, $sth);
+
+    $sql = <<EoQ;
+        INSERT INTO members
+            (id, date_entered, corporation, contact_person,
+	     first_name, middle_name, last_name,
+	     address1, address2, address3, suburb, postcode, country_code,
+	     email, phone_home, phone_mobile)
+	VALUES
+	    (?, ?, ?, ?,  ?, ?, ?,  ?, ?, ?, ?, ?, ?,  ?, ?, ?)
+EoQ
+    $sth = $dbh->prepare($sql);
+    if ( ! $sth->execute($v{'CustomerNo'}, $v{'ZUsrInnmeldtDato'}, $v{'ErArbeidsgiver'},
+			 $v{'Kontaktperson'},
+			 $first_name, $middle_name, $last_name,
+			 $v{'Address1'}, $v{'Address2'}, $v{'Address3'},
+			 $v{'PostOffice'}, $v{'PostCode'}, $country_code,
+			 $v{'EmailAddress'}, $v{'Telephone'}, $v{'MobileTelephone'}) ) {
+	add_member_cancel($dbh, "members", $sql, %v);
+	return 0;
+    }
+
+    $sql = <<EoQ;
+        INSERT INTO org_members
+            (parent_member_id, member_id, org_id, member_type_id, start_date, expiry,
+	     invoice_ref, last_invoiced_to_dt, last_paid_to_dt)
+	VALUES
+	    (?, ?, 1, 1, ?, ?,  ?, ?, ?)
+EoQ
+    $sth = $dbh->prepare($sql);
+    if ( ! $sth->execute($parent_member_id, $v{'CustomerNo'},
+			 $v{'ZUsrInnmeldtDato'}, $v{'ZUsrUtmeldtDato'}, $v{'ZUsrFakturaReferanse'},
+			 $v{'ZUsrFakturertPeriode'}, $v{'ZBetaltTil'}) ) {
+	add_member_cancel($dbh, "org_members", $sql, %v);
+	return 0;
+    }
+
+    $membership_id = $dbh->last_insert_id(undef, undef, undef, undef);
+
+    if ($product_id)
+    {
+	$sql = <<EoQ;
+	    INSERT INTO membership_products
+                (membership_id, product_id, order_dt, invoiced_to_dt, paid_to_dt)
+	    VALUES
+	        (?, ?, ?, ?, ?)
+EoQ
+        $sth = $dbh->prepare($sql);
+	if ( ! $sth->execute($membership_id, $product_id, $v{'ZUsrInnmeldtDato'},
+			     $v{'ZUsrFakturertPeriode'}, $v{'ZBetaltTil'}) ) {
+	    add_member_cancel($dbh, "membership_products", $sql, %v);
+	    return 0;
+	}
+    }
+
+    if ($v{'SAGE'})
+    {
+	$sage_product_id = $v{'ZUsrMedlemstatus'} =~ /^Student/ ? 22 : 21;
+
+	$sql = <<EoQ;
+	    INSERT INTO membership_products
+                (membership_id, product_id, order_dt, invoiced_to_dt, paid_to_dt)
+	    VALUES
+	        (?, ?, ?, ?, ?)
+EoQ
+        $sth = $dbh->prepare($sql);
+	if ( ! $sth->execute($membership_id, $sage_product_id, $v{'ZUsrInnmeldtDato'},
+			     $v{'ZUsrFakturertPeriode'}, $v{'ZBetaltTil'}) ) {
+	    add_member_cancel($dbh, "membership_products", $sql, %v);
+	    return 0;
+	}
+    }
+
+    $sql = <<EoQ;
+        INSERT INTO membership_data
+            (membership_id, usenix, sage, unixlogin)
+	VALUES
+	    (?, ?, ?, ?)
+EoQ
+    $sth = $dbh->prepare($sql);
+    if ( ! $sth->execute($membership_id, $v{'USENIX'},  $v{'SAGE'}, $v{'unixlogin'}) ) {
+	add_member_cancel($dbh, "membership_data", $sql, %v);
+	return 0;
+    }
+
+    if ($v{'Informasjon'})
+    {
+	$sql = <<EoQ;
+            INSERT INTO mailing_list_members
+                (mailing_list_id, membership_id)
+	    VALUES
+	        (1, ?)
+EoQ
+	$sth = $dbh->prepare($sql);
+        if ( ! $sth->execute($membership_id) ) {
+	    add_member_cancel($dbh, 'mailing_list_members', $sql, %v);
+	    return 0;
+	}
+    }
+
+    print '.';
+
+    $dbh->do('COMMIT');
+    $dbh->do('SET AUTOCOMMIT = 1');
+
+    return 1;
+}
Index: tools/memberdb-export
===================================================================
RCS file: tools/memberdb-export
diff -N tools/memberdb-export
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tools/memberdb-export	18 Nov 2007 21:57:16 -0000	1.2
@@ -0,0 +1,141 @@
+#!/usr/bin/perl
+#
+# Author: Gaute Nessan <gaute@nessan.no>
+# Date:   2007-10-15
+#
+# Export MemberDB tables into CSV-formatted member database for NUUG.
+#
+# Dependencies: DBI, DBD-mysql/Pg
+
+use strict;
+use warnings;
+
+use DBI;
+
+use vars qw($dbdriver $dbname $dbhost $dbuser $dbpasswd $dbh);
+
+$dbdriver = 'mysql'; # mysql or Pg
+$dbname   = 'memberdb';
+$dbhost   = 'localhost';
+$dbuser   = 'memberdb';
+$dbpasswd = 'pants';
+
+
+my $dropped = 0;
+my $printed = 0;
+
+my $sql_timetype;
+if ('Pg' eq $dbdriver) {
+    $sql_timetype = 'timestamp with time zone';
+} elsif ('mysql' eq $dbdriver) {
+    $sql_timetype = 'timestamp';
+} else {
+    die "Unhandled database driver $dbdriver";
+}
+
+
+$dbh = DBI->connect("dbi:$dbdriver:dbname=$dbname", $dbuser, $dbpasswd) # ;host=$dbhost
+    || die "Unable to connect to DB: $DBI::errstr";
+
+if ('Pg' eq $dbdriver) { # How is this done with other databases?
+    $dbh->do("SET CLIENT_ENCODING TO 'ISO_8859_1'");
+} elsif ('mysql' eq $dbdriver) {
+    $dbh->do("SET NAMES      'latin1'");
+    $dbh->do("SET SQL_MODE = 'TRADITIONAL'");
+    $dbh->do("CALL app_user_set('initial import')");
+}
+
+export_memberdb($dbh);
+
+$dbh->disconnect();
+
+print STDERR "Exported $printed users, skipped $dropped users.\n";
+
+exit 0;
+
+
+sub export_memberdb
+{
+    my ($dbh) = @_;
+
+    my ($sql, $sth);
+    $sql = <<EoQ;
+        SELECT
+               me.id, me.last_name, me.first_name, me.middle_name, me.email, md.usenix, md.sage,
+               om.parent_member_id, pm.first_name, me.contact_person,
+               ms.product_id, ms.product_name,  me.phone_mobile, me.phone_home,
+	       me.address1, me.address2, me.address3, me.postcode, me.suburb, cn.name,
+	       md.unixlogin, ml.id,
+	       IF(ms.invoiced_to_dt > om.last_invoiced_to_dt, ms.invoiced_to_dt, om.last_invoiced_to_dt) AS invoiced_to_dt,
+	       om.invoice_ref, me.corporation,
+	       om.start_date, om.expiry,
+	       IF(ms.paid_to_dt > om.last_paid_to_dt, ms.paid_to_dt, om.last_paid_to_dt) AS paid_to_dt
+	FROM
+	            members              me
+	       JOIN countries            cn  ON  me.country_code     = cn.code
+	       JOIN org_members          om  ON  me.id               = om.member_id
+	  LEFT JOIN members              pm  ON  om.parent_member_id = pm.id
+	  LEFT JOIN (SELECT  m.membership_id, m.invoiced_to_dt, m.paid_to_dt,
+                             p.id AS product_id, p.name AS product_name
+		     FROM    membership_products m
+		        JOIN products            p  ON  m.product_id      = p.id
+		        JOIN product_types       t  ON  p.product_type_id = t.id
+		     WHERE   t.name = 'membership'
+	            )                    ms  ON  om.id               = ms.membership_id
+	       JOIN membership_data      md  ON  om.id               = md.membership_id
+	  LEFT JOIN mailing_list_members lm  ON  om.id               = lm.membership_id
+	  LEFT JOIN mailing_lists        ml  ON  lm.mailing_list_id  = ml.id            AND
+                                                 ml.name             = 'medlemmer'
+	ORDER BY me.id
+EoQ
+    $sth = $dbh->prepare($sql);
+    if (! $sth->execute)
+    {
+	sql_error($dbh, $sql);
+	return 0;
+    }
+
+    my @cols = qw(CustomerNo Name EmailAddress USENIX SAGE ZUsrArbeidsgiverNo ZUsrArbeidsgiver
+                  Kontaktperson ZUsrMedlemstatusNo ZUsrMedlemstatus MobileTelephone Telephone
+                  Address1 Address2 Address3 PostCode PostOffice Country unixlogin Informasjon
+                  ZUsrFakturertPeriode ZUsrFakturaReferanse ErArbeidsgiver
+                  ZUsrInnmeldtDato ZUsrUtmeldtDato ZBetaltTil);
+
+    print join("\t", @cols), "\n";
+
+    while (my @row = $sth->fetchrow_array)
+    {
+	# Convert null/undef values to empty string
+	my @r = map { defined($_) ? $_ : '' } @row;
+
+	# Convert various values
+	splice(@r, 1, 3, join(' ', grep({ $_ } @r[1..3]))); # last_name, first_name, middle_name -> Name
+	$r[4]  = 0  if ($r[4] eq '');			# SAGE:                 '' -> 0
+	$r[8]  = 0  if ($r[8] eq '');			# ZUsrMedlemstatusNo:   '' -> 0
+	$r[19] = $r[19] eq '' ? '0' : '1';		# Informasjon:          '' -> 0 else 1
+#	$r[20] = '2007-12-31'  if ($r[20] eq '');	# ZUsrFakturertPeriode: '' -> '2007-12-31'
+	$r[24] = '1970-01-01 00:00:00'  if ($r[24] eq ''); # ZUsrUtmeldtDato:   '' -> MS epoch
+#	$r[25] = '2007-12-31'  if ($r[25] eq '');	# ZBetaltTil:           '' -> '2007-12-31'
+
+	# Convert tabs to spaces
+	my @r2;
+	foreach my $val (@r)
+	{
+	    $val =~ s/\s+/ /g;
+	    push @r2, $val;
+	}
+
+	print join("\t", @r), "\n";
+
+	$printed++;
+    }
+
+    return 1;
+}
+
+sub sql_error
+{
+    my ($dbh, $sql) = @_;
+    print STDERR "Problem selecting from tables:\n";
+    print STDERR "$sql\n";
+}
Index: www/config.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/config.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.4
diff -u -3 -p -r1.1.1.1 -r1.4
--- www/config.inc	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ www/config.inc	15 Nov 2007 23:44:15 -0000	1.4
@@ -32,13 +32,21 @@ $config['org_id'] = 1;
 $config['member_member_type_id'] = 2;
 
 /*
+ * country code
+ * ------------
+ *
+ * Default country code.
+ */
+$config['country_code'] = 'NO';
+
+/*
  * Index page title
  * ----------------
  *
  * Title to appear on the index page, i.e. what users see
  * before they go executing functions.
  */
-$config['index_title'] = "Linux Australia Membership Database";
+$config['index_title'] = "NUUG Membership Database";
 
 /*
  * Base dir
@@ -58,7 +66,7 @@ $config['basedir'] = getcwd();
  *
  * This is used when putting URLs into emails.
  */
-$config['baseurl']= 'http://localhost/~stewart/memberdb/'; // 'http://www.linux.org.au/membership/';
+$config['baseurl'] = 'http://memberdb.nuug.no/'; // 'http://www.linux.org.au/membership/';
 
 /**
  *
@@ -68,14 +76,14 @@ $config['baseurl']= 'http://localhost/~s
  * This is where copies of mails sent to users are sent.
  * We may also send other things here in the future.
  */
-$config['membership-admin']= 'membership-admin@lists.linux.org.au';
+$config['membership-admin'] = 'membership-admin@nuug.no';
 
 /**
  * From
  * ----
  * Who mails are from.
  */
-$config['from']='ctte@linux.org.au';
+$config['from'] = 'sekretariat@nuug.no';
 
 /**
  * Enable sending emails
@@ -84,5 +92,5 @@ $config['from']='ctte@linux.org.au';
  * Set to 1 to enable the sending of emails. Otherwise they are queued in
  * the database.
  */
-$config['send_emails']=0;
+$config['send_emails'] = 0;
 ?>
Index: www/favicon.ico
===================================================================
RCS file: www/favicon.ico
diff -N www/favicon.ico
Binary files /dev/null and /tmp/cvsmisiQd differ
Index: www/index.php
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/index.php,v
retrieving revision 1.1.1.2
retrieving revision 1.6
diff -u -3 -p -r1.1.1.2 -r1.6
--- www/index.php	9 Mar 2007 11:01:01 -0000	1.1.1.2
+++ www/index.php	18 Nov 2007 21:57:16 -0000	1.6
@@ -226,8 +226,8 @@ function headers($param)
      </div>
      <?php
      } else {
-     echo '<div class="menu">';
-      print '<ul class="menu">';
+      print "<div class=\"menu\">\n";
+      print "<ul class=\"menu\">\n";
       print show_menu($permissions_pages); // do menu stuff
       print '</ul>';
    }
@@ -260,6 +260,7 @@ if($page_param['menu']) {
 
 
     echo '<h2>Login</h2>';
+    echo '<p>';
 
     if($login_error)
       echo '<div class="error"><p>'.$login_error.'</p></div>';
@@ -270,6 +271,7 @@ if($page_param['menu']) {
 		"Login","post",
 		($page=='reset-password')?'?page=index':$PHP_SELF
 		);
+    echo '</p>';
 
   } else {
     echo '<p>You are logged in as '.$_SESSION['user'].'</p>';
@@ -303,7 +305,9 @@ if($config['send_emails'])
 <div class="copyright">
 <hr/>
 <?php include $config['basedir'].'/site-look/footer.inc'; ?>
-<p>This site is running <a href="http://www.flamingspork.com/projects/memberdb/">MemberDB</a> &copy;2002-2005 Stewart Smith. Report bugs at <a href="http://bugzilla.flamingspork.com">http://bugzilla.flamingspork.com/</a>.</p>
+
+<p>This site is running <a href="http://www.flamingspork.com/projects/memberdb/">MemberDB</a>
+&copy;2002-2005 Stewart Smith. Report bugs at <a href="http://bugzilla.flamingspork.com">http://bugzilla.flamingspork.com/</a>.</p>
 </div>
 <script type="text/javascript">
 sortables_init();
Index: www/pages.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/pages.inc,v
retrieving revision 1.1.1.2
retrieving revision 1.3
diff -u -3 -p -r1.1.1.2 -r1.3
--- www/pages.inc	9 Mar 2007 11:01:01 -0000	1.1.1.2
+++ www/pages.inc	18 Nov 2007 21:57:16 -0000	1.3
@@ -22,6 +22,11 @@ $permissions_pages = array(
 	     array('page' =>'signup',
 		   'title'=>'Sign-up',
 		   ),
+	     'new_member' =>
+	       array('page'     => 'new-member',
+                     'title'    => 'New Member',
+                     'activity' => 'add new member',
+		   ),
 	     'approve_members'=>
 	     array('page'=>'approve_members',
 		   'title'=>'Approve',
@@ -45,6 +50,11 @@ $permissions_pages = array(
        'page'=>'edit-member',
        'logged_in'=>1,
        'items'=>array(
+		      'mymembership' =>
+                        array('page'      => 'edit-membership',
+                              'title'     => 'My Membership',
+                              'logged_in' => 1
+                              ),
 		      'passwd'=>
 		      array('page'=>'passwd',
 			    'title'=>'Change Password',
Index: www/authenticated/edit-member.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/authenticated/edit-member.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.10
diff -u -3 -p -r1.1.1.1 -r1.10
--- www/authenticated/edit-member.inc	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ www/authenticated/edit-member.inc	18 Nov 2007 21:57:16 -0000	1.10
@@ -18,14 +18,57 @@ if($_GET['id']
   }
 }
 
+if ($member) {
+  include 'forms/comment.inc';
+
+  $errors = validate($form_addcomment, $_POST);
+
+  if (!$errors
+      and strtolower($_SERVER['REQUEST_METHOD']) == 'post'
+      and isset($_POST['add-comment'])) {
+    $res = add_comment($member->comment_id, $_POST['comm']);
+    if (DB::isError($res))
+      $errors = "<div class=\"error\">
+                 <p><img src=\"images/error.png\" width=\"16\" height=\"16\" alt=\"Error:\" /> Database error: " . $res->getMessage() . "</p>
+                 </div>\n";
+    elseif (!$member->comment_id) {
+      $sql = <<<EoQ
+        update members
+        set    comment_id = $res
+        where  id         = $member->id
+EoQ;
+      $result = sql_query($sql, __FUNCTION__);
+      if (DB::isError($result))
+        $errors = "<div class=\"error\">
+                   <p><img src=\"images/error.png\" width=\"16\" height=\"16\" alt=\"Error:\" /> Database error: " . $result->getMessage() . "</p>
+                   </div>\n";
+      else
+        $member = get_member($member->id);
+    }
+  }
+
+  print $errors;
+}
+
+
+if($member) {
+  $parent_member_id = get_member_parent($member->id);
+}
+
+$countries = get_countries();
+$country_items = array();
+foreach ($countries as $c) {
+  $country_items[$c->code] = $c->name;
+}
+
 include 'forms/member.inc';
 
 if($member) {
 echo '<div class="stdform">';
 
-echo '<h2>Edit Details</h2>';
+echo '<h2>Edit Details for member #'.$member->id.'</h2>';
 
-$errors = validate($form_editmember, $HTTP_POST_VARS);
+$errors = validate($form_editmember, $_POST);
 
 if(!$errors
    and strtolower($_SERVER['REQUEST_METHOD']) == 'post'
@@ -37,9 +80,6 @@ if(!$errors
   else
     $sexval = ' ';
 
-  $dbh->query("insert into members_old_details select * from members where "
-	      ."id=".$member->id);
-
   sql_update('members',array(
 			     'date_entered'=> date('Y-m-d H:i:s'),
 			     'first_name'  => $_POST['first_name'],
@@ -49,10 +89,11 @@ if(!$errors
 			     'sex'         => $sexval,
 			     'address1'    => $_POST['address1'],
 			     'address2'    => $_POST['address2'],
+			     'address3'    => $_POST['address3'],
 			     'suburb'      => $_POST['suburb'],
 			     'postcode'    => $_POST['postcode'],
 			     'state'       => $_POST['state'],
-			     'country'     => $_POST['country'],
+			     'country_code'=> $_POST['country_code'],
 			     'email'       => $_POST['email'],
 			     'phone_home'  => $_POST['phone_home'],
 			     'phone_mobile'=> $_POST['phone_mobile']
@@ -69,16 +110,15 @@ output_form($form_editmember,"edit-membe
 
 echo '</div>';
 ?>
+
 <table class="sortable" id="memberships_list">
 <thead>
-<tr>
-<th>Type</th>
-<th>Description</th>
-<th>Start</th>
-<th>Expiry</th>
-<th>Validates Membership</th>
-<th>Revokes Membership</th>
-</tr>
+  <tr>
+    <th>Type</th>
+    <th>Description</th>
+    <th>Start</th>
+    <th>Expiry</th>
+  </tr>
 </thead>
 <tbody>
 <?php
@@ -87,25 +127,97 @@ $sql= "select  * from org_memberships wh
    " order by validates_membership desc,start_date desc";
 $result= $dbh->query($sql);
 while($row = $result->fetchrow(DB_FETCHMODE_OBJECT)) {
+  /* Assume the last entry in org_membership is the active one */
+  $membership_id = $row->membership_id;
+  $submemberof = $row->parent_member_id;
+?>
+  <tr>
+    <td><?= htmlise($row->type) ?></td>
+    <td><?= htmlise($row->description) ?></td>
+    <td><?= ($row->start_date) ? htmlise(date("d/m/Y", strtotime($row->start_date)))
+                               : "Never" ?></td>
+    <td><?= ($row->expiry) ? htmlise(date("d/m/Y", strtotime($row->expiry)))
+                           : "Never" ?></td>
+  </tr>
+<?php
+}
+
+?>
+</tbody>
+</table>
+<?php
+
+if ($membership_id)
+  print "<p><a href=\"?page=edit-membership&id=" . $membership_id . "\">Edit membership</a></p>\n";
+
+if ($member->corporation) {
+  print "<p>Corporation, listing submembers.</p>\n";
+  print "<table><tbody>\n";
+
+$sql= "select * from org_members where parent_member_id=".$member->id.
+   " and org_id=".$config['org_id'].
+   " order by start_date desc";
+$result= $dbh->query($sql);
+if (DB::isError($result))
+  my_syslog(LOG_INFO,'edit-member.c: select error message: ' . $result->getMessage());
+elseif (!DB::isError($result->numRows()) && $result->numRows() == 0)
+  print "<tr><td><i>No members found.</i></td></tr>\n";
+else
+  while($row = $result->fetchrow(DB_FETCHMODE_OBJECT)) {
+    $submember = get_member($row->member_id);
   ?>
 <tr>
-<td><?=htmlise($row->type)?></td>
-<td><?=htmlise($row->description)?></td>
-<td><?=($row->start_date)?
-   htmlise(date("d/m/Y",strtotime($row->start_date)))
-   :
-  "Never"?></td>
-<td><?=($row->expiry)?
-   htmlise(date("d/m/Y",strtotime($row->expiry)))
-   :
-  "Never"?></td>
-<td><?=htmlise($row->validates_membership)?></td>
-<td><?=htmlise($row->revokes_membership)?></td>
+<td><a href="?page=edit-member&id=<?=htmlise($submember->id)?>"><?=htmlise($submember->id)?></a></td>
+<td><?=htmlise($submember->first_name)?></td>
+<td><?=htmlise($submember->middle_name)?></td>
+<td><?=htmlise($submember->last_name)?></td>
 </tr>
 <?php
-}
+  } /* while fetchrow */
+  print '</tbody></table>';
+} else { /* not $member->corporation */
+  if ($submemberof) {
+    $parentmember = get_member($submemberof);
+  ?>
+<p>Sub-member of
+<a href="?page=edit-member&id=<?=htmlise($submemberof)?>"><?=htmlise($parentmember->id)?></a> <?=htmlise($parentmember->first_name)?></p>
+<?php
+  } /* $submemberof */  
+} /* not $member->corporation */
 
-} // if $member
+
+print "<div class=\"stdform\">\n";
+print "<p><br/></p>\n";
+
+$comments = get_comments($member->comment_id);
+if ( $comments ) {
+?>
+<table class="sortable" id="memberships_list">
+<thead>
+  <tr><th>Date</th><th>By</th><th>Comment</th></tr>
+</thead>
+<tbody>
+<?php
+    foreach( $comments as $comment ) {
+?>
+  <tr>
+    <td><?= htmlise($comment->created_dtm) ?></td>
+    <td><?= htmlise($comment->created_by)  ?></td>
+    <td><?= htmlise($comment->comm)        ?></td>
+  </tr>
+<?php
+    }
 ?>
 </tbody>
 </table>
+<p></p>
+<?php
+}
+
+output_form($form_addcomment, 'add-comment', 'Add comment', 'post',
+	    $_GET['id'] ? '&id=' . $member->id : '');
+
+echo "</div>\n";
+
+} // if $member
+?>
\ No newline at end of file
Index: www/authenticated/edit-membership.inc
===================================================================
RCS file: www/authenticated/edit-membership.inc
diff -N www/authenticated/edit-membership.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ www/authenticated/edit-membership.inc	19 Nov 2007 23:17:19 -0000	1.2
@@ -0,0 +1,226 @@
+<?php
+$title = "Edit Membership Details";
+headers(array('title' => $title, 'menu' => 1));
+
+if ($_GET['id']
+    && has_permission(get_member_id($_SESSION['user']), 'edit org_members')) {
+  $membership = get_membership($_GET['id']);
+} else {
+  if ($_GET['id']) {?>
+    <div class="error">
+      <p>You do not have adequate permissions to edit the details of this member .</p>
+    </div>
+    <?php
+    $membership = NULL; // Don't go editing anyone if we don't have permission
+  } else {
+    // default to self
+    $membership = get_membership_by_member(get_member_id($_SESSION['user']));
+  }
+}
+
+if (DB::isError($membership))
+  print "<div class=\"error\">
+         <p><img src=\"images/error.png\" width=\"16\" height=\"16\" alt=\"Error:\" /> Database error: " . $membership->getMessage() . "</p>
+         </div>\n";
+elseif (!$membership)
+  print "<tr><td><i>No membership found.</i></td></tr>\n";
+else {
+  include 'forms/comment.inc';
+
+  $errors = validate($form_addcomment, $_POST);
+
+  if (!$errors
+      and strtolower($_SERVER['REQUEST_METHOD']) == 'post'
+      and isset($_POST['add-comment'])) {
+    $res = add_comment($membership->comment_id, $_POST['comm']);
+    if (DB::isError($res))
+      $errors = "<div class=\"error\">
+                 <p><img src=\"images/error.png\" width=\"16\" height=\"16\" alt=\"Error:\" /> Database error: " . $res->getMessage() . "</p>
+                 </div>\n";
+    elseif (!$membership->comment_id) {
+      $result = set_comment_id('org_members', 'id', $membership->id, $res);
+      if (DB::isError($result))
+        $errors = "<div class=\"error\">
+                   <p><img src=\"images/error.png\" width=\"16\" height=\"16\" alt=\"Error:\" /> Database error: " . $result->getMessage() . "</p>
+                   </div>\n";
+      else
+        $membership = get_membership($membership->id);
+    }
+  }
+
+  print $errors;
+
+
+  $corporations = get_corporate_members();
+  $corp_items = array();
+  $corp_items[''] = '';
+  foreach ($corporations as $c) {
+    $corp_items[$c->id] = $c->first_name;
+  }
+
+
+  print "<div class=\"stdform\">\n";
+  print '<h2>Edit Membership Details for member #' . $membership->member_id . "</h2>\n";
+
+  include 'forms/membership.inc';
+
+  $errors = validate($form_editmembership, $_POST);
+
+  if (!$errors
+      and strtolower($_SERVER['REQUEST_METHOD']) == 'post'
+      and isset($_POST['edit-membership'])) {
+
+    $res = sql_update('org_members',
+                      array(
+                            'parent_member_id'     => $_POST['corporation'],
+                            'invoice_ref'          => $_POST['invoice_ref'],
+                            'invoice_payment_days' => $_POST['invoice_payment_days'],
+                            ),
+                      'id = ' . $membership->id
+                      );
+    if (DB::isError($res))
+      print "<p>Database error: " . $res->getMessage() . "</p>\n";
+    else
+      print '<p>Successfully updated.</p>';
+  }
+
+  print $errors;
+  output_form($form_editmembership, 'edit-membership', 'Submit', 'post',
+              $_GET['id'] ? '&id='.$membership->id : '');
+
+  print "</div>\n";
+
+  print "<div class=\"stdform\">\n";
+  print "<p><br/></p>\n";
+
+  $membership_data = get_membershipdata($membership->id);
+  if (DB::isError($membership_data))
+    print "<p>Database error: " . $membership_data->getMessage() . "</p>\n";
+  elseif (!$membership_data)
+    print "<tr><td><i>No membership data found.</i></td></tr>\n";
+  else {
+
+    include 'forms/membership-data.inc';
+
+    $errors = validate($form_editmembership_data, $_POST);
+
+    if (!$errors
+        and strtolower($_SERVER['REQUEST_METHOD']) == 'post'
+        and isset($_POST['edit-membership-data'])) {
+
+      $res = sql_update('membership_data',
+                        array(
+                              'usenix'      => $_POST['usenix'],
+                              'sage'        => $_POST['sage'],
+                              'unixlogin'   => $_POST['unixlogin'],
+                            ),
+                        'membership_id = ' . $membership->id
+                        );
+      if (DB::isError($res))
+        print "<p>Database error: " . $res->getMessage() . "</p>\n";
+      else
+        print '<p>Successfully updated.</p>';
+    }
+
+    print $errors;
+
+    output_form($form_editmembership_data, 'edit-membership-data', 'Submit', 'post',
+                $_GET['id'] ? '&id='.$membership->id : '');
+  }
+  print "</div>\n";
+
+  print "<div class=\"stdform\">\n";
+  print "<p><br/></p>\n";
+
+  $products = get_products($membership->id);
+  if ($products) { ?>
+    <table class="pretty" id="products_list">
+    <thead>
+      <tr><th>Product&nbsp;name</th><th>Order date</th><th>Activation date</th><th>Termination order date</th><th>Termination date</th><th>First invoice date</th><th>Invoiced to date</th><th>Paid to date</th><th>Invoice text</th><th>Price list name</th><th>Price</th><th>Comments</th></tr>
+    </thead>
+    <tbody>
+    <?php
+    foreach ($products as $product) { ?>
+      <tr>
+        <td><?= htmlise($product->product_name)         ?></td>
+        <td><?= htmlise($product->order_dt)             ?></td>
+        <td><?= htmlise($product->activation_dt)        ?></td>
+        <td><?= htmlise($product->termination_order_dt) ?></td>
+        <td><?= htmlise($product->termination_dt)       ?></td>
+        <td><?= htmlise($product->first_invoice_dt)     ?></td>
+        <td><?= htmlise($product->invoiced_to_dt)       ?></td>
+        <td><?= htmlise($product->paid_to_dt)           ?></td>
+        <td><?= htmlise($product->invoice_text)         ?></td>
+        <td><?= htmlise($product->price_list_name)      ?></td>
+        <td><?= htmlise($product->price)                ?></td>
+        <td><?= htmlise($product->comment_id)           ?></td>
+      </tr>
+      <?php
+    } ?>
+    </tbody>
+    </table>
+    <p></p>
+    <?php
+  }
+  echo "</div>\n";
+
+
+  print "<div class=\"stdform\">\n";
+  print "<p><br/></p>\n";
+
+  $mailing_lists = get_mailinglists($membership->id);
+  if ($mailing_lists) { ?>
+    <table class="pretty" id="products_list">
+    <thead>
+      <tr><th>Mailing list name</th><th>Description</th><th>Reg date</th><th>Override email address</th></tr>
+    </thead>
+    <tbody>
+    <?php
+    foreach ($mailing_lists as $mailing_list) { ?>
+      <tr>
+        <td><?= htmlise($mailing_list->name)         ?></td>
+        <td><?= htmlise($mailing_list->description)  ?></td>
+        <td><?= htmlise($mailing_list->reg_dtm)      ?></td>
+        <td><?= htmlise($mailing_list->email)        ?></td>
+      </tr>
+      <?php
+    } ?>
+    </tbody>
+    </table>
+    <p></p>
+    <?php
+  }
+  echo "</div>\n";
+
+  print "<div class=\"stdform\">\n";
+  print "<p><br/></p>\n";
+
+  $comments = get_comments($membership->comment_id);
+  if ($comments) { ?>
+    <table class="sortable" id="comments_list">
+    <thead>
+      <tr><th>Date</th><th>By</th><th>Comment</th></tr>
+    </thead>
+    <tbody>
+    <?php
+    foreach( $comments as $comment ) { ?>
+      <tr>
+        <td><?= htmlise($comment->created_dtm) ?></td>
+        <td><?= htmlise($comment->created_by)  ?></td>
+        <td><?= htmlise($comment->comm)        ?></td>
+      </tr>
+      <?php
+    } ?>
+    </tbody>
+    </table>
+    <p></p>
+    <?php
+  }
+
+  output_form($form_addcomment, 'add-comment', 'Add comment', 'post',
+              $_GET['id'] ? '&id=' . $membership->id : '');
+
+  echo "</div>\n";
+}
+
+?>
\ No newline at end of file
Index: www/authenticated/edit-org.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/authenticated/edit-org.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -3 -p -r1.1.1.1 -r1.2
--- www/authenticated/edit-org.inc	9 Mar 2007 11:01:01 -0000	1.1.1.1
+++ www/authenticated/edit-org.inc	15 Nov 2007 23:44:15 -0000	1.2
@@ -25,6 +25,12 @@ if($_GET['id']
   $can_edit= 0;
 }
 
+$countries = get_countries();
+$country_items = array();
+foreach ($countries as $c) {
+  $country_items[$c->code] = $c->name;
+}
+
 $form_editorg= array(
   'first_name'=> array('label'   =>'Name',
 		       'type'    =>'text',
@@ -77,15 +83,11 @@ $form_editorg= array(
 		       'default' =>$org->state,
 		       'maxlength'=>50,
 		       ),
-  'country'    =>array('label'   =>'Country',
-		       'type'    =>'text',
-		       'required'=>true,
-		       'default' =>$org->country,
-		       'maxlength'=>50,
-		       'valid'   =>array(
-					 'preg_match("/\w+/", $val)'
-					 =>'Must enter a country'
-					 ),
+  'country_code' => array('label'    => 'Country',
+                          'type'     => 'select',
+                          'items'    => $country_items,
+                          'default'  => $org->country_code,
+                          'required' => true
 		       ),
   'email'      =>array('label'   =>'Email Address',
 		       'type'    =>'text',
Index: www/authenticated/exportmembers.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/authenticated/exportmembers.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -u -3 -p -r1.1.1.1 -r1.3
--- www/authenticated/exportmembers.inc	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ www/authenticated/exportmembers.inc	15 Nov 2007 23:44:15 -0000	1.3
@@ -45,11 +45,13 @@ array(
 					 'label'=>'Address (Line 1)'),
 		       'address2'=>array('type'=>'checkbox',
 					 'label'=>'Address (Line 2)'),
+		       'address3'=>array('type'=>'checkbox',
+					 'label'=>'Address (Line 3)'),
 		       'suburb'=>array('type'=>'checkbox',
 				       'label'=>'Suburb'),
 		       'postcode'=>array('type'=>'checkbox',
 					 'label'=>'Postcode (ZIP)'),
-		       'country'=>array('type'=>'checkbox',
+		       'country_code' => array('type' => 'checkbox',
 					'label'=>'Country'),
 		       'email'=>array('type'=>'checkbox',
 				      'label'=>'Email'),
Index: www/authenticated/memberlist.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/authenticated/memberlist.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.4
diff -u -3 -p -r1.1.1.1 -r1.4
--- www/authenticated/memberlist.inc	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ www/authenticated/memberlist.inc	18 Nov 2007 21:57:16 -0000	1.4
@@ -20,17 +20,28 @@ $order_by = array('id' => 'id,first_name
 		  'date_entered' => 'date_entered,first_name,middle_name,last_name',
 		  'name' => 'first_name,middle_name,last_name',
 		  'name_l' => 'last_name,middle_name,first_name',
+		  'corp'   => 'corp_member desc, cm.corporation desc, pm.first_name, cm.first_name, cm.middle_name, cm.last_name',
 		  'dob' => 'dob',
 		  'sex' => 'sex',
-		  'address' => 'country,state,suburb,address1,address2',
+		  'address' => 'country,state,suburb,address1,address2,address3',
 		  'home_phone' => 'phone_home',
 		  'mobile_phone' => 'phone_mobile',
 		  'email' => 'email'
 );
-$sql = "select *,date_entered as date_joined from current_memberships where org_id=".$config['org_id'];
+$sql = <<<EoQ
+    SELECT cm.*, DATE(cm.date_entered) AS date_joined, cn.name AS country,
+           pm.first_name AS parent_name, IF(pm.first_name IS NULL, 0, 1) AS corp_member
+    FROM        current_memberships cm
+           JOIN countries           cn  ON  cm.country_code = cn.code
+      LEFT JOIN members             pm  ON  cm.parent_member_id = pm.id
+    WHERE  org_id = $config[org_id]
+EoQ;
 if($_GET['sort'] && $order_by[$_GET['sort']]) {
   $sql.=' order by '.$order_by[$_GET['sort']];
 }
+else
+  $sql .= ' order by '.$order_by['id'];
+# print "<pre>\n" . htmlise($sql) . "</pre>\n";
 $result = $dbh->query($sql);
 my_syslog(LOG_DEBUG,'membership list: '.$sql);
 if(DB::isError($result))
@@ -50,8 +61,9 @@ details of that member</p>
   <th scope="col"><a href="<?=$PHP_SELF?>?page=memberlist&sort=id">ID</a></th>
   <th scope="col"><a href="<?=$PHP_SELF?>?page=memberlist&sort=date_entered">Date Entered</a></th>
   <th scope="col">Name (<a href="<?=$PHP_SELF?>?page=memberlist&sort=name">F</a>|<a href="<?=$PHP_SELF?>?page=memberlist&sort=name_l">L</a>)</th>
-  <th scope="col"><a href="<?=$PHP_SELF?>?page=memberlist&sort=dob">D.O.B</a></th>
-  <th scope="col"><a href="<?=$PHP_SELF?>?page=memberlist&sort=sex">Sex</a></th>
+  <th scope="col"><a href="<?=$PHP_SELF?>?page=memberlist&sort=corp">Corporation</a></th>
+<!--  <th scope="col"><a href="<?=$PHP_SELF?>?page=memberlist&sort=dob">D.O.B</a></th>
+      <th scope="col"><a href="<?=$PHP_SELF?>?page=memberlist&sort=sex">Sex</a></th>   -->
   <th scope="col"><a href="<?=$PHP_SELF?>?page=memberlist&sort=email">Email</a></th>
   <th scope="col"><a href="<?=$PHP_SELF?>?page=memberlist&sort=address">Address</a></th>
   <th scope="col"><a href="<?=$PHP_SELF?>?page=memberlist&sort=home_phone">Home Phone</a></th>
@@ -68,10 +80,14 @@ while($row = $result->fetchrow(DB_FETCHM
   print '<td><a href="?page=edit-member&id='.htmlise($row->id).'">'.htmlise($row->id).'</a></td>';
   print '<td>'.htmlise($row->date_joined).'</td>';
   print '<td>'.htmlise($row->first_name.' '.$row->middle_name.' '.$row->last_name).'</td>';
-  print '<td>'.($row->dob? htmlise($row->dob): '&nbsp;').'</td>';
-  print '<td>'.($row->sex && $row->sex!=' '? htmlise($row->sex): '&nbsp;').'</td>';
+  print '<td>'.($row->parent_name ? htmlise($row->parent_name) : '&nbsp;') .'</td>';
+//  print '<td>'.($row->dob? htmlise($row->dob): '&nbsp;').'</td>';
+//  print '<td>'.($row->sex && $row->sex!=' '? htmlise($row->sex): '&nbsp;').'</td>';
   print '<td><a href="mailto:'.htmlise($row->email).'">'.htmlise($row->email).'</a></td>';
-  print '<td>'.htmlise($row->address1).'<br/>'.htmlise($row->address2).'<br/>'.htmlise($row->suburb).'<br/>'.htmlise($row->state).' '.htmlise($row->postcode).'<br/>'.htmlise($row->country).'</td>';
+  if ($row->country_code == 'NO')
+    print '<td>'.htmlise($row->address1).'<br/>'.htmlise($row->address2).'<br/>'.htmlise($row->address3).'<br/>'.htmlise($row->postcode).' '.htmlise($row->suburb).'</td>';
+  else
+    print '<td>'.htmlise($row->address1).'<br/>'.htmlise($row->address2).'<br/>'.htmlise($row->address3).'<br/>'.htmlise($row->suburb).'<br/>'.htmlise($row->state).' '.htmlise($row->postcode).'<br/>'.htmlise($row->country).'</td>';
   print '<td>'.($row->phone_home?htmlise($row->phone_home):'&nbsp;').'</td>';
   print '<td>'.($row->phone_mobile?htmlise($row->phone_mobile):'&nbsp;').'</td>';
 
Index: www/authenticated/new-member.inc
===================================================================
RCS file: www/authenticated/new-member.inc
diff -N www/authenticated/new-member.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ www/authenticated/new-member.inc	15 Nov 2007 23:44:15 -0000	1.3
@@ -0,0 +1,123 @@
+<?php
+$title = 'Add New Member';
+headers(array('title' => $title, 'menu' => 1));
+
+if (!has_permission(get_member_id($_SESSION['user']), 'add org_members')) {
+  ?>
+  <div class="error">
+     <p>You do not have adequate permissions to add a new member.</p>
+  </div>
+  <?php
+  return;
+}
+
+$corporations = get_corporate_members();
+$corp_items = array();
+$corp_items[''] = '- Personlig medlem -';
+foreach ($corporations as $c) {
+  $corp_items[$c->id] = $c->first_name;
+}
+
+$parent_member_id = '';
+
+$countries = get_countries();
+$country_items = array();
+foreach ($countries as $c) {
+  $country_items[$c->code] = $c->name;
+}
+
+
+include 'forms/member.inc';
+
+echo "<div class=\"stdform\">\n";
+
+echo "<h2>Add new member</h2>\n";
+
+$errors = validate($form_editmember, $HTTP_POST_VARS);
+
+if (!$errors
+    && strtolower($_SERVER['REQUEST_METHOD']) == 'post'
+    && isset($_POST['new-member'])) {
+  if ($_POST['sex'] == 'Male')
+    $sexval = 'M';
+  elseif ($_POST['sex'] == 'Female')
+    $sexval = 'F';
+  else
+    $sexval = ' ';
+
+  $parent_member_id = (integer) $_POST['corporation'];
+  if ($parent_member_id > 0) {
+      $corporation = 1;
+  } else {
+      $corporation = 0;
+      $parent_member_id = 'NULL';
+  }
+
+  $dbh->query('START TRANSACTION');
+
+  $result = sql_insert('members',
+                       array('date_entered' => date('Y-m-d H:i:s'),
+                             'corporation'  => $corporation,
+			     'first_name'   => $_POST['first_name'],
+			     'middle_name'  => $_POST['middle_name'],
+			     'last_name'    => $_POST['last_name'],
+			     'dob'          => $_POST['dob'],
+			     'sex'          => $sexval,
+			     'address1'     => $_POST['address1'],
+			     'address2'     => $_POST['address2'],
+			     'address3'     => $_POST['address3'],
+			     'suburb'       => $_POST['suburb'],
+			     'postcode'     => $_POST['postcode'],
+			     'state'        => $_POST['state'],
+			     'country_code' => $_POST['country_code'],
+			     'email'        => $_POST['email'],
+			     'phone_home'   => $_POST['phone_home'],
+			     'phone_mobile' => $_POST['phone_mobile']));
+
+  if (DB::isError($result)) {
+    $dbh->query('ROLLBACK');
+    my_syslog(LOG_ERR, 'insert into members failed: ' . $result->getMessage());
+    print "<p>Database operation failed.</p>\n";
+    print "</div>\n";
+    return;
+  }
+
+  $sql = "select LAST_INSERT_ID() as seq";
+  $result = $dbh->query($sql);
+  if (DB::isError($result)) {
+    $dbh->query('ROLLBACK');
+    my_syslog(LOG_ERR, 'select last_insert_id() failed: ' . $result->getMessage());
+    print "<p>Database operation failed.</p>\n";
+    print "</div>\n";
+    return;
+  }
+  $row = $result->fetchrow(DB_FETCHMODE_OBJECT);
+  $id = $row->seq;
+  my_syslog(LOG_DEBUG,"member id seq: $id");
+
+  $sql = <<<EoQ
+      insert into org_members
+          (org_id, member_id, parent_member_id, member_type_id, start_date, expiry)
+      values
+          ($config[org_id], $id, $parent_member_id, (select id from member_types where org_id = $config[org_id] and type='Pending'), now(), date_add(now(), interval 1 year))
+EoQ;
+  my_syslog(LOG_DEBUG, $sql);
+  $result = $dbh->query($sql);
+  if (DB::isError($result)) {
+    $dbh->query('ROLLBACK');
+    my_syslog(LOG_ERR, 'insert into org_members failed: ' . $result->getMessage());
+    print "<p>Database operation failed.</p>\n";
+    print "</div>\n";
+    return;
+  }
+
+  $dbh->query('COMMIT');
+
+  print '<p>Successfully added member #' . $id . ".</p>\n";
+}
+
+print $errors;
+output_form($form_editmember, 'new-member', 'Submit', 'post', '');
+
+echo "</div>\n";
+?>
\ No newline at end of file
Index: www/authenticated/renew_membership.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/authenticated/renew_membership.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -3 -p -r1.1.1.1 -r1.2
--- www/authenticated/renew_membership.inc	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ www/authenticated/renew_membership.inc	16 Jul 2007 22:51:56 -0000	1.2
@@ -7,7 +7,15 @@ headers(array('title'=>$title,'menu'=>1,
 
 <h2>Renew Membership</h2>
 <?php
-$sql = 'insert into org_members (org_id,member_id,member_type_id,start_date,expiry) values ('.$config['org_id'].','.$dbh->quote(get_member_id($_SESSION['user'])).",(select id from member_types where type='Member'),now(),now()+'1year')";
+
+$member_id = get_member_id($_SESSION['user']);
+    // Extend expiry date.
+$sql = <<<EoQ
+    UPDATE org_members
+    SET    expiry = NOW() + '1year'
+    WHERE  org_id    = $config[org_id]  AND
+           member_id = $member_id
+EoQ;
 my_syslog(LOG_DEBUG,$sql);
 $result = $dbh->query($sql);
 if (DB::isError($result)) {
Index: www/backend/authentication.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/backend/authentication.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -3 -p -r1.1.1.1 -r1.2
--- www/backend/authentication.inc	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ www/backend/authentication.inc	4 Mar 2007 23:10:30 -0000	1.2
@@ -30,8 +30,10 @@ function login($username, $password)
     ereg("^[0-9a-z]+", $row->password,$dbpass);
     ereg("^[0-9a-zA-Z]+",$row->salt,$dbsalt);
 
-    if(md5($dbsalt[0].$password) == $dbpass[0])
+    if(md5($dbsalt[0].$password) == $dbpass[0]) {
+        $dbh->query("call app_user_set('" . $dbh->quote($username) . "')");
 	return 1;
+    }
   }
   else
     my_syslog(LOG_ERR,"DB MESSAGE: ".var_export($result));
Index: www/backend/database.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/backend/database.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.8
diff -u -3 -p -r1.1.1.1 -r1.8
--- www/backend/database.inc	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ www/backend/database.inc	19 Nov 2007 23:17:19 -0000	1.8
@@ -28,6 +28,35 @@ if(DB::isError($dbh))
  die("Could not connect to database");
 }
 
+//$dbh->query("SET NAMES 'utf8'");
+$dbh->query("SET SQL_MODE = 'TRADITIONAL'");
+
+
+function sql_query($sql, $query_function = 'sql_query')
+{
+  global $dbh;
+
+  my_syslog(LOG_DEBUG, $query_function . ' SQL: ' . $sql);
+  $result = $dbh->query($sql);
+  if (DB::isError($result)) {
+    my_syslog(LOG_ERR, $query_function . ' error: '  . $result->getMessage());
+    my_syslog(LOG_ERR, $query_function . ' result: ' . var_export($result, TRUE));
+  }
+
+  return $result;
+}
+
+function sql_last_insert_id()
+{
+  $sql = 'select LAST_INSERT_ID() as seq';
+  $result = sql_query($sql, __FUNCTION__);
+  if (DB::isError($result))
+    return $result;
+
+  $row = $result->fetchrow(DB_FETCHMODE_OBJECT);
+  return $row->seq;
+}
+
 function sql_insert($table,$values)
 {
   global $dbh;
@@ -48,16 +77,8 @@ function sql_insert($table,$values)
   $sql_values.=$dbh->quote($values[$fields[$i]]).")";
 
   $sql.=") ".$sql_values;
-  my_syslog(LOG_DEBUG,"sql_insert() SQL statement: $sql");
 
-  $result = $dbh->query($sql);
-  if (DB::isError($result)) {
-    my_syslog(LOG_ERR,'sql_insert error: '.$result->getMessage());
-    $e = var_export($result,TRUE);
-    my_syslog(LOG_ERR,"sql_insert result: $e");
-  }
-
-  return $result;
+  return sql_query($sql, __FUNCTION__);
 }
 
 function sql_update($table, $fields, $where)
@@ -74,15 +95,7 @@ function sql_update($table, $fields, $wh
 
   $sql.=' where '.$where;
 
-  my_syslog(LOG_DEBUG,"sql_update() SQL: $sql");
-
-  $result = $dbh->query($sql);
-  if (DB::isError($result)) {
-    my_syslog(LOG_ERR,'sql_update error: '.$result->getMessage());
-    my_syslog(LOG_ERR,'sql_update result: '.var_export($result));
-  }
-
- return $result;
+  return sql_query($sql, __FUNCTION__);
 }
 
 function sql_select1($table, $fields, $where)
@@ -90,18 +103,12 @@ function sql_select1($table, $fields, $w
  global $dbh;
  $sql = "select ".$fields." from ".
       	$table." where ".$where;
- $result = $dbh->query($sql);
- my_syslog(LOG_DEBUG,'sql_select1 query: '.$sql);
- if(!DB::isError($result))
-   $row = $result->fetchrow(DB_FETCHMODE_OBJECT);
- else
-   {
-     $row = NULL;
-     my_syslog(LOG_INFO,'sql_select1 message: '.$result->getMessage());
-     my_syslog(LOG_INFO,'sql_select1 result: '.var_export($result,true));
-   }
 
- return $row;
+ $result = sql_query($sql, __FUNCTION__);
+ if (DB::isError($result))
+   return NULL;
+
+ return $result->fetchrow(DB_FETCHMODE_OBJECT);
 }
 
 function sql_select($table, $where)
@@ -109,14 +116,12 @@ function sql_select($table, $where)
  global $dbh;
  $sql = "select * from ".
    $table." where ".$where;
- my_syslog(LOG_ERR,'sql_select query: '.$sql);
- $result = $dbh->query($sql);
- if(!DB::isError($result))
-   $row = $result->fetchrow(DB_FETCHMODE_OBJECT);
- else
-   $row = NULL;
 
- return $row;
+ $result = sql_query($sql, __FUNCTION__);
+ if (DB::isError($result))
+   return NULL;
+
+ return $result->fetchrow(DB_FETCHMODE_OBJECT);
 }
 
 function has_permission($member_id,$activity)
@@ -169,6 +174,55 @@ function get_member($id,$state="current_
   return $row;
 }
 
+
+function get_corporate_members($state = 'current_memberships')
+{
+  global $dbh, $config;
+
+  $sql = "select * from {$state} where corporation = 1";
+  if ($state == 'current_memberships')
+    $sql .= ' and org_id = ' . $config['org_id'];
+  $sql .= ' order by first_name';
+
+  $result = $dbh->query($sql);
+  my_syslog(LOG_DEBUG,'get_corporate_members query: ' . $sql);
+  if (DB::isError($result)) {
+    my_syslog(LOG_INFO, 'get_corporate_members error: ' . var_dump($result, true));
+    return false;
+  }
+
+  $list = array();
+  while($row = $result->fetchrow(DB_FETCHMODE_OBJECT))
+    $list[] = $row;
+
+  return $list;
+}
+
+
+function get_member_parent($id)
+{
+  global $dbh, $config;
+
+  $id = $dbh->quote($id);
+
+  $sql = <<<EoQ
+      select parent_member_id
+      from   org_members
+      where  member_id = $id  and
+             org_id    = $config[org_id]
+EoQ;
+  $result = $dbh->query($sql);
+  my_syslog(LOG_DEBUG, 'get_member_parent query: ' . $sql);
+  if (DB::isError($result)) {
+    my_syslog(LOG_INFO, 'get_member error: ' . var_dump($result, true));
+    return false;
+  }
+
+  $row = $result->fetchrow(DB_FETCHMODE_OBJECT);
+  return $row->parent_member_id;
+}
+
+
 function get_member_types() {
   global $dbh,$config;
   $list = array();
@@ -192,6 +246,56 @@ function get_member_type($id) {
   return $row;
 }
 
+function get_membership($membership_id)
+{
+  global $dbh, $config;
+
+  $sql = <<<EoQ
+    select *
+    from   org_members
+    where  id = $membership_id
+EoQ;
+  $result = sql_query($sql, __FUNCTION__);
+  if (DB::isError($result))
+    return $result;
+
+  return $result->fetchrow(DB_FETCHMODE_OBJECT);
+}
+
+function get_membership_by_member($member_id)
+{
+  global $dbh, $config;
+
+  $sql = <<<EoQ
+    select *
+    from   org_members
+    where  member_id = $member_id
+      and  org_id    = $config[org_id]
+EoQ;
+  $result = sql_query($sql, __FUNCTION__);
+  if (DB::isError($result))
+    return $result;
+
+  return $result->fetchrow(DB_FETCHMODE_OBJECT);
+}
+
+function get_membershipdata($membership_id)
+{
+  global $dbh;
+
+  $sql = <<<EoQ
+    select *
+    from   membership_data
+    where  membership_id = $membership_id
+EoQ;
+  $result = sql_query($sql, __FUNCTION__);
+  if (DB::isError($result))
+    return $result;
+
+  return $result->fetchrow(DB_FETCHMODE_OBJECT);
+}
+
+
 function add_qualification($name,$department,$institution)
 {
  sql_insert('qualifications',
@@ -223,4 +327,136 @@ function add_org_relation($org_id, $rela
                                    'org_relation_type_id' => $org_relation_type_id));
 }
 
-?>
+
+function get_countries()
+{
+  global $dbh;
+
+  $sql = 'SELECT * FROM countries';
+  $result = sql_query($sql, __FUNCTION__);
+  if (DB::isError($result))
+    return $result;
+
+  $list = array();
+  while ($row = $result->fetchrow(DB_FETCHMODE_OBJECT))
+    $list[] = $row;
+  return $list;
+}
+
+
+function add_comment($comment_id, $comment_text)
+{
+  global $dbh;
+
+  if (!$comment_id) {
+    $result = sql_query('insert into comment_groups (comment_id) values (NULL)', __FUNCTION__);
+    if (DB::isError($result))
+      return $result;
+
+    $comment_id = sql_last_insert_id();
+  }
+
+  $result = sql_insert('comments', array('comment_id' => $comment_id,
+                                         'comm'       => $comment_text));
+  if (DB::isError($result))
+    return $result;
+  return $comment_id;
+}
+
+function set_comment_id($table, $pk_field, $pk_value, $comment_id)
+{
+  global $dbh;
+
+  if (!$table || !$pk_field || !$pk_value || !$comment_id)
+    return NULL;
+
+  $sql = <<<EoQ
+    update $table
+    set    comment_id = $comment_id
+    where  $pk_field  = $pk_value
+EoQ;
+  $result = sql_query($sql, __FUNCTION__);
+  if (DB::isError($result))
+    return $result;
+
+  return True;
+}
+
+function get_comments($comment_id)
+{
+  global $dbh;
+
+  if (!$comment_id)
+    return array();
+
+  $sql = 'SELECT * FROM comments WHERE comment_id = ' . $comment_id;
+  $result = sql_query($sql, __FUNCTION__);
+  if (DB::isError($result))
+    return $result;
+
+  $list = array();
+  while ($row = $result->fetchrow(DB_FETCHMODE_OBJECT))
+    $list[] = $row;
+  return $list;
+}
+
+
+function get_products($membership_id)
+{
+  global $dbh, $config;
+
+  if (!$membership_id)
+    return array();
+
+  $sql = <<<EoQ
+    SELECT mp.*, pr.name AS product_name, pl.name AS price_list_name, ps.price
+    FROM        membership_products mp
+           JOIN products            pr  ON  mp.product_id    = pr.id
+           JOIN prices              ps  ON  pr.id            = ps.product_id
+           JOIN price_lists         pl  ON  ps.price_list_id = pl.id
+    WHERE
+           mp.membership_id = $membership_id   AND
+           pr.org_id        = $config[org_id]  AND
+           pr.active        = True             AND
+           pl.org_id        = $config[org_id]  AND
+           CURDATE() BETWEEN pl.start_dt AND pl.end_dt
+EoQ;
+  $result = sql_query($sql, __FUNCTION__);
+  if (DB::isError($result))
+    return $result;
+
+  $list = array();
+  while ($row = $result->fetchrow(DB_FETCHMODE_OBJECT))
+    $list[] = $row;
+
+  return $list;
+}
+
+
+function get_mailinglists($membership_id)
+{
+  global $dbh, $config;
+
+  if (!$membership_id)
+    return array();
+
+  $sql = <<<EoQ
+    SELECT *
+    FROM        mailing_list_members lm
+           JOIN mailing_lists        ml  ON  lm.mailing_list_id = ml.id
+    WHERE
+           lm.membership_id = $membership_id   AND
+           ml.org_id        = $config[org_id]
+EoQ;
+  $result = sql_query($sql, __FUNCTION__);
+  if (DB::isError($result))
+    return $result;
+
+  $list = array();
+  while ($row = $result->fetchrow(DB_FETCHMODE_OBJECT))
+    $list[] = $row;
+
+  return $list;
+}
+
+?>
\ No newline at end of file
Index: www/backend/forms.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/backend/forms.inc,v
retrieving revision 1.1.1.2
retrieving revision 1.4
diff -u -3 -p -r1.1.1.2 -r1.4
--- www/backend/forms.inc	9 Mar 2007 11:01:01 -0000	1.1.1.2
+++ www/backend/forms.inc	18 Nov 2007 21:57:17 -0000	1.4
@@ -45,6 +45,8 @@ function field_text($name,$setup) {
   if(isset($setup['maxlength'])) {
     echo "maxlength=\"{$setup['maxlength']}\" ";
   }
+  if(isset($setup['readonly']))
+    echo 'readonly="1" ';
   echo "value=\"{$setup['value']}\" />";
 }
 
@@ -240,7 +242,8 @@ function validate(&$fields, &$source,$fo
       $fields[$field]['value'] = htmlentities(get_magic_quotes_gpc()?
 					      stripslashes($source[$field])
 					      :$source[$field]);
-      if(isset($values['valid'])) {
+      if((strlen($val) > 0 || $values['required']) &&
+         isset($values['valid'])) {
 	if($values['type'] == 'date') {
 	  // americanise
 	  $aval = preg_replace('|\s*\b(\d\d?)\s*[./\-\s]\s*(\d\d?)\b\s*|', '$2/$1', $val);
Index: www/backend/syslog.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/backend/syslog.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -3 -p -r1.1.1.1 -r1.2
--- www/backend/syslog.inc	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ www/backend/syslog.inc	4 Mar 2007 23:27:18 -0000	1.2
@@ -64,6 +64,8 @@ $logging["database"] = LOG_DEBUG;
 $logging["syslog"] = LOG_WARNING;
 
 define_syslog_variables();
+openlog('memberdb', 0, LOG_LOCAL7);
+
 
 /**
 * Log Entries
@@ -88,7 +90,7 @@ function my_syslog($priority, $message)
         global $logging;
 
         if($logging["database"] > -1 && $logging["database"] >= $priority
-	   && !DB::isError($dbh))
+	   /* && !DB::isError($dbh) */)
         {
 	  $db_message = $dbh->quote($message);
           $db_priority = $dbh->quote($priority);
Index: www/backend/utils.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/backend/utils.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -3 -p -r1.1.1.1 -r1.2
--- www/backend/utils.inc	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ www/backend/utils.inc	18 Nov 2007 21:57:17 -0000	1.2
@@ -80,6 +80,7 @@ function show_menu($menu)
 	  $page['page'].'">'.
 	  $page['title'].'</a></li>';
     }
+    $str .= "\n";
   }
   
   return $str;
Index: www/backend/functions/approve_members.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/backend/functions/approve_members.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -3 -p -r1.1.1.1 -r1.2
--- www/backend/functions/approve_members.inc	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ www/backend/functions/approve_members.inc	16 Jul 2007 22:51:56 -0000	1.2
@@ -22,7 +22,15 @@ foreach (array_keys($_POST) as $item)
     { // is a member we have to modify
       $member_id = substr($item,strlen('member'));
       // FIXME: make it a seperate function
-      $sql = "insert into org_members (member_id,org_id,member_type_id,start_date,expiry) values ($member_id,".$config['org_id'].",".$dbh->quote($_POST['member'.$member_id]).",now(),NULL)";
+      $sql = <<<EoQ
+          UPDATE org_members
+          SET    member_type_id = (SELECT id
+                                   FROM   member_types
+                                   WHERE  org_id = $config[org_id] AND
+                                          type   = 'Member')
+          WHERE  org_id    = $config[org_id]  AND
+                 member_id = $member_id
+EoQ;
       my_syslog(LOG_DEBUG,"sql_insert() SQL statement: $sql");
 
       $result = $dbh->query($sql);
Index: www/backend/functions/confirm_membership.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/backend/functions/confirm_membership.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.4
diff -u -3 -p -r1.1.1.1 -r1.4
--- www/backend/functions/confirm_membership.inc	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ www/backend/functions/confirm_membership.inc	15 Nov 2007 23:44:16 -0000	1.4
@@ -15,7 +15,7 @@
  *
  */
 
-// we +0 to get an integer, this way we don't have to dbh->quote
+// we +0 to get an integer, this way we don\'t have to dbh->quote
 // to remove any security threat.
 $id = $_GET['id']+0; // id is parameter, using GET method
 
@@ -24,16 +24,22 @@ if(get_member($id)) {
  $already_confirmed= 1;
 }
 
-$row = sql_select1("org_members","count(*) as c","member_type_id=(select id from member_types where type='Pending') and member_id=$id");
+$row = sql_select1("org_members", "count(*) as c",
+                   "member_type_id = (select id from member_types where org_id = $config[org_id] and type = 'Pending') and org_id = $config[org_id] and member_id = $id");
 if($row->c > 0) {
  $already_confirmed= 1;
 }
 
-$sql = "select members.id as id from members,org_members where ".
-       "org_members.org_id=".$config['org_id']." and ".
-       "members.id = org_members.member_id and ".
-       "org_members.member_type_id = (select id from member_types where type='Applied') and members.id = ".$id;
-
+$sql = <<<EoQ
+    SELECT member_id AS id
+    FROM   org_members
+    WHERE  member_id      = $id             AND
+           org_id         = $config[org_id] AND
+           member_type_id = (SELECT id
+                             FROM   member_types
+                             WHERE  org_id = $config[org_id] AND
+                                    type   = 'Applied')
+EoQ;
 my_syslog(LOG_DEBUG,$sql);
 $result = $dbh->query($sql);
 
@@ -50,7 +56,15 @@ else
   my_syslog(LOG_DEBUG,"About to change ".$row->id." to Pending ($id) (".($row->id==$id).")");
   if($row->id==$id)
     {
-      $sql = 'insert into org_members (org_id,member_id,member_type_id,start_date,expiry) values ('.$config['org_id'].','.$id.",(select id from member_types where type='Pending'),now(),DATE_ADD(now(),INTERVAL 1 YEAR))";
+      $sql = <<<EoQ
+          UPDATE org_members
+          SET member_type_id = (SELECT id
+                                FROM   member_types
+                                WHERE  org_id = $config[org_id] AND
+                                       type   = 'Pending')
+          WHERE org_id    = $config[org_id] AND
+                member_id = $id
+EoQ;
       my_syslog(LOG_DEBUG,$sql);
       $result = $dbh->query($sql);
       if (DB::isError($result)) {
@@ -70,9 +84,10 @@ else
 			  $row->middle_name.' '.$row->last_name,
 			  'MEMBER_ADDRESS'=>$row->address1."\n".
 			  $row->address2."\n".
+			  $row->address3."\n".
 			  $row->suburb."  ".$row->postcode."\n".
 			  $row->state."\n".
-			  $row->country."\n",
+			  $row->country_code."\n",
 			  'MEMBER_EMAIL'=>$row->email,
 			  'DATE'=>date('l dS \of F Y h:i:s A')
 			  );
Index: www/forms/comment.inc
===================================================================
RCS file: www/forms/comment.inc
diff -N www/forms/comment.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ www/forms/comment.inc	18 Nov 2007 23:20:39 -0000	1.1
@@ -0,0 +1,11 @@
+<?php
+
+$form_addcomment = array(
+  'comm' => array('label'  => 'New comment',
+                  'type'   => 'textarea',
+                  'rows'   =>  4,
+                  'cols'   => 50,
+                  )
+);
+
+?>
\ No newline at end of file
Index: www/forms/member.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/forms/member.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.9
diff -u -3 -p -r1.1.1.1 -r1.9
--- www/forms/member.inc	27 Feb 2007 23:00:46 -0000	1.1.1.1
+++ www/forms/member.inc	18 Nov 2007 21:57:17 -0000	1.9
@@ -20,11 +20,13 @@ $form_editmember= array(
 		       'maxlength'=>50,
 		       'default' =>$member->last_name,
 		       ),
+/* We do not want to ask members for gender
   'sex'        =>array('label'   =>'Sex',
 		       'type'    =>'select',
 		       'options' =>array('Male','Female'),
 		       'default' =>($member->sex=='F')?'Female':'Male',
 		       ),
+*/
   'address1'   =>array('label'   =>'Address 1',
 		       'type'    =>'text',
 		       'required'=> true,
@@ -41,7 +43,12 @@ $form_editmember= array(
 		       'maxlength'=>50,
 		       'default' =>$member->address2,
 		       ),
-  'suburb'     =>array('label'   =>'Suburb',
+  'address3'   =>array('label'   =>'Address 3',
+		       'type'    =>'text',
+		       'maxlength'=>50,
+		       'default' =>$member->address3,
+		       ),
+  'suburb'     =>array('label'   =>'Post Office',
 		       'type'    =>'text',
 		       'required'=>true,
 		       'default' =>$member->suburb,
@@ -66,19 +73,15 @@ $form_editmember= array(
 		       'default' =>$member->state,
 		       'maxlength'=>50,
 		       ),
-  'country'    =>array('label'   =>'Country',
-		       'type'    =>'text',
-		       'required'=>true,
-		       'default' =>$member->country,
-		       'maxlength'=>20,
-		       'valid'   =>array(
-					 'preg_match("/\w+/", $val)'
-					 =>'Must enter a country'
-					 ),
+  'country_code' => array('label'    => 'Country',
+                          'type'     => 'select',
+                          'items'    => $country_items,
+                          'default'  => ($member->country_code ? $member->country_code : $config['country_code']),
+                          'required' => true
 		       ),
   'email'      =>array('label'   =>'Email Address',
 		       'type'    =>'text',
-		       'required'=>true,
+		       'required' => false,
 		       'maxlength'=>50,
 		       'default' =>$member->email,
 		       'valid'   =>array(
Index: www/forms/membership-data.inc
===================================================================
RCS file: www/forms/membership-data.inc
diff -N www/forms/membership-data.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ www/forms/membership-data.inc	18 Nov 2007 23:20:39 -0000	1.1
@@ -0,0 +1,23 @@
+<?php
+$form_editmembership_data = array(
+  'usenix'               => array('label'     => 'USENIX',
+                                  'type'      => 'text',
+                                  'default'   => $membership_data->usenix,
+                                  'maxlength' => 10,
+                                  'valid'     => array(
+                                                       'preg_match("/^\d+$/", $val)'
+                                                         => 'Must enter a USENIX member id'
+                                                       )
+                                  ),
+  'sage'                 => array('label'     => 'SAGE member',
+                                  'type'      => 'select',
+                                  'items'     => array('0' => 'False' , '1' => 'True'),
+                                  'default'   => $membership_data->sage,
+                                  ),
+  'unixlogin'            => array('label'     => 'UNIX login',
+                                  'type'      => 'text',
+                                  'maxlength' => 16,
+                                  'default'   => $membership_data->unixlogin,
+		       ),
+  );
+?>
\ No newline at end of file
Index: www/forms/membership.inc
===================================================================
RCS file: www/forms/membership.inc
diff -N www/forms/membership.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ www/forms/membership.inc	18 Nov 2007 23:20:39 -0000	1.1
@@ -0,0 +1,31 @@
+<?php
+$form_editmembership = array(
+  'corporation'          => array('label'     => 'Corporation',
+                                  'type'      => 'select',
+                                  'items'     => $corp_items,
+                                  'default'   => $membership->parent_member_id
+                                  ),
+  'invoice_ref'          => array('label'     => 'Invoice reference',
+                                  'type'      => 'text',
+                                  'maxlength' => 50,
+                                  'default'   => $membership->invoice_ref,
+                                  ),
+  'invoice_payment_days' => array('label'     => 'Invoice payment days',
+                                  'type'      => 'select',
+                                  'options'   => array('15', '30'),
+                                  'default'   => $membership->invoice_payment_days,
+                                  ),
+  'last_invoiced_to_dt'  => array('label'     => 'Last invoiced to',
+                                  'type'      => 'text',
+                                  'maxlength' => 11,
+                                  'default'   => $membership->last_invoiced_to_dt,
+                                  'readonly'  => True,
+		       ),
+  'last_paid_to_dt'      => array('label'     => 'Last paid to',
+                                  'type'      => 'text',
+                                  'maxlength' => 11,
+                                  'default'   => $membership->last_paid_to_dt,
+                                  'readonly'  => True,
+		       ),
+  );
+?>
\ No newline at end of file
Index: www/site-look/footer.inc
===================================================================
RCS file: www/site-look/footer.inc
diff -N www/site-look/footer.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ www/site-look/footer.inc	5 Mar 2007 21:56:29 -0000	1.2
@@ -0,0 +1 @@
+<p>Site Contents &copy;2007 <a href="http://www.nuug.no">NUUG</a>.</p>
\ No newline at end of file
Index: www/site-look/header.inc
===================================================================
RCS file: www/site-look/header.inc
diff -N www/site-look/header.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ www/site-look/header.inc	4 Mar 2007 15:04:35 -0000	1.1
@@ -0,0 +1,3 @@
+<div class="heading">
+<h1><?=$param['title']?></h1>
+</div>
\ No newline at end of file
Index: www/site-look/index.inc
===================================================================
RCS file: www/site-look/index.inc
diff -N www/site-look/index.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ www/site-look/index.inc	5 Mar 2007 21:56:29 -0000	1.2
@@ -0,0 +1,2 @@
+<h2>Memberships are open!</h2>
+<p>Click the Signup link to become a member of NUUG.</p>
\ No newline at end of file
Index: www/site-look/recover-password-form.inc
===================================================================
RCS file: www/site-look/recover-password-form.inc
diff -N www/site-look/recover-password-form.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ www/site-look/recover-password-form.inc	4 Mar 2007 15:04:35 -0000	1.1
@@ -0,0 +1 @@
+<p>If you are a member, and have forgotten your password, enter you email address below and we will mail you instructions on how to reset it. If you enter you email address and you are not a member, you will receive information on joining.</p>
\ No newline at end of file
Index: www/site-look/signup/responsibilities.inc
===================================================================
RCS file: www/site-look/signup/responsibilities.inc
diff -N www/site-look/signup/responsibilities.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ www/site-look/signup/responsibilities.inc	6 Mar 2007 08:24:36 -0000	1.3
@@ -0,0 +1,17 @@
+<h2>Membership Conditions / Responsibilities</h2>
+
+<ul>
+<li>In the interest of being a transparent and open organisation, a list of first and last names of members will be made public. By becoming a member, you agree to have this information made public.</li>
+<li>Your email address will be used as the method to contact you unless you
+    specifically notify the secretary
+    (<a href="mailto:sekretariat@nuug.no">sekretariat@nuug.no</a>)</li>
+<li>For the application to be constitutionally valid, the following process is used:</li>
+<ol>
+<li>You fill out this form</li>
+<li>You confirm the information on this form</li>
+<li>You confirm your application via email, which transmits the appropriate
+    form to the NUUG Board</li>
+<li>A member of the committee nominates you for membership and can accept your nomination</li>
+<li>You become a member, and are notified via email.</li>
+</ol>
+</ul>
Index: www/unauthenticated/signup.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/unauthenticated/signup.inc,v
retrieving revision 1.1.1.2
retrieving revision 1.6
diff -u -3 -p -r1.1.1.2 -r1.6
--- www/unauthenticated/signup.inc	9 Mar 2007 11:01:01 -0000	1.1.1.2
+++ www/unauthenticated/signup.inc	15 Nov 2007 23:44:16 -0000	1.6
@@ -12,6 +12,13 @@ headers(array('title'=>$title,'menu'=>1)
 
 $member= NULL;
 
+$countries = get_countries();
+$country_items = array();
+foreach ($countries as $c) {
+  $country_items[$c->code] = $c->name;
+}
+
+
 include 'forms/member.inc';
 
 echo '<div class="stdform">';
@@ -20,8 +27,8 @@ $errors = validate($form_editmember, $HT
 
 if(!$errors && strtolower($_SERVER['REQUEST_METHOD']) == 'post'
    && $_POST['signup']=='Apply for Membership') {
-  $dbh->query("begin");
-  $id = $dbh->nextId('public.members_id_seq');
+  $dbh->query('START TRANSACTION');
+//  $id = $dbh->nextId('public.members_id_seq');
 
   if($_POST['sex']=='Male')
     $sexval = 'M';
@@ -30,21 +37,32 @@ if(!$errors && strtolower($_SERVER['REQU
   else
     $sexval = ' ';
 
-  sql_insert('members',array('first_name'  => $_POST['first_name'],
+  $result = sql_insert('members',
+                       array('first_name'  => $_POST['first_name'],
 			     'middle_name' => $_POST['middle_name'],
 			     'last_name'   => $_POST['last_name'],
+                             'date_entered'=> date('Y-m-d H:i:s'),
 			     'dob'         => $_POST['dob'],
 			     'sex'         => $sexval,
 			     'address1'    => $_POST['address1'],
 			     'address2'    => $_POST['address2'],
+			     'address3'    => $_POST['address3'],
 			     'suburb'      => $_POST['suburb'],
 			     'postcode'    => $_POST['postcode'],
 			     'state'       => $_POST['state'],
-			     'country'     => $_POST['country'],
+			     'country_code'=> $_POST['country_code'],
 			     'email'       => $_POST['email'],
 			     'phone_home'  => $_POST['phone_home'],
 			     'phone_mobile'=> $_POST['phone_mobile']));
 
+  if (DB::isError($result)) {
+    my_syslog(LOG_ERR, 'insert into members failed: ' . $result->getMessage());
+    $dbh->query('ROLLBACK');
+    print '<p>Database operation failed.</p>';
+    print '</div>';
+    return;
+  }
+
   /* We don't need to quote anything
      in this query as it's all valid anyway (from the DB or subselect).
 
@@ -55,17 +73,31 @@ if(!$errors && strtolower($_SERVER['REQU
   $sql = "select LAST_INSERT_ID() as seq";
   $result = $dbh->query($sql);
   if(DB::isError($result)) {
-        my_syslog(LOG_INFO,$result->getMessage());
+    my_syslog(LOG_ERR, 'select last_insert_id() failed: ' . $result->getMessage());
+    $dbh->query('ROLLBACK');
+    print '<p>Database operation failed.</p>';
+    print '</div>';
+    return;
+
   }
   $row = $result->fetchrow(DB_FETCHMODE_OBJECT);
   $id = $row->seq;
   my_syslog(LOG_DEBUG,"member id seq: $id");
 
-  $sql = 'insert into org_members (org_id,member_id,member_type_id,start_date,expiry) values ('.$config['org_id'].",$id,(select id from member_types where type='Applied'),now(),now()+'1year')";
+  $sql = <<<EoQ
+      insert into org_members
+          (org_id, member_id, member_type_id, start_date, expiry)
+      values
+          ($config[org_id], $id, (select id from member_types where org_id = $config[org_id] and type='Applied'), now(), now()+'1year')
+EoQ;
   my_syslog(LOG_DEBUG,$sql);
   $result = $dbh->query($sql);
   if (DB::isError($result)) {
-    my_syslog(LOG_INFO,$result->getMessage());
+    my_syslog(LOG_ERR, 'insert into org_members failed: ' . $result->getMessage());
+    $dbh->query('ROLLBACK');
+    print '<p>Database operation failed.</p>';
+    print '</div>';
+    return;
   }
 
   $messagevars= array('ORG_NAME'=>$org->name,
@@ -80,10 +112,11 @@ if(!$errors && strtolower($_SERVER['REQU
 		       "Sex: ".$_POST['sex']."\r\n".
 		       "Address 1: ".$_POST['address1']."\n".
 		       "Address 2: ".$_POST['address2']."\n".
+		       "Address 3: ".$_POST['address3']."\n".
 		       "Suburb: ".$_POST['suburb']."\n".
 		       "Postcode: ".$_POST['postcode']."\n".
 		       "State: ".$_POST['state']."\n".
-		       "Country: ".$_POST['country']."\n".
+		       "Country: ".$_POST['country_code']."\n".
 		       "Email: ".$_POST['email']."\n".
 		       "Home Phone: ".$_POST['phone_home']."\n".
 		       "Mobile Phone: ".$_POST['phone_mobile']."\n\n",
@@ -93,7 +126,7 @@ if(!$errors && strtolower($_SERVER['REQU
 
   site_message('signup/application-request-for-confirm', $messagevars);
 
-  $dbh->query("commit");
+  $dbh->query('COMMIT');
 
   print '<p>You submitted the following information:</p>'.
     '<ul>'.
@@ -103,10 +136,11 @@ if(!$errors && strtolower($_SERVER['REQU
     '<li><b>Sex: </b>'.$_POST['sex'].'</li>'.
     '<li><b>Address 1: </b>'.$_POST['address1'].'</li>'.
     '<li><b>Address 2: </b>'.$_POST['address2'].'</li>'.
+    '<li><b>Address 3: </b>'.$_POST['address3'].'</li>'.
     '<li><b>Suburb: </b>'.$_POST['suburb'].'</li>'.
     '<li><b>Postcode: </b>'.$_POST['postcode'].'</li>'.
     '<li><b>State: </b>'.$_POST['state'].'</li>'.
-    '<li><b>Country: </b>'.$_POST['country'].'</li>'.
+    '<li><b>Country: </b>'.$country_items[$_POST['country_code']].'</li>'.
     '<li><b>Email Address: </b>'.$_POST['email'].'</li>'.
     '<li><b>Home Phone: </b>'.$_POST['phone_home'].'</li>'.
     '<li><b>Mobile Phone: </b>'.$_POST['phone_mobile'].'</li>'.
Index: www/unauthenticated/vendors.inc
===================================================================
RCS file: /home/cvs/nuug-memberdb/www/unauthenticated/vendors.inc,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -3 -p -r1.1.1.1 -r1.2
--- www/unauthenticated/vendors.inc	9 Mar 2007 11:01:01 -0000	1.1.1.1
+++ www/unauthenticated/vendors.inc	9 Mar 2007 19:27:14 -0000	1.2
@@ -1,7 +1,7 @@
 <?php
 $title = "Vendors";
 headers(array('title'=>$title,'menu'=>1,
-	      'head_extras'=>'<script language="Javascript" src="lib/sorttable.js"></script>',
+	      'head_extras'=>'<script type="text/javascript" src="lib/sorttable.js"></script>',
 	      )
 	);
 
