1<?php
2/**
3*
4* Database Management plugin: manages tables and column data
5*
6* The function action_db is called when the user has navigated to the database namespace
7*
8* @license  GPL 2 (http://www.gnu.org/licenses/gpl.html)
9* @author   Langham Associates <enquiries@langhamAssociates.com>
10* v1.0.4
11*/
12
13/*
14Description
15-----------
16A simple database management system which stores data in files in a
17namespace in the dokuwiki folder
18There is a new folder under dokuwiki named database (held as $dbName)
19This folder contains
20- a new "system" folder called "Create Table"
21- one folder per user table , e.g. "01-Countries" and "02-people"
22For any session, the record is assigned an id, which is the row number in the file
23
24User data is held in the folders with names of the form nn-Name , e.g.
2501-Countries
26
27When a user creates a table, a new folder is created, with the next sequence number assigned, e.g. 02-People
28
29Each folder holds two files and data within the files is pipe(|)-separated:
30  definition.txt holds the column definitions (see below)
31  data.txt holds the values in the columns
32
33There are two types of reference fields, where the field holds a pointer rather than a value.
34To implement this, there are two special types of column, over and above string , number etc.
35The concept of LOOKUP is that within one set of data (held in the language file) are held such things as gender
36If the column has type 101, this relates to the first row in the set, i.e. gender.
37The field then holds the number 1 or 2 to distinguish the value.
38The concept of REFERENCE is that a user table is defined with an ID as the first field.
39This will dynamically add it to the list of reference tables when a column is defined.
40If, for example, this was a table of countries, and a row with id = 1 and name = France existed,
41whenever a column was defined as referring to that table, if 1 was in the field, France would be displayed.
42The above is hidden from the user, since they only ever see the drop-downs showing France etc.
43
44
45The main objects are:
46Environment
47Session
48Table
49Column
50File
51Row
52Record
53
54
55Table consists of Columns
56File consists of Rows
57
58The data for a Table is held in a File
59
60When a row is read in from the file it is read into a Record
61
62Admin
63-----
64this involves treating definition.txt as a table with one record per column
65The elements in the definition of each column are
66name - a name for the column (not used)
67label - displayed on the screen
68mandatory - yes/no
69unique - yes/no - particularly need for reference tables for both the id
70and the value e.g. countries
71type - 1    String
72       2    Number
73       3    Date
74       101  Lookup Gender   )
75       102  Yes and No      ) more can be added in lang.php
76       103  Lookup Title    )
77       1001 Reference Countries (for example) - added when table created
78with unique numeric id as first column
79visible - yes/no
80
81N.B The following are not permitted in v1.0
82Removing tables
83Removing columns
84
85The following are not recommended :
86Deleting or amending records in reference tables
87
88V1.0.1
89------
90wild-cards in search
91Remove any HTML errors
92Correct sorting by reference column
93
94V1.0.2
95------
96language-enable the following
97- data types
98- Referring to other tables
99- lookups
100ensure compatibility with IE
101
102V1.0.3
103-------
1041. allow fields to be hidden , additional column attribute called visible defaults to Yes
105
1062. New link fields
107new field type INTERNAL_LINK
108   - format [[wiki:aa:bb|Displayed Name]]
109   - display_for_change displays above
110   - display function displays "Displayed Name" as href to link with id=wiki:aa:bb
111   - the string is stored with : instead of |
112new field type EXTERNAL_LINK
113   - format [[http://....... |Displayed Name]]
114   - display_for_change displays above
115   - display function displays "Displayed Name" as href to link
116   - the string is stored with : instead of |
117Must sort correctly on links last element
118
1193. optimistic locking - lost updates
120   - check each original field value against current contents
121   - if any not the same there must have been an intermediate update
122N.B To implement Lost updates it is necessary to store the original valueson
123the update form and the delete form, these are then passed to the update function
124
125New lang entries needed for
126lost_update
127external links
128hidden
129
130v1.0.4
131------
132Create the "Create table" folder if it is not there already
133This allows zip to hold only the code for database folder and to use Dokuwiki plugin manager
134*/
135
136/**
137* Environment Class
138*
139* Establishes the location of the files
140*
141*/
142class Environment {
143    var $URL;
144    var $TYPES=array();                 // the names of the types read from the lang file
145    var $NUMBER_OF_BASIC_TYPES = 5;     // the number of basic types string, number etc
146    var $TYPE_STRING = 1;
147    var $TYPE_NUMBER =2;
148    var $TYPE_DATE = 3;
149    var $TYPE_INTERNAL_LINK = 4;
150    var $TYPE_EXTERNAL_LINK = 5;
151    var $COLUMNS=array();             // holds the definition of the structure, e.g. column attributes
152    var $YES = 1;
153    var $LOOKUP_OFFSET = 100;         // the first set of lookups is 101
154    var $REFERENCE_OFFSET = 1000;     // the first reference table is 1001
155    var $TYPE_LOOKUP = 999;           // the column is of type lookup
156    var $DICTIONARY_FOLDER = "Create table";
157    var $DEBUG_FILE = "debug.txt";
158    var $WILD_CARD = '%';             // can be used at the start or end of a filter
159    var $COLUMN_SEPARATOR='|';        // used to separate fields when the data is written to the file
160    var $COLON = ':';                 // used to separate the elements in the link field
161    var $baseFolder ;
162    var $imagesFolder;
163    var $tableNames= array();         // the names of the tables
164/**
165*
166* Constructor
167*
168* @param    none
169* @returns  object
170*
171*/
172    function Environment () {
173        global $dbName;
174        global $myLang;
175// Set up the main data types
176        $this->TYPES[1]=getMyLang('type_string');
177        $this->TYPES[2]=getMyLang('type_number');
178        $this->TYPES[3]=getMyLang('type_date');
179        $this->TYPES[4]=getMyLang('type_internal_link');
180        $this->TYPES[5]=getMyLang('type_external_link');
181// The column parameters are set up here - the label comes from lang file
182        $this->COLUMNS['name'][1] ='name';
183        $this->COLUMNS['name'][2] ='label';
184        $this->COLUMNS['name'][3] ='mandatory';
185        $this->COLUMNS['name'][4] ='unique';
186        $this->COLUMNS['name'][5] ='type';
187        $this->COLUMNS['name'][6] ='visible';
188        $this->COLUMNS['mandatory'][1] =1;
189        $this->COLUMNS['mandatory'][2] =1;
190        $this->COLUMNS['mandatory'][3] =1;
191        $this->COLUMNS['mandatory'][4] =1;
192        $this->COLUMNS['mandatory'][5] =1;
193        $this->COLUMNS['mandatory'][6] =1;
194        $this->COLUMNS['unique'][1] =1;
195        $this->COLUMNS['unique'][2] =1;
196        $this->COLUMNS['unique'][3] =2;
197        $this->COLUMNS['unique'][4] =2;
198        $this->COLUMNS['unique'][5] =2;
199        $this->COLUMNS['unique'][6] =2;
200        $this->COLUMNS['type'][1] =1;
201        $this->COLUMNS['type'][2] =1;
202        $this->COLUMNS['type'][3] =102;
203        $this->COLUMNS['type'][4] =102;
204        $this->COLUMNS['type'][5] =999;
205        $this->COLUMNS['type'][6] =102;
206        $this->COLUMNS['visible'][1] =1;
207        $this->COLUMNS['visible'][2] =1;
208        $this->COLUMNS['visible'][3] =1;
209        $this->COLUMNS['visible'][4] =1;
210        $this->COLUMNS['visible'][5] =1;
211        $this->COLUMNS['visible'][6] =1;
212// Pick up the column label
213        for ($i=1;$i<= count($myLang['column_label']);$i++)
214            $this->COLUMNS['label'][$i] = getMyLang('column_label',$i);
215// The images oflder
216        $this->imagesFolder=DOKU_BASE.'lib/plugins/'.$dbName.'/images/';
217// The base folder
218        $this->baseFolder = DOKU_INC.'data/pages/'.$dbName.'/';
219        $this->URL = DOKU_URL.'doku.php?idx='.$dbName;
220// Begin add v1.0.4
221// Check if dictionary folder exists
222        $directoryName = $this->baseFolder.$this->DICTIONARY_FOLDER;
223        if (!file_exists($directoryName)) {
224	//			echo "creating $this->baseFolder";
225          mkdir("$this->baseFolder",0777);
226//          echo ("creating $this->baseFolder$this->DICTIONARY_FOLDER");
227          mkdir("$this->baseFolder$this->DICTIONARY_FOLDER",0777);
228			}
229// End add v1.0.4
230
231// Get list of table names excluding Dictionary
232        if ($handle = opendir($this->baseFolder)) {
233            while (false !== ($file = readdir($handle))) {
234                if ($file != "." && $file != ".." && $file != $this->DICTIONARY_FOLDER) {
235                    $counter = abs(substr($file,0,strpos($file,'-')));
236                    $this->tableNames[$counter] = substr($file,3);
237                }
238            }
239            closedir($handle);
240        }
241    }
242/**
243* gets the folder name for a table
244*
245* @param    number     $p_id the id of the table
246* @returns  string     the folder name
247*
248*/
249    function get_folder($p_id) {
250        $ret = $this->baseFolder;
251        if ($p_id<10 ) $id='0'.$p_id;
252        else $id = $p_id;
253// form the name of the folder based on <nn>-<name>
254        $ret .= $id."-".$this->tableNames[$p_id]."/";
255        return $ret;
256    }
257}
258/**
259* Session Class
260*
261* Holds session-specific information
262*
263*/
264class Session {
265    var $columnTypeList = array();
266    var $lookupTitles = array();
267    var $lookupOptions = array();
268/**
269* Constructor
270*
271* Establishes the location of the files and any lookups
272*
273*/
274    function Session() {
275        global $display;
276        global $environment;
277        global $auditTrail;
278        global $session;
279        global $myLang;
280// Get list of column types
281        $auditTrail->add_header("Session");
282        for ($i=1;$i<=$environment->NUMBER_OF_BASIC_TYPES;$i++) {
283            $this->columnTypeList[$i] = $environment->TYPES[$i];
284        }
285// Get the list of lookups
286        $auditTrail->add("Lookups");
287// the lookups are in myLang$['lookup']
288// Set up lookupTitles, lookupOptions and columnTypeList
289        for ($lookupCount=1; $lookupCount<=count($myLang['lookup']);$lookupCount++) {
290            $this->lookupTitles[$lookupCount] = getMyLang('lookup',$lookupCount,1);
291            $this->columnTypeList[$environment->LOOKUP_OFFSET+$lookupCount]
292                  = getMyLang('prompt_looking_up').' '.getMyLang('lookup',$lookupCount,1);
293            for ($optionCount=2;$optionCount<=count($myLang['lookup'][$lookupCount]);$optionCount++) {
294                $this->lookupOptions[$lookupCount][$optionCount-1] = getMyLang('lookup',$lookupCount,$optionCount);
295            }
296        }
297        $counter=0;
298// Get the list of reference tables
299        $auditTrail->add("reference Tables");
300        foreach ($environment->tableNames as $tableName) {
301            $counter++;
302            $tab = new Table($counter);
303            if ($tab->possibleReference) {
304               $this->columnTypeList[$environment->REFERENCE_OFFSET+$counter]
305                       =getMyLang('prompt_referencing').' '.$tableName;
306                 $auditTrail->add("reference Table ".$tableName);
307            }
308        }
309        $auditTrail->add_footer("Session");
310    }
311/**
312* Determines whether or not the user has the necessary permissions to perform the action
313*
314* @param    string     $action - the action being taken
315* @returns  boolean    true if the user has the necessary permissions
316*                      (not convinced that the perms are being used correctly (should be binary mask)
317*
318*/
319    function allowed ($action) {
320        global $auditTrail;
321        $INFO = pageinfo();            // picked up from the dokuwiki environment
322        $perm = $INFO['perm'];         // the permissions of the current user
323        $auditTrail->add_header('Session->allowed');
324// Decide on the level of permission required
325        if ($action== 'insertForm')   $requiredLevel = AUTH_CREATE;
326        if ($action== 'insertAction') $requiredLevel = AUTH_CREATE;
327        if ($action== 'updateForm')   $requiredLevel = AUTH_EDIT;
328        if ($action== 'updateaction') $requiredLevel = AUTH_EDIT;
329        if ($action== 'createTable')  $requiredLevel = AUTH_ADMIN;
330        if ($action== 'createTableForm')  $requiredLevel = AUTH_ADMIN;
331        if ($action== 'admin')        $requiredLevel = AUTH_ADMIN;
332        if ($action== 'deleteForm')   $requiredLevel = AUTH_DELETE;
333        if ($action== 'deleteAction') $requiredLevel = AUTH_DELETE;
334        $auditTrail->add('requiredLevel = '.$requiredLevel);
335        $auditTrail->add('perm = '.$perm);
336// Decide whether or not to procede
337        $auditTrail->add_footer('Session->allowed');
338        if ($perm >= $requiredLevel)
339           return true;
340        else
341           return false;
342    }
343/**
344* Get the list of data type including lookups and references
345*
346* @param    string     $value     the selected value which will be marked as selected
347* @param    string     $name      e.g. "values" so that the names of the variables are values0 values1 etc
348* @param    string     $type      if "search" the No Selection is added
349* @returns  array      list of options for data type
350*
351*/
352    function list_of_types($value, $name, $type) {
353        global $environment;
354        global $myLang;
355        $list = "<select name='".$name."'>";
356// For a search we need the No Selection option first
357        if ($type=="search") {
358            $list .= "<option value='' >";
359            $list .= getMyLang('prompt_no_selection') ;
360            $list .= "</option>";
361        }
362// The Basic types
363        for ($i=1;$i<=$environment->NUMBER_OF_BASIC_TYPES;$i++) {
364            $list .= "<option value = '".$i."'";
365            if ($value == $i) $list .= " selected = 'selected' ";
366            $list .=">".$environment->TYPES[$i]."</option>";
367        }
368        $lookups = $this->lookupTitles;;
369        $counter = $environment->LOOKUP_OFFSET;
370// The lookups
371        foreach ($lookups as $lookup) {
372            $counter +=1;
373            $list .= "<option value = '".$counter."'";
374            if ($value == $counter) $list .= " selected ='selected' ";
375            $list .= '>'.getMyLang('prompt_looking_up').' '.$lookup.'</option>';
376        }
377// The reference tables
378        for ($ptr =1;$ptr <=count($environment->tableNames);$ptr++) {
379            $tab = new Table($ptr);
380            if ($tab->possibleReference) {
381                $counter=$ptr+$environment->REFERENCE_OFFSET;
382                $list .= "<option value = '".$counter."'";
383                if ($value == $counter) $list .= " selected ='selected' ";
384
385                $list .= '>'.getMyLang('prompt_referencing').' '.$tab->name.'</option>';
386            }
387        }
388        $list .= "</select>";
389        return $list;
390    }
391}
392/**
393* Messages Class
394*
395* Record and display the messages
396*
397*/
398class Messages {
399    var $messageList = array();
400    var $exist = false;
401    function Messages () {
402        $this->exist = false;
403    }
404/**
405* add the message to the list
406*
407* @param    string     $p_message    the error message
408* @returns  None
409*
410*/
411    function add($p_message) {
412        $this->messageList[] = $p_message;
413        $this->exist = true;
414    }
415/**
416* display the messages
417*
418* @param    none
419* @returns  none
420*
421*/
422    function fail_soft () {
423        global $auditTrail;
424/* Uncomment if debugging
425        $auditTrail->display();
426*/
427   }
428/**
429* display all the messages
430*
431* @param    None
432* @returns  None
433*
434*/
435    function displayAll() {
436        global $display;
437        $display->output("<table border=1>");
438        foreach($this->messageList as $message) {
439            $display->output("<tr><td align=center>".$message."</td></tr>");
440        }
441        $display->output("</table>");
442    }
443}
444/**
445* AuditTrail Class
446*
447* accumulates and formats audit trail info
448*
449*/
450class AuditTrail {
451    var $messageList = array();
452    var $indent;
453/**
454* Constructor
455*
456* @param    none
457* @returns  object
458*
459*/
460    function AuditTrail () {
461        $this->indent=0;
462    }
463/**
464* add a message to the audit trail
465*
466* @param    string     $p_message   the message
467* @returns  none
468*
469*/
470    function add($p_message) {
471        global $display;
472        $this->messageList[] = $this->tab().$p_message;
473        $display->debug_output($this->tab().$p_message);
474    }
475/**
476* add a header to the audit trail
477*
478* @param    string     $p_message   the message
479* @returns  none
480*
481*/
482    function add_header($p_message) {
483        $this->indent++;
484        $this->add("<--- Start of ". $p_message."-->");
485    }
486/**
487* add a footer to the audit trail
488*
489* @param    string     $p_message   the message
490* @returns  none
491*
492*/
493    function add_footer($p_message) {
494        $this->add("<--- End of ". $p_message."-->");
495        $this->indent--;
496    }
497/**
498* calculate the indentation for the audit trail
499*
500* @param    string     $p_message   the message
501* @returns  none
502*
503*/
504    function tab() {
505        $t='';
506        for ($i=0;$i<$this->indent;$i++)
507            $t .= '>>';
508        return $t;
509    }
510/**
511* display the messages in the audit trail
512*
513* @param    string     $p_message   the message
514* @returns  none
515*
516*/
517    function display() {
518        echo "<BR>Audit Trail";
519        foreach($this->messageList as $message)
520            echo "<BR>".$message;
521        echo "<BR>";
522    }
523}
524/**
525* Column Class
526*
527* holds info on a column in a table
528*
529*/
530class Column {
531    var $id;
532    var $name;
533    var $label;
534    var $mandatory;
535    var $unique;
536    var $type;
537    var $visible;
538    var $isVisible;
539    var $allowNoSelection;
540/**
541*
542* Constructor
543*
544* @param    number $p_id         id of the column
545* @param    string $p_name       name of the column
546* @param    string $p_label      label for the column
547* @param    number $p_mandatory  lookup , 1=yes, 2= no
548* @param    string $p_type       type of the column, string, number, date, lookup or reference
549* @param    string $p_visible    visible , 1=yes, 2= no
550* @returns  object
551*
552*/
553    function Column ($p_id, $p_name, $p_label, $p_mandatory, $p_unique, $p_type, $p_visible) {
554        global $display;
555        global $environment;
556        global $auditTrail;
557        $auditTrail->add_header('Creating column '.$p_name);
558
559        $this->id        = $p_id;
560        $this->name      = $p_name;
561        $this->label     = $p_label;
562        $this->mandatory = $p_mandatory;
563        $this->unique    = $p_unique;
564        $this->type      = $p_type;
565// allowNoSelection is the equivalent of optional
566        if ($this->mandatory == $environment->YES) $this->allowNoSelection=false;
567        else $this->allowNoSelection=true;
568// set up isVisible
569        $this->visible   = $p_visible;
570        if ($this->visible == $environment->YES) $this->isVisible = true;
571        else $this->isVisible = false;
572
573        $auditTrail->add_footer('Creating column '.$p_name);
574    }
575/**
576* return the string to display the column for edit
577*
578* @param    string     $value   the value to be displayed for edit
579* @returns  string     the HTML to be displayed
580*
581*/
582    function display_for_change($value='',$type='search') {
583        global $environment;
584        global $session;
585        global $auditTrail;
586//The output used for the search in the list and for update and create
587        $auditTrail->add_header('Column - display_for_change column = '.$this->name);
588        $name = "values".$this->id;
589        $ret='';
590//Convert the form held on the database to the form for display and change
591        $value = $this->fromDatabase($value);
592// When searching, you can have "No selection"
593        if ($type=='search') $noSelectionAllowed = true;
594        else $noSelectionAllowed = $this->allowNoSelection; // true if not mandatory
595
596        if ($this->type==$environment->TYPE_LOOKUP) {
597            $auditTrail->add('returning list_of_types');
598            $ret=$session->list_of_types($value, $name,$type);
599        }
600        elseif ($this->type==$environment->TYPE_STRING
601               || $this->type==$environment->TYPE_NUMBER
602               || $this->type==$environment->TYPE_DATE
603               || $this->type==$environment->TYPE_INTERNAL_LINK
604               || $this->type==$environment->TYPE_EXTERNAL_LINK) {
605            $ret .= '<input type="text" class="edit" name="'.$name.'"';
606            if ($value > ' ')
607                $ret .= ' value="'.$value.'"';
608            $ret.=' />';
609        }
610        elseif ($this->type>$environment->LOOKUP_OFFSET && $this->type<$environment->REFERENCE_OFFSET) {
611// It is a lookup
612            $list=$session->lookupOptions[$this->type - $environment->LOOKUP_OFFSET];
613            $ret .= $this->drop_down($name,$list,$noSelectionAllowed,$value);
614        }
615        elseif ($this->type>$environment->REFERENCE_OFFSET) {
616//It is a reference table
617            $referenceTable = new Table($this->type - $environment->REFERENCE_OFFSET,"data");
618//get all the records
619            $referenceTable->get_all();
620// put items in list
621            $recCounter=0;
622            $listOfValues=array();
623            foreach($referenceTable->recordObjs as $record) {
624                $recCounter++;
625                $auditTrail->add('Reference table = '.$record->values[1]);
626                $listOfPointers[$recCounter] = $record->values[1];
627                $listOfValues[$recCounter]=$record->values[2];
628            }
629            $sortedListObj = new SortedArray($listOfValues);
630            $sortedList = $sortedListObj->sortedList;
631            $counter = 0;
632            foreach($sortedList as $ptr) {
633                $counter++;
634                $sortedListOfPointers[$counter] = $listOfPointers[$ptr];
635                $sortedListOfValues[$counter] = $listOfValues[$ptr];
636            }
637            $ret .= $this->drop_down($name,$sortedListOfValues,$noSelectionAllowed,$value,$sortedListOfPointers);
638        }
639        $auditTrail->add_footer('column - display_for_change');
640        return $ret;
641    }
642/**
643* return the string to display the original value on an edit form as a hidden field
644*
645* @param    string     $value   the value of the field
646* @returns  string     the HTML to display
647*
648*/
649    function display_original($p_counter, $p_value) {
650        $fieldName = "originals".$p_counter;
651        $ret = '<input type="hidden" name="'.$fieldName.'" value="'.$p_value.'">';
652        return $ret;
653    }
654
655/**
656* prepare the field for inserting into/updating the database
657*
658* @param    string     $value   the value of the field
659* @returns  string
660*
661*/
662    function toDatabase($value) {
663        global $environment;
664        global $messages;
665        $ret = $value; // default
666// No | within field unless link
667        $posOfPipe = strpos($value, $environment->COLUMN_SEPARATOR);
668// Now test if there is a pipe character
669        if ($posOfPipe === false) {
670// If there is no pipe and this is a link then there is a problem
671            if ($this->type == $environment->TYPE_INTERNAL_LINK
672               || $this->type == $environment->TYPE_EXTERNAL_LINK) {
673                       $messages->add($this->label.getMyLang('err_dat_missing_pipe').$value);
674            }
675        } else {
676// But if there is a pipe and this is not a link then there is a problem
677            if ($this->type != $environment->TYPE_INTERNAL_LINK
678               && $this->type != $environment->TYPE_EXTERNAL_LINK) {
679                $messages->add($this->label.getMyLang('err_dat_extra_pipe').$value);
680            }
681        }
682// The display format of a link is either aaa:bbb:ccc|linkName or http://www...|linkName        }
683// but the db version replaces the | with :
684        if ($this->type == $environment->TYPE_INTERNAL_LINK) {
685// convert the | into :
686            $ret = str_replace( $environment->COLUMN_SEPARATOR, $environment->COLON, $value);
687        }
688        if ($this->type == $environment->TYPE_EXTERNAL_LINK) {
689// convert the | into :
690           $ret = str_replace( $environment->COLUMN_SEPARATOR, $environment->COLON, $value);
691        }
692        return $ret;
693    }
694/**
695* convert the field from its form in the database
696*
697* @param    string     $value   the value of the field
698* @returns  string
699*
700*/
701    function fromDatabase($value) {
702        global $environment;
703        $ret = $value;
704        if ($this->type == $environment->TYPE_INTERNAL_LINK
705           || $this->type == $environment->TYPE_EXTERNAL_LINK) {
706// convert the last : into |
707            if ($value>'') {
708                $ptr=strrpos ($value,$environment->COLON); // the last colon
709                $ret = substr($value,0,$ptr).$environment->COLUMN_SEPARATOR.substr($value,$ptr+1);
710            }
711        }
712        return $ret;
713    }
714/**
715* validate the value being entered for a field
716*
717* @param    string     $value     the value of the field
718* @param    object     $messages  the messages object
719* @returns  none       but message added if not valid
720*
721*/
722    function validate ($value, $messages) {
723        global $environment;
724        global $myLang;
725        global $conf;
726// Number
727        if ($this->type == $environment->TYPE_NUMBER
728              || $this->type > $environment->NUMBER_OF_BASIC_TYPES) {
729            if (!is_numeric($value)) {
730                $messages->add($this->label.getMyLang('err_dat_typ_num').$value);
731            }
732        }
733// Date
734        if ($this->type == $environment->TYPE_DATE) {
735            if ($value>'' ) {
736                $day=substr($value,8,2);
737                $month = substr($value,5,2);
738                $year = substr($value,0,4);
739                if (substr($value,4,1) !='/'
740                 ||substr($value,7,1) !='/'
741                 ||!checkdate($month,$day,$year))
742                    $messages->add($this->label.getMyLang('err_dat_typ_dat').$value);
743            }
744        }
745// Internal Link
746        if ($this->type == $environment->TYPE_INTERNAL_LINK) {
747            if (substr($value,0,2) != '[['
748               || substr($value,strlen($value)-2) != ']]'
749               || strpos($value, $environment->COLUMN_SEPARATOR) === false
750               ) {
751                $messages->add($this->label.getMyLang('err_dat_typ_int').$value);
752            }
753        }
754// External Link
755        if ($this->type == $environment->TYPE_EXTERNAL_LINK) {
756            if (substr($value,0,2) != '[['
757               || substr($value,strlen($value)-2) != ']]'
758               || strpos($value, $environment->COLUMN_SEPARATOR) === false
759               ) {
760                 $messages->add($this->label.getMyLang('err_dat_typ_ext').$value);
761            }
762        }
763// Mandatory
764        if ($this->mandatory==$environment->YES) {
765            if ($value=='') {
766                $messages->add($this->label.getMyLang('err_dat_typ_man'));
767            }
768        }
769    }
770/**
771* display the value of a field
772*
773* @param    string     $value   the value of the field
774* @returns  string
775*
776*/
777    function display($value) {
778//The output used for the display only of values
779        global $environment;
780        global $session;
781        global $conf;
782// String or number
783        if ($this->type==$environment->TYPE_STRING
784          ||$this->type==$environment->TYPE_NUMBER)
785            $ret = $value;
786// Date
787        elseif ($this->type==$environment->TYPE_DATE) {
788/*
789            $dateFormat = $conf['dformat'];
790            $year = substr($value,0,4);
791            $month = substr($value,5,2);
792            $day  = substr($value,8,2);
793            $dateAndTime = mktime(0,0,0,$month,$day,$year);
794            $ret = date($dateFormat, $dateAndTime);
795*/
796            $ret = $value;
797        }
798// Internal Link
799        elseif ($this->type==$environment->TYPE_INTERNAL_LINK) {
800            $elements=explode($environment->COLON, $value);
801            if ($value>'') {
802                $last=count($elements)-1;
803                $name=$elements[$last];
804
805                $ptr=strrpos ($value,$environment->COLON); // the last colon
806                $ret ='<a href="'.DOKU_URL.'doku.php?id='.substr($value,2,$ptr-2).'">'.substr($name,0,strlen($name)-2).'</a>';
807            }
808        }
809// External Link
810        elseif ($this->type==$environment->TYPE_EXTERNAL_LINK) {
811            $elements=explode($environment->COLON, $value);
812            if ($value>'') {
813                $last=count($elements)-1;
814                $name=$elements[$last];
815                $ptr=strrpos ($value,$environment->COLON); // the last colon
816                $ret ='<a href="'.substr($value,2,$ptr-2).'">'.substr($name,0,strlen($name)-2).'</a>';
817            }
818        }
819// Lookup of column types (admin)
820        elseif ($this->type == $environment->TYPE_LOOKUP) {
821            $ret = $session->columnTypeList[$value];
822        }
823// Lookup
824        elseif ($this->type>$environment->LOOKUP_OFFSET && $this->type<$environment->REFERENCE_OFFSET) {
825            $list=$session->lookupOptions[$this->type - $environment->LOOKUP_OFFSET];
826            if ($value=='') $ret='';
827            else $ret = $list[$value];
828        }
829// Reference
830        elseif ($this->type>$environment->REFERENCE_OFFSET) {
831//Open the reference table
832            $referenceTable = new Table($this->type - $environment->REFERENCE_OFFSET,"data");
833//get all the records
834            $referenceTable->get_all();
835// put items in list
836            $recCounter=0;
837            foreach($referenceTable->recordObjs as $record) {
838                $recCounter++;
839                if ($value == $record->values[1])
840                    $ret = $record->values[2];
841            }
842        }
843        return $ret;
844    }
845/**
846* construct the drop-down list for a field
847*
848* @param    string     $name            the name of the column
849* @param    array      $values          the list of values of the field
850* @param    boolean    $plusZero        whether a zero value (No Selection) should be added
851* @param    object     $p_value         the selected value
852* @param    array      $listOfPointers  reference tables must have id as first field - this is the list of ids
853* @returns  string     the HTML for the list
854*
855*/
856    function drop_down($name,$values,$plusZero=false,$p_value='',$listOfPointers='') {
857        global $myLang;
858        $ret = '';
859        $ret .= "<select name='".$name."'>";
860        $counter=0;
861// Is No Selection to be displayed at the top of the list
862        if ($plusZero) {
863            $ret .= "<option value='' >";
864            $ret .= getMyLang('prompt_no_selection') ;
865            $ret .= "</option>";
866        }
867        if (is_array($values)) {
868// The order of the values is determined by $listOfPointers if it exists
869            foreach($values as $value) {
870                $counter++;
871                $ret .= "<option value='";
872                if (is_array($listOfPointers))
873                    $itemPointer = $listOfPointers[$counter];
874                else
875                    $itemPointer =$counter;
876                $ret .=$itemPointer. "'";
877                if ($p_value!='' && $p_value==$itemPointer)
878                    $ret .= " selected ";
879                $ret .= ">";
880                $ret .= $value;
881                $ret .= "</option>";
882             }
883        }
884        $ret .= "</select>";
885        return $ret;
886    }
887}
888/**
889* Table Class
890*
891* holds the information on a table
892*
893*/
894class Table {
895    var $id;
896    var $displayId;
897    var $isAdmin;
898    var $name;
899    var $columnObjs = array();
900    var $recordObjs = array();
901    var $populated = false;
902    var $possibleReference = false;
903/**
904* Constructor
905*
906* @param    number/string $p_idOrName   id of the table if it already
907exists or name if it is to be created
908* @returns  object
909*
910*/
911    function Table ($p_idOrName) {
912        global $display;
913        global $environment;
914        global $messages;
915        global $auditTrail;
916        global $session;
917        global $myLang;
918        $auditTrail->add_header("Table");
919        $auditTrail->add("param = ".$p_idOrName);
920// The id is passed so this is an existing table
921        if (is_numeric($p_idOrName)) {
922            $p_id = $p_idOrName;
923            $this->displayId = $p_id;
924            $this->id= abs($p_id);
925// Get the folder name with this id
926            if (abs($this->id)>count($environment->tableNames)) {
927                $messages->add('TableDefinition : id too large');
928                $messages->fail_soft();
929            }
930            $display->debug_output ("creating table with id=".$this->id);
931            $this->name     = $environment->tableNames[abs($p_id)];
932            if ($p_id<0) {
933                $this->isAdmin=true;
934                $auditTrail->add('administering the table');
935            } else {
936                $this->isAdmin=false;
937                $auditTrail->add('standard table');
938            }
939// If this is admin definition then get from lang and hard-code
940            if ($this->isAdmin) {
941                for ($i=1;$i<=count($environment->COLUMNS['label']);$i++) {
942                    $this->columnObjs[$i] = new Column($i
943                                                        , $environment->COLUMNS['name'][$i]
944                                                        , $environment->COLUMNS['label'][$i]
945                                                        , $environment->COLUMNS['mandatory'][$i]
946                                                        , $environment->COLUMNS['unique'][$i]
947                                                        , $environment->COLUMNS['type'][$i]
948                                                        , $environment->COLUMNS['visible'][$i]
949                                                        );
950                }
951
952            } else {
953                $this->columnObjs = $this->get_columns();
954            }
955// Could this be a reference table
956            if ($this->columnObjs[1]->type==$environment->TYPE_NUMBER
957               && $this->columnObjs[1]->unique==$environment->YES) {
958                $auditTrail->add($this->name." is a reference table ");
959                $this->possibleReference = true;
960            } else {
961                $auditTrail->add($this->name." is not a reference table ");
962                $this->possibleReference = false;
963            }
964// Create Table option taken
965       }elseif ($p_idOrName == $environment->DICTIONARY_FOLDER ) {
966           $this->name = $environment->DICTIONARY_FOLDER;
967           $this->displayId = 0;
968           $this->id=0;
969// No parameter passed
970       } elseif ($p_idOrName == '') {
971           $messages->add("Invalid null table");
972           $messages->fail_soft();
973       } else {
974// This is a new table so create the folder and then the two files
975            $tableName = $p_idOrName;
976// Check that the name is different
977            $alreadyExists = false;
978            if (in_array($tableName,$environment->tableNames ,true))
979                $alreadyExists = true;
980            if (!$alreadyExists) {
981                $tableId = count($environment->tableNames)+1;
982                $this->id=$tableId;
983                $this->isAdmin=false;
984                if ($tableId<10) $id='0'.$tableId;
985                else $id = $tableId;
986                $directoryName = $environment->baseFolder.$id.'-'.$tableName;
987                mkdir($directoryName,0777);
988                $environment = new Environment();
989                $file = new File($this,"data");
990                $file->create_file();
991                $file = new File($this,"definition");
992                $file->create_file();
993            } else
994                $messages->add($tableName.getMyLang('err_tab_nam'));
995        }
996        $auditTrail->add_footer("Table");
997    }
998/**
999* check if the row being inserted has been changed by another user
1000*
1001* @param    number  $recordPointer    the record in question
1002* @returns  boolean true if changed
1003*
1004*/
1005    function hasChanged($p_id, $p_originals) {
1006        global $environment;
1007        global $messages;
1008        global $myLang;
1009// Get the current database values
1010        $this->get_all();
1011// check all the original fields in this record
1012        $currentRecord =$this->get($p_id);
1013        $match=true;
1014// Compare $currentRecord->values with $p_originals
1015        $counter=0;
1016        foreach ($currentRecord->values as $currentValue) {
1017// check each field
1018            $counter++;
1019            if ($currentValue != $p_originals[$counter]) {
1020                $match=false;
1021            }
1022        }
1023// if the record does not match then return true - it has changed
1024        if (!$match) {
1025             $messages->add("****** ".getMyLang('err_lost_update') ." ******");
1026             return true;
1027        } else {
1028// no problem so return false i.e. nothing has changed
1029            return false;
1030        }
1031    }
1032/**
1033* check if the row being inserted is unique or breaks the uniqueness of columns shown as unique
1034*
1035* @param    array   $values         the values in the record
1036* @param    number  $recordPointer  the record in question
1037* @returns  boolean true if unique
1038*
1039*/
1040    function isUnique($values,$recordPointer=0) {
1041        global $environment;
1042        global $messages;
1043        global $myLang;
1044        if ($recordPointer == 0)
1045            $thisMode="create";
1046        else
1047            $thisMode="update";
1048// populate if not already
1049        if (!$this->populated) $this->get_all();
1050        $recordCounter = 0;
1051// check all the fields in each record
1052        foreach ($this->recordObjs as $record) {
1053            $recordCounter++;
1054            $match=true;
1055            $counter=0;
1056            foreach ($record->values as $value) {
1057// when updating - do not check the record itself
1058                if ($recordPointer!=$recordCounter) {
1059                    $counter++;
1060// check each field
1061                    if ($value != $values[$counter]) {
1062                        $match=false;
1063                    }
1064                } else
1065                    $match=false;
1066// now check the unique columns
1067                if ($this->columnObjs[$counter]->unique == $environment->YES) {
1068                   if ($values[$counter] == $value && $recordPointer != $recordCounter) {
1069                       $messages->add('****** '.$this->columnObjs[$counter]->label." : ".getMyLang('err_dup_val') .' ******');
1070                       return false;
1071                   }
1072                }
1073            }
1074// as soon as a record matches then return false
1075            if ($match) {
1076                $messages->add("****** ".getMyLang('err_dup_rec') ." ******");
1077                return false;
1078            }
1079        }
1080// no match so return true
1081        return true;
1082    }
1083/**
1084* get the columns for this table
1085*
1086* @param    none
1087* @returns  array  the columns
1088*
1089*/
1090    function get_columns () {
1091        global $auditTrail;
1092        global $environment;
1093        $auditTrail->add_header('get_columns');
1094        $definitionFile = new File($this, "definition");
1095        $definitionFile->read_buffer();
1096        $rowObjs=$definitionFile->rowObjs;
1097        $counter = 0;
1098        $columns = array();
1099        foreach ($rowObjs as $rowObj) {
1100            $counter = $counter + 1;
1101            $fields = $rowObj->fields;
1102// Check on the visible, which may not be populated
1103            if ($fields[6] == '') $fields[6] = $environment->YES;
1104            if ($rowObj->raw>'')
1105                $columns[$counter] =
1106                  new Column($counter, $fields[1],$fields[2],$fields[3],$fields[4], $fields[5], $fields[6]);
1107        }
1108        $auditTrail->add_footer('get_columns');
1109        return $columns;
1110    }
1111/**
1112* filter the values in the table according to the values entered on the form
1113*
1114* @param    array     $filters      array of filters to be applied
1115* @param    array     $sortedList   the sorted list
1116* @returns  array of strings
1117*
1118*/
1119    function filter($filters,$sortedList) {
1120        $recCounter=0;
1121        $listCounter=0;
1122        global $display;
1123        global $environment;
1124        $filterExists    = array();
1125        $wildCardExists  = array();
1126        $wildCardAtStart = array();
1127        $wildCardAtEnd   = array();
1128
1129        $display->debug_output("In filter recCounter= ".count($this->recordObjs));
1130        $list=array();
1131// Which fields should we look at?
1132        for ($colCounter=1; $colCounter<=count($this->columnObjs);$colCounter++) {
1133// Look at each field
1134            $type = $this->columnObjs[$colCounter]->type;
1135            $filterValue = $filters[$colCounter];
1136            $filterExists [$colCounter] = false;
1137// check if there is a filter value for it
1138            if ($filterValue > ' ' ) {
1139                $filterExists [$colCounter] = true;
1140                if ($type= $environment->TYPE_STRING) {
1141                    $posOfWildCard = strpos($filterValue, $environment->WILD_CARD);
1142                    if ($posOfWildCard === false) $wildCardsExist [$colCounter] = false;
1143                    else {
1144                        $wildCardsExist [$colCounter]   = true;
1145                        $wildCardAtStart [$colCounter]  = false;
1146                        $wildCardAtEnd  [$colCounter]   = false;
1147// wildcard either at start or end or both
1148                        if ($posOfWildCard == 0) {
1149                            $wildCardAtStart [$colCounter] = true;
1150                        }
1151                        $posOfWildCard = strpos($filterValue, $environment->WILD_CARD,1);
1152                        if ($posOfWildCard === false) $wildCardAtEnd [$colCounter] = false;
1153                        else $wildCardAtEnd [$colCounter] = true;
1154                    }
1155                }
1156            }
1157        }
1158// The list has already been sorted so we must filter the sorted list
1159        for( $recCounter=1; $recCounter<=count($this->recordObjs); $recCounter++) {
1160// Pick the item from the sorted list
1161            $recordObj = $this->recordObjs[$sortedList[$recCounter]];
1162            $display->debug_output($recordObj->values[1]);
1163// Assume the row will be included unless it fails any of the filters
1164            $include=true;
1165// Test each value
1166            for ($colCounter=1;$colCounter<=count($this->columnObjs);$colCounter++) {
1167                $type = $this->columnObjs[$colCounter]->type;
1168                $ignore = false;
1169                if ($filterExists [$colCounter] ) {
1170                    $value = $recordObj->values[$colCounter];
1171// Offset fields with a value of zero are ignored since no selection has been made
1172                    if ($value==0 && $type>$environment->LOOKUP_OFFSET) {
1173                        $ignore = true;
1174                    }
1175// As soon as any field fails the test we break out of the loop round the fields
1176                    if (!$ignore) {
1177                        if ($wildCardsExist[$colCounter]) {
1178                            $filterValue = str_replace($environment->WILD_CARD,'',$filters[$colCounter]);
1179                            $pos = strpos($value, $filterValue);
1180                            if ($pos===false) {
1181                                $include = false;
1182                                break;
1183                            } else {
1184//There is a wildcard and the string has been found but is it in the right place
1185                                if ($wildCardAtEnd[$colCounter]) {
1186// If the wildcard is at the end then the find must be zero
1187                                    if ($pos!=0) {
1188                                        $include = false;
1189                                        break;
1190                                    }
1191                                }
1192// if the wildcard is at the start then the last find must be at the end
1193// the search is in restToCheck then the last find is in restOfString
1194                                if ($wildCardAtStart[$colCounter]) {
1195                                    $restToCheck = $value;
1196                                    $nextPos = $pos;
1197                                    while ($nextPos!=false) {
1198                                        $pos = $nextPos;
1199                                        $restOfString = substr($restToCheck, $nextPos);
1200                                        $restToCheck = substr($restToCheck, $nextPos+1);
1201                                        $nextPos= strpos($restToCheck, $filterValue);
1202                                    }
1203                                    if ($restOfString != $filterValue) {
1204                                        $include = false;
1205                                        break;
1206                                    }
1207                                }
1208                            }
1209
1210                        } else {
1211// No wildcards so the test is simple - and if one fails we break out of the loop
1212                            if ($filters[$colCounter] != $value) {
1213                                $include=false;
1214                                break;
1215                            }
1216                        }
1217                    }
1218                }
1219            }
1220            if ($include) {
1221                $listCounter++;
1222                $list[$listCounter] = $recCounter;
1223                $display->debug_output("Adding record ".$recCounter);
1224            }
1225        }
1226        return $list;
1227    }
1228/**
1229* get all the records in this table
1230*
1231* @param    none
1232* @returns  row objects
1233*
1234*/
1235    function get_all () {
1236        global $display;
1237        global $auditTrail;
1238        $auditTrail->add_header('get_all for table '.$this->name);
1239        $this->populated = true;
1240        $dataFile = new File($this,"data");
1241        $dataFile->read_buffer();
1242        $recCounter=0;
1243        $display->debug_output( "The contents of the table ".$this->name."=". $dataFile->buf);
1244        foreach($dataFile->rowObjs as $rowObj) {
1245            $recCounter=$recCounter + 1;
1246            $this->recordObjs[$recCounter] = new Record($this,$recCounter,$rowObj->fields);
1247        }
1248        $auditTrail->add_footer('get_all for table '.$this->name);
1249        return $dataFile->rowObjs;
1250    }
1251/**
1252* get a particular record
1253*
1254* @param    number          $p_id      the record id
1255* @returns  array of record objects
1256*
1257*/
1258    function get($p_id) {
1259        global $display;
1260        $display->debug_output("Getting record with id=".$p_id." from table ".$this->name);
1261        return $this->recordObjs[$p_id];
1262    }
1263/**
1264* create a new record
1265*
1266* @param    array     $p_values       the values to be inserted into each column
1267* @returns  none
1268*
1269*/
1270    function create($p_values) {
1271        global $display;
1272        global $messages;
1273        if ($this->isUnique($p_values)) {
1274            $dataFile = new File($this,"data");
1275            $dataFile->write_record($p_values);
1276        }
1277    }
1278/**
1279* update a record in this table
1280*
1281* @param    number    $p_id       the id of the record to be updated
1282* @param    array     $p_values   the values for each column
1283* @returns  none
1284*
1285*/
1286    function update($p_id,$p_values) {
1287        global $display;
1288        $display->debug_output("table:update : record ".$p_id." in table ".$this->name);
1289        $dataFile = new File($this,"data");
1290        $dataFile->update($p_id, $p_values);
1291    }
1292/**
1293* delete a record in this table
1294*
1295* @param    number    $p_id       the id of the record to be deleted
1296* @returns  none
1297*
1298*/
1299    function delete ($p_id) {
1300        $dataFile = new File($this,"data");
1301        $dataFile->delete($p_id);
1302    }
1303}
1304/**
1305* ReferenceTable Class
1306*
1307* A sub-class of table specifically for the reference table
1308*
1309*/
1310class ReferenceTable extends Table {
1311    var $list;
1312/**
1313*
1314* Constructor
1315*
1316* @param    none
1317* @returns  object  the reference table contains the list
1318*
1319*/
1320    function ReferenceTable ($id) {
1321       global $auditTrail;
1322       $auditTrail->add_header("ReferenceTable");
1323        $this->Table($id);
1324        $this->get_all();
1325        $counter=0;
1326        foreach ($this->recordObjs as $rec) {
1327            $this->list[$rec->values[1]] = $rec->values[2];
1328        }
1329       $auditTrail->add_footer("ReferenceTable");
1330    }
1331/**
1332* Get the list of lookups e.g. Gender, Title
1333*
1334* @param    none
1335* @returns  the list of lookups e.g. Gender
1336*
1337*/
1338    function get_list () {
1339        return $this->list;
1340    }
1341}
1342/**
1343* Record Class
1344*
1345* Holds information on each record in each table
1346*
1347*/
1348class Record {
1349    var $tableObj;
1350    var $id;
1351    var $values;
1352/**
1353*
1354* Constructor
1355*
1356* @param    table object $p_tableObj   the table to which this record belongs
1357* @param    number       $p_recordId   the ID of the record
1358* @param    array        $p_values     an array containing the fields
1359* @returns  object
1360*
1361*/
1362    function Record ($p_tableObj,$p_recordId,$p_values='') {
1363        global $auditTrail;
1364        $auditTrail->add_header('Record creation '.$p_tableObj->name);
1365
1366        $this->tableObj = $p_tableObj;
1367        $this->values = $p_values;
1368        if (count($p_values) < count($this->tableObj->columnObjs))
1369            for ($counter=count($p_values)+1; $counter<=count($this->tableObj->columnObjs);$counter++)
1370                $this->values[$counter]='';
1371        $this->id=$p_recordId;
1372        $auditTrail->add_footer('Record creation '.$p_tableObj->name);
1373    }
1374}
1375/**
1376* File Class
1377*
1378* Holds the information on a physical file
1379*
1380*/
1381class File {
1382    var $name;
1383    var $buf;
1384    var $rowObjs = array();
1385    var $CR ;
1386    var $ASCILF=10;
1387/**
1388*
1389* Constructor
1390*
1391* @param    table object or string $p_tableObjOrName   the table object to which this file belongs or the name of a non-database file
1392* @param    string                 $type               'data' for a data file, 'definition' for a definition file and 'debug' for a log file
1393* @returns  object
1394*
1395*/
1396    function File ($p_tableObjOrName, $type) {
1397        global $environment;
1398        $this->CR= chr(13);
1399        if ($type == "data" || $type == "definition")
1400            $this->file_maker($p_tableObjOrName, $type);
1401// This is a general file create and the name has been passed as the first param
1402        else {
1403            $this->name = $environment->baseFolder.$environment->DICTIONARY_FOLDER.'/'.$p_tableObjOrName;
1404        }
1405    }
1406/**
1407* Extends the constructor for files associated with user tables as opposed
1408to debug files etc
1409*
1410* @param    table object    $p_tableObj
1411* @param    string          $type           definition or data
1412* @returns  object
1413*
1414*/
1415    function file_maker($p_tableObj, $type) {
1416        global $environment;
1417        global $display;
1418        global $session;
1419        $dbFolder = $environment->get_folder($p_tableObj->id);
1420        if ($p_tableObj->isAdmin) {
1421            if ($type=="definition")
1422// v1.0.2 this was originally taken from a file - it is now a mix of hard-coding and $lang
1423                $this->name = 'definition.txt';
1424            else
1425                $this->name = $dbFolder."definition.txt";
1426        }
1427        else  {
1428            if ($type=="definition")
1429                $this->name = $dbFolder."definition.txt";
1430            else
1431                $this->name = $dbFolder."data.txt";
1432        }
1433        $display->debug_output( "The file name = ".$this->name);
1434    }
1435/**
1436* Creates this file
1437*
1438* @param    none
1439* @returns  none
1440*
1441*/
1442    function create_file() {
1443        global $messages;
1444        $handle = fopen($this->name,"w");
1445        if (!$handle)
1446            $messages->add("Unable to open the file ".$this->name." when attempting to create it");
1447        else
1448            fclose($handle);
1449    }
1450/**
1451* Deletes this file
1452*
1453* @param    none
1454* @returns  none
1455*
1456*/
1457    function delete_file() {
1458        if ($this->exists())
1459            unlink($this->name);
1460    }
1461/**
1462* Does this file exist
1463*
1464* @param    none
1465* @returns  boolean
1466*
1467*/
1468    function exists () {
1469       if (file_exists($this->name))
1470           return true;
1471       else
1472           return false;
1473    }
1474/**
1475* Reads in the contents of this file into rowObjs
1476*
1477* @param    none
1478* @returns  none
1479*
1480*/
1481    function read_buffer () {
1482        global $display;
1483        global $auditTrail;
1484        global $messages;
1485        $auditTrail->add_header('read_buffer');
1486// Intercept the admin definition
1487        if (!$this->exists()) {
1488            $messages->add('Unable to open the file '.$this->name.' when attempting to read');
1489        } else {
1490            $handle = fopen($this->name,"r");
1491            $display->debug_output( "<br>read_buffer opening ".$this->name);
1492            $buf = '';
1493// The filesize is cached and should be cleared
1494            clearstatcache();
1495            if (filesize($this->name) > 0 )
1496                $buf = fread($handle,filesize($this->name));
1497            fclose($handle);
1498            $auditTrail->add( "read in ".$buf);
1499            $this->buf = $buf;
1500// get the rows
1501            $rows = explode($this->CR,$buf);
1502// get the columns
1503            $counter = 0;
1504            foreach ($rows as $row) {
1505                if (ord(substr($row,0,1)) == $this->ASCILF)
1506                    $row = substr($row,1);
1507                if (strlen($row)>0) {
1508                    $counter = $counter + 1;
1509                    $this->rowObjs[$counter] = new Row($row);
1510                }
1511            }
1512            $auditTrail->add_footer('read_buffer');
1513        }
1514    }
1515/**
1516* writes record to this file
1517*
1518* @param    array    $p_values    the values to be written into the columns of this new record
1519* @returns  none
1520*
1521*/
1522    function write_record ($p_values) {
1523        global $messages;
1524        global $environment;
1525        if ($this->exists()) {
1526            $handle = fopen($this->name,"a");
1527        } else {
1528            $handle = fopen($this->name,"w");
1529        }
1530        if (!$handle) {
1531            $messages->add('Unable to open the file '.$this->name.' when attempting to write');
1532        } else {
1533            $sep='';
1534            $buf='';
1535            if (is_array($p_values)) {
1536                foreach($p_values as $value) {
1537                    $buf.= $sep.$value;
1538                    $sep=$environment->COLUMN_SEPARATOR;
1539                }
1540            } else {
1541                $buf.= $p_values;
1542            }
1543            if (fwrite ($handle,$buf.$this->CR) === FALSE)
1544               $messages->add('Error writing to the file '.$this->name);
1545            fclose($handle);
1546        }
1547    }
1548/**
1549* Deletes a record
1550*
1551* @param    number   $p_id  ID of the record to be deleted
1552* @returns  none
1553*
1554*/
1555    function delete ($p_id) {
1556        global $messages;
1557        $this->read_buffer();
1558        $handle = fopen($this->name,"w");
1559        if (!$handle) {
1560            $messages->add("Unable to open the file ".$this->name." when attempting to delete");
1561        } else {
1562            for ($counter=1;$counter<$p_id;$counter++)
1563                fwrite($handle,$this->rowObjs[$counter]->raw.$this->CR);
1564
1565// Skip the record
1566// Write the rest
1567            for ($counter=$p_id+1;$counter<=count($this->rowObjs);$counter++)
1568                fwrite($handle,$this->rowObjs[$counter]->raw.$this->CR);
1569            fclose($handle);
1570        }
1571    }
1572/**
1573* Update a record in this file
1574*
1575* @param    number   $p_id       ID of the record to be deleted from the file
1576* @param    array    $p_values   the values to be inserted into each column
1577* @returns  none
1578*
1579*/
1580    function update ($p_id, $p_values) {
1581        global $display;
1582        global $messages;
1583        global $environment;
1584        $display->debug_output( "file:update updating rec ".$p_id."in ".$this->name);
1585        $this->read_buffer();
1586        $display->debug_output( "first line = ".$this->rowObjs[1]->raw);
1587        $handle = fopen($this->name,"w");
1588        if (!$handle)
1589            $messages->add("Unable to open the file ".$this->name." when attempting to update");
1590        else {
1591            for ($counter=1;$counter<$p_id;$counter++)
1592                fwrite($handle,$this->rowObjs[$counter]->raw.$this->CR);
1593// Create the record
1594            $sep='';
1595            $buf='';
1596            foreach($p_values as $value) {
1597                $buf.= $sep.$value;
1598                $sep=$environment->COLUMN_SEPARATOR;
1599            }
1600            fwrite ($handle,$buf);
1601            fwrite($handle,$this->CR);
1602//Write the rest
1603            for ($counter=$p_id+1;$counter<=count($this->rowObjs);$counter++)
1604                fwrite($handle,$this->rowObjs[$counter]->raw.$this->CR);
1605            fclose($handle);
1606        }
1607    }
1608}
1609/**
1610* Row Class
1611*
1612* Holds the information on a row in a file
1613*
1614*/
1615class Row {
1616    var $fields = array();
1617    var $raw;
1618/**
1619*
1620* Constructor
1621*
1622* @param    srtring $row    the raw data including the pip separator
1623* @returns  object
1624*
1625*/
1626    function Row($row) {
1627        global $auditTrail;
1628        global $environment;
1629        $auditTrail->add_header('Creating Row using '.$row);
1630        $this->raw = $row;
1631        $elements=explode($environment->COLUMN_SEPARATOR, $row);
1632        $counter = 0;
1633        foreach($elements as $element) {
1634            $counter = $counter + 1;
1635            $this->fields[$counter] = $element;
1636            $auditTrail->add('Element Number '.$counter.' = '.$element);
1637        }
1638        $auditTrail->add_footer('Creating Row ');
1639    }
1640}
1641/**
1642* Display Class
1643*
1644* Manages the display to the screen
1645*
1646*/
1647class Display {
1648    var $debug=false ;
1649    var $IDX;
1650    var $currentSteps =0;
1651/**
1652*
1653* Constructor
1654*
1655* @param     none
1656* @returns  object
1657*
1658*/
1659    function Display() {
1660        global $dbName;
1661        $this->IDX ='<input type="hidden" name="id" value="start" />';
1662        $this->IDX .='<input type="hidden" name="idx" value="'.$dbName.'"
1663/>';
1664    }
1665/**
1666* Displays the form for creating a record
1667*
1668* @param    table object     $tab         the table in which the record will be created
1669* @param    messages object  $messages    the messages object so that any errors are trapped and displayed
1670* @param    string array     $values      the values to be placed in the fields in case there have been errors
1671* @returns  none
1672*
1673*/
1674    function create_form ($tab, $messages, $values='') {
1675        global $environment;
1676        global $myLang;
1677// Indent
1678        $this->output ('<div class="level2">');
1679// The table name
1680        $this->output ('<h1>'.$tab->name.'</h1>');
1681        if ($tab->isAdmin) $title = getMyLang('prompt_col');
1682        else $title = getMyLang('prompt_rec');
1683// The title ie. Creating Records
1684        $this->output ('<h2>'.getMyLang('prompt_cre').' '.$title.'</h2>');
1685        if ($messages->exist)
1686            $messages->displayAll();
1687// Indent
1688        $this->output ('<div class="level3">');
1689
1690        $this->output ($this->form_start());
1691        $this->output ('<table border=1>');
1692        $counter=0;
1693        foreach($tab->columnObjs as $column) {
1694            $counter++;
1695            $this->output ('<tr>');
1696            $this->output ('<th>');
1697            $this->output ($column->label);
1698            $this->output ('</th>');
1699            $this->output ('<td>');
1700// If there were validation messages, redisplay values
1701            $name = "values".$counter;
1702            if (is_array($values)) $this->output($column->display_for_change($values[$counter],'create'));
1703            else
1704               $this->output($column->display_for_change('','create'));
1705            $this->output ('</td>');
1706            $this->output ('</tr>');
1707        }
1708        $this->output ('</table>');
1709        $this->output ('<span style="white-space: nowrap;">'); // This form seems to behave differently from the others
1710        $this->output($this->button('insertAction',getMyLang('btn_cre_cre') ,false, $tab->displayId));
1711        $this->output ($this->form_end());
1712// The Home button
1713        $this->output($this->button('home',getMyLang('btn_home') ,true));
1714// The Back button
1715        $this->output($this->button('listall',getMyLang('btn_back') ,true, $tab->displayId));
1716        $this->output ('</span>');
1717        $this->output('</div>');
1718        $this->output ('</div>');
1719    }
1720/**
1721* Displays the form for editing a record
1722*
1723* @param    record object    $p_recordObj the record to be edited
1724* @param    messages object  $messages    the messages object so that any errors are trapped and displayed
1725* @param    string array     $values      the values to be placed in the fields
1726* @returns  none
1727*
1728*/
1729    function edit_form($p_recordObj,$messages,$values='') {
1730// $p_recordObj has the current db values, $values has the values from the last editing (if they exist)
1731        global $environment;
1732        global $myLang;
1733        $tableObj=$p_recordObj->tableObj;
1734// Indent
1735        $this->output ('<div class="level2">');
1736// The table name
1737        $this->output ('<h1>'.$tableObj->name.'</h1>');
1738        if ($tab->isAdmin) $title = getMyLang('prompt_col');
1739        else $title = getMyLang('prompt_rec');
1740// The title ie. Creating Records
1741        $this->output ('<h2>'.getMyLang('prompt_edt').' '.$title.'</h2>');
1742        if ($messages->exist)
1743            $messages->displayAll();
1744        $columnObjs = $tableObj->columnObjs;
1745// Indent
1746        $this->output ('<div class="level3">');
1747// Start the form
1748        $this->output ($this->form_start());
1749// Start of table
1750        $this->output( "<table border=1>");
1751        for($counter=1;$counter<count($columnObjs)+1;$counter++) {
1752            $column = $columnObjs[$counter];
1753            $this->output('<tr>');
1754            $this->output('<th>');
1755            $this->output($columnObjs[$counter]->label);
1756            $this->output ('</th>');
1757            $this->output ('<td>');
1758            if (is_array($values))
1759                $this->output($column->display_for_change($values[$counter],'update'));
1760            else
1761                $this->output($column->display_for_change($p_recordObj->values[$counter],'update'));
1762            $this->output('</td>');
1763            $this->output('</tr>');
1764        }
1765        $this->output('</table>');
1766// Now for the original db values as hidden fields
1767        for($counter=1;$counter<count($columnObjs)+1;$counter++) {
1768            $column = $columnObjs[$counter];
1769            $fieldName = "originals".$counter;
1770            $this->output($column->display_original($counter, $p_recordObj->values[$counter]));
1771        }
1772// Update button
1773        $this->output($this->button('updateAction',getMyLang('btn_edt_upd'),false, $tableObj->displayId,$p_recordObj->id));
1774        $this->output($this->form_end());
1775// The Home button
1776        $this->output($this->button('home',getMyLang('btn_home') ,true));
1777// The Back button
1778        $this->output($this->button('listall',getMyLang('btn_back') ,true, $tableObj->displayId));
1779
1780        $this->output('</div>');
1781        $this->output('</div>');
1782    }
1783/**
1784* Displays the form for deleting a record
1785*
1786* @param    record object  $p_recordObj the record to be deleted
1787* @param    messages object  $messages  the messages object so that any errors are trapped and displayed
1788* @returns  none
1789*
1790*/
1791    function delete_form($p_recordObj,$messages) {
1792        global $environment;
1793        global $myLang;
1794        $tableObj=$p_recordObj->tableObj;
1795// Indent
1796        $this->output ('<div class="level2">');
1797// The table name
1798        $this->output ('<h1>'.$tableObj->name.'</h1>');
1799        if ($tableObj->isAdmin) $title = getMyLang('prompt_col');
1800        else $title = getMyLang('prompt_rec');
1801// The title ie. Deleting .....
1802        $this->output ('<h2>'.getMyLang('prompt_del_rec').'</h2>');
1803        if ($messages->exist)
1804            $messages->displayAll();
1805        $columnObjs = $tableObj->columnObjs;
1806// Indent
1807        $this->output ('<div class="level3">');
1808        $this->output ($this->form_start());
1809        $this->output( "<table border=1>");
1810// Display the contents
1811        for($counter=1;$counter<count($columnObjs)+1;$counter++) {
1812            $this->output('<tr>');
1813            $this->output('<th>');
1814            $this->output($columnObjs[$counter]->label);
1815            $this->output('</th>');
1816            $this->output('<td>');
1817
1818            $this->output($columnObjs[$counter]->display($p_recordObj->values[$counter]));
1819            $this->output('</td>');
1820            $this->output('</tr>');
1821        }
1822// Now for the original db values as hidden fields
1823        for($counter=1;$counter<count($columnObjs)+1;$counter++) {
1824            $column = $columnObjs[$counter];
1825            $fieldName = "originals".$counter;
1826            $this->output($column->display_original($counter, $p_recordObj->values[$counter]));
1827        }
1828// Display the buttons
1829        $this->output('</table>');
1830        $this->output($this->button('deleteAction',getMyLang('btn_del_cnf'),false,
1831        $tableObj->displayId,$p_recordObj->id));
1832        $this->output ($this->form_end());
1833// The Home button
1834        $this->output($this->button('home',getMyLang('btn_home') ,true));
1835// The Back button
1836        $this->output($this->button('listall',getMyLang('btn_back') ,true, $tableObj->displayId));
1837        $this->output('</div>');
1838        $this->output ('</div>');
1839    }
1840/**
1841* Displays all the records in a table for sorting, searching etc
1842*
1843* @param    table object  $tab            the table whose contents are to be displayed
1844* @param    array         $list           the contents to be displayed
1845* @param    array         $sortedList     the sorted list of record IDs
1846* @param    number        $sortedColumnId the column on which it has been sorted
1847* @returns  none
1848*
1849*/
1850    function list_all($tab, $list,$sortedList='',$sortedColumnId=0) {
1851        global $environment;
1852        global $myLang;
1853        $numberOfCells = count($tab->columnObjs)+3;
1854        $this->output ('<div class="level2">');
1855// The title
1856        $this->output ('<h1>'.$tab->name.'</h1>');
1857// The Home button
1858        $this->output($this->button('home',getMyLang('btn_home') ,true));
1859//The create button
1860        $prompt = getMyLang('btn_lst_cre_new_rec');
1861        if ($tab->isAdmin) $prompt=getMyLang('btn_lst_cre_new_col');
1862        $this->output($this->button('insertForm',$prompt,true, $tab->displayId));
1863//The Admin button
1864        if (!$tab->isAdmin) {
1865             $this->output($this->button('admin',getMyLang('btn_adm_tab_str'),true, $tab->displayId));
1866         } else {
1867             $this->output($this->button('listall',getMyLang('btn_lst_tab_cnt') ,true, $tab->id));
1868         }
1869//************************************************************************************
1870
1871        $this->output ($this->form_start());
1872        $this->output ('<table class="inline">');
1873        $colCounter=0;
1874        $this->output ('<thead>');
1875        $this->output ('<tr>');
1876// The No sort radio button
1877        $this->output ('<th align=center>');
1878        $this->output (getMyLang('rad_lst_no_srt'));
1879        $toGo = '<input type="radio" name="sort" value="0"';
1880        if ($sortedColumnId==0)
1881            $toGo .=' checked ';
1882        $toGo .= '/>';
1883        $this->output ($toGo);
1884        $this->output ('</th>');
1885        foreach($tab->columnObjs as $column) {
1886            $colCounter++;
1887            if ($column->isVisible) {
1888                $this->output ("<th>");
1889// The Sort radio button
1890                $this->output ($column->label);
1891                $toGo = '<input type="radio" name="sort" value="'.$colCounter.'"';
1892                if ($colCounter==$sortedColumnId)
1893                    $toGo .=' checked ';
1894                $toGo .= '/>';
1895                $this->output ($toGo);
1896                $this->output ('</th>');
1897            }
1898        }
1899        $this->output ('</tr>');
1900//The filter cells
1901        $this->output ('<tr>');
1902// The Search row of filters and the Search button
1903        $colCounter=0;
1904        $this->output ('<td class="rightalign">');
1905        $this->output($this->iconButton('list','search.png', getMyLang('btn_lst_srch_srt'), $tab->displayId));
1906        $this->output ('</td>');
1907        foreach($tab->columnObjs as $column) {
1908            $colCounter++;
1909            if ($column->isVisible) {
1910                $this->output ('<td>');
1911                $this->output ($column->display_for_change('','search'));
1912                $this->output ('</td>');
1913            }
1914        }
1915        $this->output ('</tr>');
1916        $this->output ('</thead>');
1917//**************************************************************************************
1918        $this->output ('<tbody>');
1919// the rest of the main table
1920        $recCounter = 0;
1921        $columnCount=count($tab->columnObjs);
1922//The row begins
1923        for ($recCounter=1;$recCounter<=count($tab->recordObjs);$recCounter++) {
1924            $recPointer= $sortedList[$recCounter];
1925            $record=$tab->recordObjs[$recPointer];
1926//Check against the filter list
1927            $okToDisplay = false;
1928            if (in_array($recCounter,$list,true))
1929                $okToDisplay = true;
1930            if ($okToDisplay) {
1931                $this->output ("<tr>");
1932// Each column in the row
1933                $this->output ('<td>');
1934// The Edit button
1935                $this->output($this->iconButton('updateForm','edit.png',getMyLang('btn_lst_edt'), $tab->displayId,$recPointer));
1936                if (!$tab->isAdmin) {
1937// The Delete button
1938                    $this->output($this->iconButton('deleteForm','delete.png',getMyLang('btn_lst_del'), $tab->displayId,$recPointer));
1939                }
1940                $this->output ('</td>');
1941// The columns displayed
1942                for ($fieldCounter=1;$fieldCounter<=$columnCount;$fieldCounter++) {
1943                    $column = $tab->columnObjs[$fieldCounter];
1944                    if ($column->isVisible) {
1945                        $value = $record->values[$fieldCounter];
1946                        $this->output ('<td>');
1947                        $this->output ($tab->columnObjs[$fieldCounter]->display($value));
1948                        $this->output ('</td>');
1949                    }
1950                }
1951                $this->output ("</tr>");
1952            }
1953        }
1954        $this->output ('</tbody>');
1955        $this->output ('</table>');
1956        $this->output ($this->form_end());
1957//************************************************************************************
1958        $this->output ('</div>');
1959    }
1960/**
1961* Displays the form for creating a new table
1962*
1963* @param    none
1964* @returns  none
1965*
1966*/
1967    function createTableForm() {
1968        global $environment;
1969        global $messages;
1970        global $myLang;
1971        $this->output ('<h1>'.getMyLang('ttl_cre_tab').'</h1>');
1972        if ($messages->exist) $messages->displayAll();
1973        $this->output ('<table>');
1974        $this->output ($this->form_start());
1975        $this->output('<tr>');
1976        $this->output('<td>');
1977        $this->output(getMyLang('prompt_ent_tab_name'));
1978        $this->output('</td>');
1979        $this->output('<td>');
1980        $this->output('<input type="text" name="tableName" size=30 />');
1981        $this->output('</td>');
1982        $this->output('</tr>');
1983        $this->output('</table>');
1984        $this->output($this->button('createTable',getMyLang('btn_cre_new_tab'),false));
1985        $this->output($this->form_end());
1986// The Home button
1987        $this->output($this->button('home',getMyLang('btn_home') ,true));
1988    }
1989/**
1990*  Constructs the HTML form start tag
1991*
1992* @param    none
1993* @returns  string    the HTML form end tag
1994*
1995*/
1996    function form_start() {
1997        global $environment;
1998        $ret = '<form method="post" action="'.$environment->URL.'">';
1999        return $ret;
2000    }
2001/**
2002* Displays a button if permitted
2003*
2004* @param    string  $action         the action taken
2005* @param    string  $name           the name of the button
2006* @param    boolean $withTopAndTail true if the <form> and </form> are to be included
2007* @param    number  $table          the ID of the table
2008* @param    number  $record         the ID of the record
2009* @returns  string  The HTML required
2010*
2011*/
2012    function button ($action, $name, $withTopAndTail=false, $table='', $record='') {
2013        $ret='';
2014        global $session;
2015        global $auditTrail;
2016        $auditTrail->add_header('button action = '.$action.' name = '.$name);
2017        if ($session->allowed($action)) {
2018            if ($withTopAndTail) $ret .= $this->form_start();
2019            $ret .= $this->IDX;
2020            $ret .='<input type="hidden" name="action" value="'.$action.'" />';
2021            if ($table!='') $ret .= '<input type="hidden" name="table" value="'.$table.'" />';
2022            if ($record!='') $ret .= '<input type="hidden" name="record" value="'.$record.'" />';
2023            $ret .='<input type="submit" value="'.$name.'" ';
2024            $ret .= 'class="button" />';
2025            if ($withTopAndTail) $ret .= $this->form_end();
2026        }
2027        $auditTrail->add_footer('button action = '.$action.' name = '.$name);
2028        return $ret;
2029    }
2030/**
2031* Displays a button icon if permitted
2032*
2033* @param    string  $action         the action taken
2034* @param    string  $image          the image to be displayed
2035* @param    string  $title          the title for the hint
2036* @param    number  $table          the ID of the table
2037* @param    number  $record         the ID of the record
2038* @returns  string  The HTML required
2039*
2040*/
2041    function iconButton ($action, $image, $title, $table='', $record='') {
2042        $ret='';
2043        global $session;
2044        global $auditTrail;
2045        global $environment;
2046        $auditTrail->add_header('button action = '.$action.' name = '.$name);
2047        if ($session->allowed($action)) {
2048            $ret .= $this->IDX;
2049            $ret .= '<input type="image" src="'.$environment->imagesFolder.$image.'" name="fn['.$action.']['.$table.$environment->COLUMN_SEPARATOR.$record.']" title="'.$title.'" alt="'.$title.'" />';
2050        }
2051        $auditTrail->add_footer('iconButton action = '.$action.' name = '.$name);
2052        return $ret;
2053    }
2054/**
2055* Constructs the HTML form end tag
2056*
2057* @param    none
2058* @returns  string  the HTML form end tag
2059*
2060*/
2061    function form_end() {
2062        $ret ='</form>';
2063        return $ret;
2064    }
2065/**
2066* Outputs/renders to the screen
2067*
2068* @param    string  $p_string    the string to be output
2069* @param    number  $p_indent = -1 : reduce indent
2070*                             = 0  ; leave indent
2071*                             = +1 : increase indent
2072* @returns  none
2073*
2074*/
2075    function output ($p_string, $p_indent=0) {
2076        if ($p_indent==0) {
2077            if (substr($p_string,0,3)=='</t') $this->currentSteps--;
2078        }
2079        $INCREMENT = 2; // for the indent of the HTML
2080
2081        ptln( $p_string, $this->currentSteps*$INCREMENT);
2082        if ($p_indent==0) {
2083            if (substr($p_string,0,2)=='<t') $this->currentSteps++;
2084        }
2085    }
2086/**
2087* Outputs to the debug file if the debug flag is set to true
2088*
2089* @param    string  $p_string    the string to be output
2090* @returns  none
2091*
2092*/
2093    function debug_output ($p_string) {
2094        global $environment;
2095        if ($this->debug) {
2096            $file = new File($environment->DEBUG_FILE,"debug");
2097            $file->write_record($p_string);
2098        }
2099    }
2100/**
2101* the default screen
2102*
2103* @param    none
2104* @returns  none
2105*
2106*/
2107    function wiki () {
2108    }
2109}
2110/**
2111* TestObj Class
2112*
2113* Used specifically for the sort
2114*
2115*/
2116class TestObj {
2117    var $id;
2118    var $name;
2119/**
2120*
2121* Constructor
2122*
2123* @param    number $id     the ID of the element
2124* @param    string $name   the name of the element
2125* @returns  object
2126*
2127*/
2128    function TestObj($id,$name)
2129    {
2130        $this->id = $id;
2131        $this->name = $name;
2132    }
2133/**
2134* This is the static comparing function:
2135*
2136* @param    string  $a    the string to be compared
2137* @param    string  $b    the string to be compared
2138* @returns  boolean       0 if =, 1 if a>b, else -1
2139*
2140*/
2141    function cmp_obj($a, $b)
2142    {
2143        $al = strtolower($a->name);
2144        $bl = strtolower($b->name);
2145        if ($al == $bl) {
2146            return 0;
2147        }
2148        return ($al > $bl) ? +1 : -1;
2149    }
2150}
2151/**
2152* SortedArray Class
2153*
2154* Holds the sorted array specifically for sorting
2155*
2156*/
2157class SortedArray {
2158    var $sortedList = array();
2159/**
2160*
2161* Constructor
2162*
2163* @param    array $list        the array to be sorted
2164* @returns  object
2165*
2166*/
2167    function SortedArray($list) {
2168        $recCounter=0;
2169        foreach($list as $entry) {
2170            $recCounter++;
2171            $toBeSorted[]=new TestObj($recCounter,$entry);
2172        }
2173        usort ($toBeSorted,array("TestObj","cmp_obj"));
2174        $counter=0;
2175        foreach($toBeSorted as $testObj) {
2176            $counter++;
2177            $this->sortedList[$counter] = $testObj->id;
2178        }
2179    }
2180}
2181/**
2182* Main program which takes the appropriate action
2183*
2184* @param    none
2185* @returns  none
2186*
2187*/
2188function action_db() {
2189    global $environment;
2190    global $display;
2191    global $session;
2192    global $IDX;
2193    global $dbName;
2194    global $messages;
2195    global $auditTrail;
2196    global $myLang;
2197// Set up by action.php
2198    global $action;
2199    global $tableId;
2200    global $recordId;
2201
2202    $display = new Display();
2203    $auditTrail = new AuditTrail();
2204    $INFO  = pageinfo();
2205    $auditTrail->add_header("action_db");
2206    $auditTrail->add('action = '.$action.' table = '.$tableId.' record = '.$recordId);
2207    $messages = new Messages();
2208// Check whether the debug flag is set
2209    if (isset($_REQUEST['debug'])) {
2210        if ($_REQUEST['debug']=='yes') {
2211           $file=new File($environment->DEBUG_FILE,"debug");
2212           $file->create_file();
2213           $display->debug=true;
2214        } elseif ($_REQUEST['debug']=='no') {
2215           $file=new File($environment->DEBUG_FILE,"debug");
2216           $file->delete_file();
2217           $display->debug=false;
2218        }
2219    }
2220    $session = new Session();
2221// Check if it has come from the wiki directly
2222    if ($action =='') {
2223// It has come directly from the wiki
2224        $action = "listall";
2225        $command = substr($IDX,strlen($dbName)+1);
2226        $tableId = substr($command,0,2);
2227// Pick up the Create Table option
2228        if ($IDX==$dbName.':'.$environment->DICTIONARY_FOLDER)
2229           $action='createTableForm';
2230        else {
2231// make sure all is OK
2232            $tab=new Table($tableId);
2233// if there was a problem then report it
2234            if ($messages->exist)
2235               $action="messages";
2236        }
2237    }
2238    $auditTrail->add("action=".$action);
2239// Check whether the user can carry out this action
2240    if (!$session->allowed($action)) {
2241        $action= "message";
2242        $message=getMyLang('msg_wrn_priv');
2243        $messages->add($message);
2244        $action = "messages";
2245    }
2246    $auditTrail->add("action=".$action);
2247// Now handle the various actions
2248// action = messages
2249    if ($action=="messages")
2250       $messages->displayAll();
2251// action = home
2252    if ($action == "home") {
2253        $display->wiki();
2254    }
2255// action = createTable
2256    if ($action == "createTable") {
2257        $tableName = '';
2258        if (isset($_REQUEST["tableName"]))
2259            $tableName  = $_REQUEST["tableName"];
2260        if ($tableName > '') {
2261           $tab = new Table($tableName);
2262        } else {
2263            $messages->add(getMyLang('msg_wrn_tab_nam') );
2264        }
2265        if ($messages->exist) {
2266            $action = "createTableForm";
2267        } else {
2268            $tableId = - $tab->id;
2269            $messages->add(getMyLang('msg_tab_cre'));
2270            $action = "insertForm";
2271        }
2272    }
2273// action = admin
2274    if ($action=="admin") {
2275        $auditTrail->add("action=".$action." tableId is ".$tableId."will be negative");
2276        $tableId = - $tableId;
2277        $action="listall";
2278    }
2279// action =  insertAction
2280    if ($action == "insertAction") {
2281        global $display;
2282        $tab=new Table ($tableId);
2283        for ($counter=1;$counter<=count($tab->columnObjs);$counter++) {
2284            $test="values".$counter;
2285            if (isset($_REQUEST[$test])) {
2286                $values[$counter]= $_REQUEST[$test];
2287                $column = $tab->columnObjs[$counter];
2288                $column->validate($values[$counter], $messages);
2289                $values[$counter] = $column->toDatabase($values[$counter]);
2290            }
2291        }
2292        $display->debug_output ( "val=".$values[1]." and ".$values[2]);
2293        if ($messages->exist) {
2294            $action = "insertForm";
2295        } else {
2296            $tab->create($values);
2297            if ($messages->exist)
2298                $action="insertForm";
2299            else
2300                $action="listall";
2301        }
2302    }
2303// action = updateAction
2304    if ($action == "updateAction") {
2305        global $display;
2306        $tab=new Table ($tableId);
2307        $done = false;
2308        $display->debug_output ("main:update action: table= ".$tab->name);
2309        for ($counter=1;$counter<=count($tab->columnObjs);$counter++) {
2310            $column = $tab->columnObjs[$counter];
2311// Get the changed values from the form
2312            $test="values".$counter;
2313            if (isset($_REQUEST[$test])) {
2314                $values[$counter]= $_REQUEST[$test];
2315                $column->validate($values[$counter], $messages);
2316                $values[$counter] = $column->toDatabase($values[$counter]);
2317            }
2318// Now to get the originals
2319            $test="originals".$counter;
2320            if (isset($_REQUEST[$test])) {
2321                $originals[$counter]= $_REQUEST[$test];
2322            }
2323        }
2324// The array values now has the values after being changed
2325// $originals has the original values stored as hidden fields on the form
2326        if ($messages->exist) {
2327// Problems with the validation so try again
2328            $action = "updateForm";
2329        } else {
2330            if (!$tab->isUnique($values,$recordId)) {
2331                $action = "updateForm";
2332                } else {
2333                    if ($tab->hasChanged($recordId, $originals)) {
2334                        $values = ''; // to force the refresh of the values in the screen
2335                    $action = "updateForm";
2336                    } else {
2337                        $tab->update($recordId, $values);
2338                    if ($messages->exist) {
2339// Problems with the update so try again
2340                        $action = "updateForm";
2341                    } else {
2342                        $action="listall";
2343                    }
2344                }
2345            }
2346        }
2347    }
2348// action = deleteAction
2349    if ($action == "deleteAction") {
2350        global $display;
2351        $tab=new Table ($tableId);
2352        $display->debug_output ("main:delete action: table= ".$tab->name);
2353        for ($counter=1;$counter<=count($tab->columnObjs);$counter++) {
2354            $column = $tab->columnObjs[$counter];
2355// Now to get the originals
2356            $test="originals".$counter;
2357            if (isset($_REQUEST[$test])) {
2358                $originals[$counter] = $_REQUEST[$test];
2359            }
2360        }
2361        if ($tab->hasChanged($recordId, $originals)) {
2362            $action = "deleteForm";
2363           } else {
2364            $tab->delete($recordId);
2365            if ($messages->exist)
2366// Problems with the delete so try again
2367                $action = "deleteForm";
2368            else
2369                $action = "listall";
2370            }
2371    }
2372// action = list or listall
2373    if ($action == "list" || $action == "listall") {
2374        $auditTrail->add("action=".$action." tableId is ".$tableId);
2375        $tab=new Table ($tableId);
2376        $tab->get_all();
2377        $sortedColumnId=0;
2378        $filters=array();
2379        $sortedList = array();
2380// **************** Any Sorting ? ************************************************
2381        if (isset($_REQUEST["sort"]) && $_REQUEST["sort"]>'0') {
2382            $sortedColumnId  = $_REQUEST["sort"];
2383// Prepare the sort array in $list
2384// If this is a link column then we should sort on the name of the link, i.e. the last element
2385            if ($tab->columnObjs[$sortedColumnId]->type == $environment->TYPE_EXTERNAL_LINK
2386               ||$tab->columnObjs[$sortedColumnId]->type == $environment->TYPE_INTERNAL_LINK) {
2387                $counter=0;
2388                foreach($tab->recordObjs as $recordObj) {
2389                    $counter++;
2390                    $value = $recordObj->values[$sortedColumnId];
2391                    $elements=explode($environment->COLON, $value);
2392                    $name=$elements[count($elements)-1];
2393                    $list[$counter] = $name;
2394                }
2395            }
2396// If this is a reference column then sort on the second column in the reference table
2397            elseif ($tab->columnObjs[$sortedColumnId]->type >= $environment->REFERENCE_OFFSET) {
2398                $refTableID = $tab->columnObjs[$sortedColumnId]->type - $environment->REFERENCE_OFFSET;
2399                $refTable = new ReferenceTable($refTableID);
2400                $baseList = $refTable->get_list();
2401                $counter = 0;
2402                foreach($tab->recordObjs as $recordObj) {
2403                    $counter++;
2404                    $list[$counter] = $baseList[$recordObj->values[$sortedColumnId]];
2405                }
2406// Any other type just sort on that column
2407            } else {
2408                $counter=0;
2409                foreach($tab->recordObjs as $recordObj) {
2410                    $counter++;
2411                    $list[$counter] = $recordObj->values[$sortedColumnId];
2412                }
2413            }
2414// now do the sorting  - the results are in sortedList as pointers
2415            $sortedListObj = new SortedArray($list);
2416            $sortedList = $sortedListObj->sortedList;
2417        } else {
2418// No Sorting required just put consecutive integers in $sortedList
2419            $counter=0;
2420            foreach ($tab->recordObjs as $recordObj) {
2421                $counter++;
2422                $sortedList[$counter] = $counter;
2423            }
2424        }
2425//check if any filters have been applied but not if it's listall
2426        if ($action != "listall") {
2427             for ($counter=1;$counter<=count($tab->columnObjs);$counter++) {
2428                 $test="values".$counter;
2429                 if (isset($_REQUEST[$test]))
2430                     $filters[$counter]= $_REQUEST[$test];
2431                 else $filters[$counter]='';
2432             }
2433        }
2434        $list=$tab->filter($filters,$sortedList);
2435        $display->list_all($tab, $list, $sortedList, $sortedColumnId);
2436    }
2437// action = createTableForm
2438    if ($action == 'createTableForm' ) {
2439            $display->createTableForm();
2440    }
2441// action = insertForm
2442    if ($action == "insertForm") {
2443        $auditTrail->add("Calling new Table with id = ".$tableId);
2444        $tab=new Table ($tableId);
2445        $display->create_form($tab,$messages,$values);
2446    }
2447// action = updateForm
2448    if ($action == "updateForm") {
2449        $tab=new Table ($tableId);
2450// If messages are empty then this must be the first time so store the originals
2451        if ($tab->possibleReference) {
2452            $messages->add(getMyLang('msg_ref_tab_wng_1'));
2453            $messages->add(getMyLang('msg_ref_tab_wng_2'));
2454            $messages->add(getMyLang('msg_ref_tab_wng_3'));
2455        }
2456        $tab->get_all();
2457        $rec=$tab->get($recordId);
2458        $display->edit_form($rec,$messages,$values);
2459    }
2460// action = deleteForm
2461    if ($action == "deleteForm") {
2462        $tab=new Table ($tableId);
2463        if ($tab->possibleReference) {
2464            $messages->add(getMyLang('msg_ref_tab_wng_1'));
2465            $messages->add(getMyLang('msg_ref_tab_wng_2'));
2466            $messages->add(getMyLang('msg_ref_tab_wng_3'));
2467        }
2468        $tab->get_all();
2469        $rec=$tab->get($recordId);
2470        $display->delete_form($rec, $messages);
2471    }
2472    $auditTrail->add_footer("action_db");
2473}
2474/**
2475* Get the translated element in $lang
2476*
2477* @param    $id1   first  id of the lang element
2478* @param    $id2   second  id of the lang element
2479* @param    $id3   third  id of the lang element
2480* @returns  string translation
2481*
2482*/
2483function getMyLang($id1,$id2='',$id3='') {
2484    global $myLang;
2485    if ($id3>'') $value = $myLang[$id1][$id2][$id3];
2486    elseif ($id2>'') $value = $myLang[$id1][$id2];
2487    else   $value = $myLang[$id1];
2488
2489    return utf8_encode($value);
2490}
2491?>
2492