1<?php
2	/**
3	* SQL Generator - helps to construct queries statements
4	* @author Edgar Antonio Luna Diaz <eald@co.com.mx>
5	* @author Alejadro Borges
6	* @author Jonathan Alberto Rivera Gomez
7	* @copyright Copyright (C) 2003,2004 Free Software Foundation, Inc. http://www.fsf.org/
8	* @license http://www.fsf.org/licenses/gpl.html GNU General Public License
9	* @package phpgwapi
10	* @subpackage database
11	* @version $Id: class.sql_builder.inc.php 21045 2010-03-25 22:41:38Z Caeies $
12	* @internal Development of this application was funded by http://www.sogrp.com
13	* @link http://www.sogrp.com/
14	*/
15
16	/**
17	* SQL entity name
18	*/
19	define(PHPGW_SQL_ENTITY_NAME, 0);
20	/**
21	* SQL distance
22	*/
23	define(PHPGW_SQL_DISTANCE, 1);
24	/**
25	* SQL child fields
26	*/
27	define(PHPGW_SQL_CHILD_FIELDS, 2);
28	/**
29	* SQL change distance
30	*/
31	define(PHPGW_SQL_CHANGE_DISTANCE, 3);
32	/**
33	* SQL sql lazy key
34	*/
35	define(PHPGW_SQL_LAZY_KEY, 1);
36	/**
37	* SQL required key
38	*/
39	define(PHPGW_SQL_REQUIRED_KEY, 2);
40	/**
41	* SQL all match
42	*/
43	define(PHPGW_SQL_ALL_MATCH, 1);
44	/**
45	* SQL exclusive match
46	*/
47	define(PHPGW_SQL_EXCLUSIVE_MATCH, 2);
48
49
50	/**
51	* SQL Generator - helps to construct queries statements
52	*
53	* This class provide common methods to create transaction sql queries.
54	* @package phpgwapi
55	* @subpackage database
56	* @abstract
57	*/
58	class sql_builder
59	{
60		// Map have a list of false_fields
61		// false_field => ('entity to instantiate', 'distance to central table');
62		var $map;
63		var $limit_str;
64		var $ondebug;
65		var $entities;
66		var $distance;
67		var $path;
68		var $reverse_path;
69		var $all_criteria;
70		var $central_entity;
71		var $operation;
72		var $external_select_value;
73		var $external_from_value;
74		var $external_criteria_value;
75		var $_criteria_built = False;
76		/*************************************************************\
77		* Entity, class and general section                           *
78		\*************************************************************/
79
80		/**
81		* Class contructor
82		*/
83		function sql_builder()
84		{
85		}
86
87		/**
88		* Instance a entity if this not exist
89		*
90		* @param string $field  The field name
91		* @return string The class which was instanced.
92		*/
93		function get_entity($field)
94		{
95			$ent = $this->ent_name($field);
96			$this->ldebug('get_entity', array('Field' => $field,
97				      'Entity Name' => $ent));
98			if(!is_object($this->entities[$ent]) && !empty($ent))
99			{
100				$this->ldebug('get_entity', "Try to instantiate $ent because $field", 'msg');
101				$this->instance_entity($ent);
102				$this->distance[$ent] = $this->get_distance($field);
103			}
104			return $ent;
105		}
106
107		/**
108		* @param string $field The field name for search in $this->map for a class8
109		* @return integer The distance of the class that have that field defined.
110		*/
111		function get_distance($field)
112		{
113			$entity = $this->map[$field][PHPGW_SQL_ENTITY_NAME];
114			if(is_array($this->distance))
115			{
116				$distance = array_keys($this->distance);
117				if(in_array($entity, $distance))
118				{
119					return $this->distance[$entity];
120				}
121			}
122			return $this->map[$field][PHPGW_SQL_DISTANCE];
123		}
124
125		/**
126		* Add an entity to map for building queries
127		*
128		* This function waits the map variable of entity
129		* @param string $entity Name of entity and class that will be in the query development.
130		* @param mixed $map The same $map that is set in one entity class
131		* @param integer $distance To the `$entity' to central_entity
132		* <code>
133		* $contact = CreateObject('phpgwapi.contacts');
134		* $my_ent = CreateObject('my_app.my_entity');
135		* $contact->add_entity('my_entity', $my_ent->map, 20);
136		* </code>
137		*/
138		function add_entity($entity, $map, $distance = 200)
139		{
140			$fields = array_keys($map);
141			foreach ($fields as $field)
142			{
143				$this->map[$field] = array($entity, $distance);
144			}
145		}
146
147		/**
148		* This function set in the distance array the distance of the entity.
149		*
150		* caveat: need a field :/
151		* @param string $field The field name for search in $this->map
152		* @param string $ent The name of the entity which want set it's entitiy
153		*/
154		function set_distance($field, $ent)
155		{
156			$this->distance[$ent] = $this->map[$field][PHPGW_SQL_DISTANCE];
157		}
158
159		/**
160		* Instance entity based on the information on map
161		*
162		* @param string $field A false field name that could be found in the map and have and entity declared.
163		* @return array Form: 'entity_name' => entity_object
164		*/
165		function instance_entity_by_field($field)
166		{
167			$entity_name = $this->ent_name($field);
168			$entity = CreateObject($entity_name);
169			return array($entity_name, &$entity);
170		}
171
172		function instance_entity($entity_name)
173		{
174			if(is_object($this->entities[$entity_name]))
175			{
176				$this->ldebug('instance_entity',
177					      "Already an object $entity_name", 'msg');
178			}
179			else
180			{
181				$this->entities[$entity_name] = CreateObject($entity_name);
182				$this->ldebug('instance_entity',
183					      "Creating object $entity_name", 'msg');
184			}
185			$this->ldebug('instance_entity', array('Entity name' => $entity_name));
186		}
187
188		/*************************************************************\
189		* Short path section                                          *
190		\*************************************************************/
191
192		function sort_by_distances()
193		{
194			// Re-sort the distances of clases that will use
195			if(!is_array($this->entities) || empty($this->entities))
196			{
197				return;
198			}
199			arsort($this->distance);
200			$entity_distance = current($this->distance);
201			$entity_far_away = key($this->distance);
202			$this->ldebug('sort_by_distances', 'entering to sort_by_distance', 'msg');
203			$this->add_path($entity_far_away, TRUE);
204			$this->ldebug('sort_by_distances',
205				      array('Distances' => $this->distance,
206					    'Path' => $this->path,
207					    'Entity Distance' => $entity_distance,
208					    'Entity far away' => $entity_far_away),
209				      'dump');
210			$this->entity_to_center($entity_far_away,
211						$entity_distance, TRUE);
212			$this->merge_paths();
213			$this->ldebug('sort_by_distances', 'entering to while, all the add_path must be reverse from here', 'msg');
214			while(!($this->last_distance()))
215			{
216				if (empty($this->distance))
217				{
218					// Array finished?
219					break;
220				}
221				next($this->distance);
222				$entity_distance = current($this->distance);
223				$entity_far_away = key($this->distance);
224				$this->ldebug('sort_by_distances', array('Distance' => $entity_distance,'Name' => $entity_far_away));
225				// We already found his path
226				if (array_key_exists($entity_far_away, $this->path))
227				{
228					$this->ldebug('sort_by_distances', array('Already in path' => $entity_far_away));
229					continue;
230				}
231				// We reach to central entity, this ends the game.
232				if ($entity_distance != 0)
233				{
234					$this->ldebug('sort_by_distances', array('Distance2' => $entity_distance,'Name' => $entity_far_away));
235					$this->add_path($entity_far_away, TRUE);
236					$this->entity_to_center($entity_far_away,
237								$entity_distance, TRUE);
238					$this->ldebug('sort_by_distances',
239								  array('Path' => array_keys($this->path)), 'dump');
240					$this->merge_paths();
241				}
242			}
243		}
244
245		/**
246		* This create a path of any identity to central entity
247		*
248		* @param string $entiity_name The name of the entity we try to find his path.
249		* @param int $entity_distance The distance of the $entity_name.
250		* @param bool $reverse TRUE if we want that path found be sorted in reverse mode (that is what we want for all case except first one).
251		*/
252		function entity_to_center($entity_name, $entity_distance, $reverse=FALSE)
253		{
254			$this->ldebug('entity_to_center', array('Entity_name' => $entity_name));
255			if ($this->last_distance())
256			{
257				return;
258			}
259			$links = array_merge($this->entities[$entity_name]->get_ilinks(),
260					     $this->entities[$entity_name]->get_elinks());
261			$this->ldebug('entity_to_center', array('Links' => $links), 'dump');
262			$min_distance = $entity_distance;
263			foreach ($links as $entity => $link)
264			{
265				$field = $link['ffield'];
266				$dist = $this->get_distance($field);
267				$this->ldebug('entity_to_center',
268							  array('field' => $field,
269									'Min distance' => $min_distance,
270									'entity this iteration' => $entity,
271									'Actual distance' => $dist));
272				$tmp = array_keys($this->get_path());
273				if ($dist < $min_distance || ($min_distance == $entity_distance && !in_array($entity, $tmp)))
274				{
275					//We found a good one
276					$this->ldebug('entity_to_center',
277						      "Actual Distance $dist < Min Distance $min_distance",
278						      'msg');
279					$min_distance = $dist;
280					$near_entity = $this->ent_name($field);
281				}
282			}
283
284			$this->instance_entity($near_entity);
285			if(is_array($this->distance))
286			{
287				if(array_key_exists($near_entity, $this->distance))
288				{
289					unset($this->distance[$near_entity]);
290				}
291			}
292			$this->add_path($near_entity, $reverse);
293			if ($min_distance == 0)
294			{
295				$this->central_entity = $near_entity;
296			}
297			else
298			{
299				$this->entity_to_center($near_entity, $min_distance, $reverse);
300			}
301		}
302
303		function last_distance()
304		{
305			$curr = key($this->distance);
306			@$this->ldebug('last_distance', array('Before Current' => $curr), 'dump');
307			next($this->distance);
308			if (@key($this->distance))
309			{
310				$curr = key($this->distance);
311				$this->ldebug('last_distance', array('Next' => $curr), 'dump');
312				prev($this->distance);
313				$curr = key($this->distance);
314				$this->ldebug('last_distance', array('Current No last' => $curr), 'dump');
315				return FALSE;
316			}
317			else
318			{
319				$curr = key($this->distance);
320				$this->ldebug('last_distance', array('Next' => $curr), 'dump');
321				prev($this->distance);
322				$curr = key($this->distance);
323				$this->ldebug('last_distance', array('Current LAST' => $curr), 'dump');
324				return TRUE;
325			}
326		}
327
328		function add_path($ent, $reverse = FALSE)
329		{
330			$this->ldebug('add_path', array('Entity'=> $ent,
331							'Reverse' => $reverse));
332			if ($reverse)
333			{
334				$this->false_path[$ent]['identity'] = $this->entities[$ent]->get_identity();
335				$this->false_path[$ent]['alias'] = $this->entities[$ent]->get_alias();
336			}
337			else
338			{
339				$this->path[$ent]['identity'] = $this->entities[$ent]->get_identity();
340				$this->path[$ent]['alias'] = $this->entities[$ent]->get_alias();
341			}
342		}
343
344		function get_path()
345		{
346			return is_array($this->path) ? $this->path : array();
347		}
348
349		function merge_paths()
350		{
351			$this->path = $this->get_path() + array_reverse($this->false_path);
352			$this->false_path = array();
353		}
354
355		/**
356		* Return the SQL select correct for all the entity map.
357		*
358		* This is the main functionality of this class.
359		* @return String with the sql created
360		*/
361		function get_sql()
362		{
363			if(!is_array($this->entities) || empty($this->entities))
364			{
365				return;
366			}
367			$this->ldebug('get_sql',
368				      array('Entities' => array_keys($this->entities),
369					    'Path' => $this->distance),
370				      'dump');
371			$this->sort_by_distances();
372			foreach ($this->entities as $name => $class)
373			{
374				list($fields, $from, $alias, $lcriteria) = $class->get_select();
375				if ($fields)
376				{
377					$this->select_fields[] = $fields;
378				}
379				$fields = '';
380				if ($lcriteria && !$this->_criteria_built)
381				{
382					$this->all_criteria = sql_criteria::append_and(array($lcriteria, $this->all_criteria));
383				}
384			}
385			$from = $this->get_join();
386			$this->fields = implode(', ', $this->select_fields);
387			// hooks for add external things in my queries
388			$this->fields = $this->external_select($this->fields);
389			$from = $this->external_from ($from);
390			$this->all_criteria = $this->external_criteria($this->all_criteria);
391			$sql  = 'SELECT '.$this->fields.' FROM '.$from;
392			$sql .= (empty($this->all_criteria))?'':' WHERE '. $this->all_criteria;
393			$this->select_fields = NULL;
394			$this->all_criteria = NULL;
395			$this->entities = NULL;
396			$this->path = NULL;
397			$this->distance = NULL;
398			$this->false_path = NULL;
399			$this->_criteria_built = False;
400
401			$sql = $this->set_order($sql);
402			unset($this->order_string);
403			$this->ldebug('SQL', array('SQL String' => $sql));
404			return $sql;
405		}
406
407		/**
408		* Allow to add fields to select part
409		*
410		* @param string $select Internal select part
411		* @return string complete SELECT field list
412		* @private
413		*/
414		function external_select($select)
415		{
416			return $select.(($this->external_select_value) ? ' '.$this->external_select_value : '');
417		}
418
419		/**
420		* Allow to add joins to from clausule
421		*
422		* @param string $from Internal joins
423		* @return string complete joins
424		* @private
425		*/
426		function external_from($from)
427		{
428			return $from.(($this->external_from_value) ? ' '.$this->external_from_value : '');
429		}
430
431		/**
432		* Allow to add criterias to internal WHERE
433		*
434		* @param string|array $criteria token list or string that have the external criterias
435		* @return string complete criterias
436		* @private
437		*/
438		function external_criteria($criteria)
439		{
440			if(!is_array($this->external_criteria_value))
441			{
442				return $criteria.(($this->external_criteria_value) ? ' '.$this->external_criteria_value : '');
443			}
444			else
445			{
446				$this->external_criteria_value[1] = $criteria;
447				return sql_criteria::criteria($this->external_criteria_value);
448			}
449		}
450
451		// This code is the base for calling insert procedure, maybe be
452		// integrated in get_sql, too ambicious? maybe.
453		function get_insert()
454		{
455			$this->insert_entities = keys($this->entities);
456			while (!empty($this->insert_entities))
457			{
458				$this->get_dependendencies(current($this->insert_entities));
459			}
460		}
461
462		function get_dependencies($entity)
463		{
464			// Get the classes that we depend on
465			$pre_depends = $this->entities[$entity]->get_ilinks();
466			$entities_depends = keys($pre_depends);
467			foreach($entities_depends as $entity_depend)
468			{
469				if(array_key_exists($entity_depend, $this->entities))
470				{
471					//We depend in entity that is in array
472					$this->get_dependencies($entity_depend);
473					$this->set_insert_dependency($entity, $entity_depend);
474				}
475			}
476			$this->add_insert_array($entity);
477		}
478
479		/**
480		* Get the list of false fields for $entity
481		*
482		* @param string $entity name of entity to search fields
483		* @return array list of false fields of $entity
484		*/
485		function get_false_fields($entity)
486		{
487			if (is_object($this->entities[$entity_name]))
488			{
489				return $this->entities[$entity_name]->get_false_fields();
490			}
491			else
492			{
493				$entity = CreateObject($entity);
494				$fields = $entity->get_false_fields();
495				return $fields;
496			}
497		}
498
499		/**
500		* Use $this->path for the order, and create the joins of all identities in the query
501		*
502		* @return all necesary for FROM clause (the joins by magic).
503		*/
504		function get_join()
505		{
506			$path = $this->get_path();
507			// from +
508			// table as alias (1)
509			$this->ldebug('get_join',
510						  array('Path' => $path),
511						  'dump');
512			$prev_entity = key($path);
513			$prev_data = current($path);
514			$from = $prev_data['identity'];
515			array_shift($path);
516			// repeat in all $path
517			while (list($entity, $data) = each($path))
518			{
519				// get_link, will determine the type of JOIN,
520				// local_field, foreign_field, and the right alias,
521				// and identity.
522				$link = $this->find_link($entity, $prev_entity,$prev_data['alias']);
523
524				// If we found the way to know when use other kind
525				// of join change next line.
526				// INNER JOIN table as alias
527				$this->ldebug('get_join',
528							  array('entity' => $entity,
529									'prev_entity' => $prev_entity,
530									'link' => $link),
531							  'dump');
532				$from .= $link['join'].' JOIN '.$data['identity'];
533				// ON alias.lfield = prev_alias.ffield
534				$from .= ' ON '.$data['alias'].'.'.
535					$this->real_field($entity,$link['lfield']);
536				$from .= ' = '.$link['alias'].'.'.
537					$this->real_field($link['prev_entity'],
538							  $link['ffield']);
539				// In a strange world, this variable would be useful
540				// if don't unset it, I'm under drugs right now, just
541				// leave the note.
542				$prev_entity = $entity;
543				$prev_data = $data;
544			}
545			return $from;
546		}
547
548		function find_link($entity, $prev_entity, $alias)
549		{
550			$this->ldebug('find_link', array('prev_entity' => $prev_entity, 'entity' => $entity));
551			if($return_link = $this->get_link($entity, $prev_entity))
552			{
553				$this->ldebug('find_link', 'I get a link', 'msg');
554				$return_link['prev_entity'] = $prev_entity;
555				$return_link['alias'] = $alias;
556			}
557			elseif($return_link = $this->get_link($entity, $this->central_entity))
558			{
559				$this->ldebug('find_link', 'I get to center', 'msg');
560				$return_link['prev_entity'] = $this->central_entity;
561				$return_link['alias'] = $this->path[$this->central_entity]['alias'];
562				$this->ldebug('find_link', array('return_link' => $return_link), 'dump');
563			}
564			else
565 			{
566				// Maybe here is one amazing research about the nodes
567				// entities, but not for this moment.
568				return;
569			}
570			$this->ldebug('find_link', array('return_link' => $return_link), 'dump');
571			return $return_link;
572		}
573
574		function get_link($entity, $test_entity)
575		{
576			$this->ldebug('get_link', array('entity' => $entity, 'test_entity' => $test_entity));
577			if ($entity_link = $this->entities[$entity]->get_ilink($test_entity))
578			{
579				$entity_link['join'] = ($entity_link['type'] == PHPGW_SQL_REQUIRED_KEY)?
580					' INNER ':' LEFT ';
581				$this->ldebug('get_link', "ilink natural $entity -> $test_entity", 'msg');
582			}
583			elseif ($entity_link = $this->entities[$test_entity]->get_ilink($entity))
584			{
585				$this->ldebug('get_link', array('link' => $entity_link), 'dump');
586				$entity_link['join'] = ($entity_link['type'] == PHPGW_SQL_REQUIRED_KEY)?
587					' INNER ':' RIGHT ';
588				$tmp_field = $entity_link['ffield'];
589				$entity_link['ffield'] = $entity_link['lfield'];
590				$entity_link['lfield'] = $tmp_field;
591				$this->ldebug('get_link', array('link' => $entity_link), 'dump');
592				$this->ldebug('get_link', 'ilink reverse', 'msg');
593			}
594			else
595			{
596				return ;
597			}
598			return $entity_link;
599		}
600
601		/**
602		* Get the real name for $field
603		*
604		* @param string $ent entity name
605		* @param string $field false field name
606		* return string with real_name
607		*/
608		function real_field($ent, $field)
609		{
610			$this->ldebug('real_field',
611				      array('Entity' => $ent,
612					    'Field' => $field));
613			return $this->entities[$ent]->real_field($field);
614		}
615
616		/**
617		* Get the real name for $field based on the map
618		*
619		* @param string $field false field name
620		*/
621		function real_name($field, $operation='insert')
622		{
623			list($entity_name, $entity) = $this->instance_entity_by_field($field);
624			$entity->set_operation($operation);
625			return $entity->real_field($entity->put_real_alias($field));
626		}
627
628		/**
629		* Get the real name for $field based on the map and instantiate the entity
630		*
631		* @param string $field false field name
632		*/
633		function real_field_entity($field, $operation='')
634		{
635			list($entity, $instance) = $this->instance_entity_by_field($field);
636			$instance->set_opertation($operation);
637			$this->entities[$entity] = $entity;
638			return $instance->real_field($instance->put_real_alias($field));
639		}
640
641		/**
642		* Cast value for datatype
643		*
644		* @param mixed $value Which we go to cast
645		* @param string $field field name to know the datatype
646		* @param boolean $permanent decide if we want to preserv the instance of the entity in $entities array
647		*/
648		function cast($value, $field, $permanent=True)
649		{
650			$entity = $this->ent_name($field);
651			$instance = $this->instance_volatile($entity, $permanent);
652			return $instance->cast($value, $field);
653		}
654
655
656		/**
657		* Instance an entity for permant usage of for remove at instant
658		*
659		* $permanent don't have any effect if entity already exist in $entities array.
660		* @param mixed $value Which we go to cast
661		* @param string $field field name to know the datatype
662		* @param boolean $permanent decide if we want to preserv the instance of the entity in $entities array
663		*/
664		function instance_volatile($entity, $permanent=True)
665		{
666			if(is_object($this->entities[$entity]))
667			{
668				$entity =& $this->entities[$entity];
669				return $entity;
670			}
671			else
672			{
673				$instance = CreateObject($entity);
674				if($permanent)
675				{
676					$this->entities[$entity] = $instance;
677					return $this->entities[$entity];
678				}
679				else
680				{
681					return $instance;
682				}
683			}
684		}
685
686		/**
687		* Get the class name of field
688		*
689		* @param string $field The field name
690		* @return string with class name.
691		*/
692		function ent_name($field)
693		{
694			$this->ldebug('ent_name', array("Map[$field]" => $this->map[$field]),
695				      'dump');
696			if(isset($this->map[$field]))
697			{
698				return $this->map[$field][PHPGW_SQL_ENTITY_NAME];
699			}
700			else
701			{
702				$this->raise_error();
703				$this->ldebug('ent_name', array('Class Map' => $this->map),
704					      'dump', 'error');
705				$this->ldebug('ent_name', array('Field' => $field),
706					     'string', 'error');
707				return '';
708			}
709		}
710
711		/**
712		* Generate a powerfull criteria based.
713		*
714		* Recieve $data that is an array (operand_left, operand_right, operator)
715		* If operant_left is array I call... myself :), else try to get the field name.
716		* If right operand is array can myself to, else, nothing.
717		* Third operator IS_ a operator name that send to sql::operate();
718		* @param array $data Genertaded by calls of sql class.
719		* @param string $operation Is one of: insert, select, delete, update, if is diferent to select, then the result will not include the alias for table.
720		* @return Criteria for any sql query.
721		*/
722		function builder_criteria($token_criteria, $operation='insert')
723		{
724			$num_elements = count($token_criteria);
725			$this->_criteria_built = True;
726			switch($num_elements)
727			{
728			case 0:
729			case 1:
730				$local_criteria = $token_criteria;
731				break;
732			case 2:
733			case 3:
734				$operator = array_pop($token_criteria);
735				$left = array_shift($token_criteria);
736				$right = array_shift($token_criteria);
737				if(is_array($left) && $operator != 'in')
738				{
739					$left = $this->builder_criteria($left, $operation);
740				}
741				else
742				{
743					$entity = $this->get_entity($left);
744					$this->entities[$entity]->set_operation($operation);
745					$field = $left;
746					$left = $this->entities[$entity]->put_real_alias($this->real_field($entity, $left));
747				}
748				if(is_array($right))
749				{
750					if ($operator != 'in')
751					{
752						$right = $this->builder_criteria($right, $operation);
753					}
754				}
755				else
756				{
757					if($field && !($operator == 'has' || $operator == 'begin_with' || $operator == 'end_with'))
758					{
759						$right = $this->entities[$entity]->cast($right, $field);
760					}
761				}
762				if ($operator == 'append_and' || $operator == 'append_or')
763				{
764					$param = array($left, $right);
765					$local_criteria = sql_criteria::operate($operator,$param);
766				}
767				else
768				{
769					$local_criteria = sql_criteria::operate($operator,$left,$right);
770				}
771				break;
772			default:
773				$operator = array_pop($token_criteria);
774				foreach($token_criteria as $criteria)
775				{
776					$criterias[] = $this->builder_criteria($criteria, $operation);
777				}
778				$local_criteria = sql_criteria::operate($operator,$criterias);
779			}
780			return $local_criteria;
781		}
782
783		/**
784		* This is an optional criteria generator, powerfull because use sql_criteria and sql_entity
785		*/
786		function criteria_token($criteria, $operation='select')
787		{
788			$this->all_criteria = $this->builder_criteria($criteria, $operation);
789		}
790
791		/**
792		* Set the criteria to correspoding class
793		*
794		* @param Array $data with many criterias (how could I support that)
795		*/
796		function criteria($data)
797		{
798			// $this->ldebug('criteria', array('Query' => $data), 'dump');
799			if(empty($data))
800			{
801				$this->ldebug('criteria', 'Oh, is empty', 'msg');
802				return;
803			}
804			if(is_array($data))
805			{
806				foreach ($data as $field => $filter)
807				{
808					$ent = $this->get_entity($field);
809					// $this->instance_entity($ent);
810					$this->entities[$ent]->set_operation('select');
811					$this->ldebug('criteria', array('Field' => $field,
812									'Filter' => $filter,
813									'Ent name' => $ent));
814					$this->entities[$ent]->add_criteria($field, $filter);
815				}
816			}
817			elseif(is_string($data))
818			{
819				$this->ldebug('criteria', 'Query a string is', 'msg');
820				list($field, $filter) = split ('=', str_replace(' ', '', $data));
821				$ent = $this->get_entity($field);
822				$this->entities[$ent]->add_criteria($field, $filter);
823			}
824			else
825			{
826				// This means we have problems
827				$this->ldebug('criteria', array('Data' => $data), 'dump');
828				return;
829			}
830		}
831
832		/**
833		* Get the criteria for all instanced class
834		*
835		* @return the criteria for each entity.
836		*/
837		function get_criteria()
838		{
839			foreach($this->entities as $entity)
840			{
841				$entity->get_criteria();
842			}
843		}
844
845		/**
846		* Allow set an alias for an entity
847		*
848		* @param string $entity The name of the entity that set alias
849		* @param string $alias The alias to set
850		*/
851		function set_alias($entity, $alias)
852		{
853			if(is_object($this->entities[$entity]))
854			{
855				$this->entities[$entity]->set_alias($alias);
856			}
857		}
858
859		// FIXME: needed?
860		function set_orders($fields)
861		{
862			if(is_array($fields))
863			{
864				foreach($fields as $field)
865				{
866					$this->set_order($field);
867				}
868			}
869		}
870
871		/**
872		* Set field name to use in ORDER BY
873		*
874		* @param string $sql the string wiht thee sql that we want to limit
875		* @return sql
876		*/
877		function set_order($sql)
878		{
879			return (!empty($this->order_string))? $sql.$this->order_string : $sql;
880		}
881
882		/**
883		* Get the order list in string
884		*
885		* @return the order list.
886		*/
887		function get_order()
888		{
889			return implode(',', $this->order);
890		}
891
892		/**
893		* Call to set_order to set order by field
894		*
895		* @param Array $field The data's list that you wan sort (based on false fields.
896		*/
897		function order($fields = array(), $type = 'ASC')
898		{
899			foreach($fields as $field)
900			{
901				$order_fields = implode(',',$fields);
902			}
903			$this->order_string .= ' ORDER BY '. $order_fields . ' ' .$type;
904		}
905
906		function inserts($datas)
907		{
908			foreach($datas as $data => $value)
909			{
910				$this->insert($data, $value);
911			}
912		}
913
914		/**
915		* Find the class an call it, giving to her the field name
916		*
917		* @param array|string $data The $data that is requested
918		* @param array|string $value The $value that is requested
919		*/
920		function insert($data, $value = '')
921		{
922			$this->operation = 'insert';
923			if(is_string($data))
924			{
925				$fields = explode(',', $data);
926			}
927			elseif(is_array($data))
928			{
929				$fields = &$data;
930			}
931
932			if(is_string($value))
933			{
934				$values = explode(',',$value);
935			}
936			elseif(is_array($value))
937			{
938				$values = &$value;
939			}
940			if ((count($values) != count($fields)) && $value != '')
941			{
942				//What do you think, that I'm a genious?
943				$this->raise_error();
944				return;
945			}
946
947			if ($value != '')
948			{
949				// Waiting for array_combine in php5
950				$all_data = array();
951				foreach($fields as $field_)
952				{
953					$t[$field_] = current($values);
954					$all_data = $all_data + $t;
955					next($values);
956				}
957			}
958			else
959			{
960				$all_data = &$data;
961			}
962
963			foreach($all_data as $field_ => $value_)
964			{
965				$this->ldebug('insert',
966					      array('Data' => $field_));
967				$this->_insert($field_, $value_);
968			}
969		}
970
971		function _insert($data, $value)
972		{
973			$ent = $this->get_entity($data);
974			$this->entities[$ent]->add_insert($data, $value);
975		}
976
977		function many_inserts($multiple_data = array())
978		{
979			if(empty($multiple_data))
980			{
981				$this->raise_error();
982				return;
983			}
984			foreach($multiple_data as $key => $insert_array)
985			{
986				$this->insert_indexed($key, $insert_array);
987			}
988		}
989
990		function insert_indexed($key, $data)
991		{
992			foreach($data as $field => $data)
993			{
994				$ent = $this->get_entity($field);
995				$this->entities[$ent]->add_insert_index($field, $data, $key);
996			}
997		}
998
999		/**
1000		* Run the insert method of all instanced class
1001		*
1002		* @return The insert for each entity
1003		*/
1004		function run_insert()
1005		{
1006			foreach($this->entities as $entity)
1007			{
1008				array_push($this->sql_inserts, $entity->insert());
1009			}
1010			return $this->sql_inserts;
1011		}
1012
1013		function updates($datas)
1014		{
1015			foreach($datas as $data => $value)
1016			{
1017				$this->update($data, $value);
1018			}
1019		}
1020
1021		/**
1022		* Find the class an call it, giving to her the field name
1023		*
1024		* @param string $data The $data that is requested. ie. false field
1025		* @param string|array $value The $value that is requested
1026		*/
1027		function update($data, $value)
1028		{
1029			$ent = $this->get_entity($data);
1030			$this->entities[$ent]->add_update($data, $value);
1031		}
1032
1033		/**
1034		* Run the update method of all instanced class
1035		*
1036		* @return The update for each entity
1037		*/
1038		function run_update()
1039		{
1040			foreach($this->entities as $entity)
1041			{
1042				array_push($this->sql_updates, $entity->update());
1043			}
1044			return $this->sql_updates;
1045		}
1046
1047		/**
1048		* Delete a record according to criteria.
1049		*
1050		* For this delete we can only support one criteria, this is because coding the function that get the
1051		* right fields names and parse a tree of operations is complex (in O(n^y) terms), so it will do this less
1052		* scalable. I just decided dropped this feature for delete operations.
1053		* @param Array $criteria Form: (Field, value) or string with `field = value'
1054		* @param integer $action PHPGW_SQL_RETURN_SQL | PHPGW_SQL_RUN_SQL
1055		* @return string SQL string
1056		*/
1057		function _delete($criteria, $action=PHPGW_SQL_RETURN_SQL)
1058		{
1059			if(is_string($criteria))
1060			{
1061				//Hope that app-developer know what sends
1062				list($field,$value) = explode(',',$criteria);
1063				if(empty($field) || empty($value))
1064				{
1065					$GLOBALS['phpgw']->log->error(array('text'=>'E-ArgError, err: Wrong argument $criteria' .$criteria,
1066									'p1'=>'This is a grave Error',
1067									'file'=>__FILE__,
1068									'line'=>__LINE__));
1069				}
1070			}
1071			else
1072			{
1073				$field = array_shift($criteria);
1074				$value = array_shift($criteria);
1075			}
1076			$entity = $this->instance_volatile($this->ent_name($field), False);
1077			$real_name = $entity->real_field($field);
1078			$criteria = $entity->index_criteria(array('real_field' => $real_name, 'value' => $value, 'field' => $field));
1079			return $entity->delete($criteria, $action);
1080		}
1081
1082		/**
1083		* Run the delete method of all instanced class
1084		*
1085		* @return The delete for each entity
1086		*/
1087		function run_delete()
1088		{
1089			foreach($this->entities as $entity)
1090			{
1091				array_push($this->sql_deletes, $entity->delete());
1092			}
1093			return $this->sql_deletes;
1094		}
1095
1096		function request($data)
1097		{
1098			//FIMXE:
1099			$this->operation = 'select';
1100			if(is_string($data))
1101			{
1102				$fields = explode(',', $data);
1103			}
1104			elseif(is_array($data))
1105			{
1106				$fields = &$data;
1107			}
1108			foreach($fields as $field)
1109			{
1110				$this->ldebug('request', array('Data' => $field));
1111				$this->_request($field);
1112			}
1113		}
1114
1115		/**
1116		* Find the class an call it, giving to her the field name
1117		*
1118		* @param string $data The $data that is requested
1119		*/
1120		function _request($data)
1121		{
1122			/**
1123			* This is the NEW parent/child fields
1124			* implementation, I hope this solve my
1125			* problems, but this don't remove that exist a
1126			* design problem with the n-n relations and
1127			* 1-1.
1128			*/
1129			if (is_array($this->map[$data][PHPGW_SQL_CHILD_FIELDS]) && count($this->map[$data][PHPGW_SQL_CHILD_FIELDS]) > 0)
1130			{
1131				foreach($this->map[$data][PHPGW_SQL_CHILD_FIELDS] as $child)
1132				{
1133					$this->_request($child);
1134				}
1135			}
1136
1137			$ent = $this->get_entity($data);
1138			$this->ldebug('_request', array('Data' => $data,
1139						       'Entity name' => $ent));
1140			if(is_object($this->entities[$ent]))
1141			{
1142				$this->entities[$ent]->add_select($data);
1143			}
1144			else
1145			{
1146				$this->raise_error($data);
1147			}
1148			if(intval($this->map[$data][PHPGW_SQL_CHANGE_DISTANCE]) != 0)
1149			{
1150				$this->distance[$ent] = $this->map[$data][PHPGW_SQL_CHANGE_DISTANCE];
1151			}
1152		}
1153
1154		/**
1155		* Add external select fields to actual list
1156		*
1157		* The string is added just before the SELECT clause be created.
1158		* @param string $select actual string with select part.
1159		*/
1160		function append_select($select)
1161		{
1162			$this->external_select_value = $select;
1163		}
1164
1165		/**
1166		* Add external JOINS to actual FROM part
1167		*
1168		* String is added just before FROM clause be created.
1169		* @param string $from actual string with select part.
1170		*/
1171		function append_from($from)
1172		{
1173			$this->external_from_value = $from;
1174		}
1175
1176		/**
1177		* Add external criterias to actual query
1178		*
1179		* Run just before WHERE clause be created.
1180		* @param Array $criteria format: (token, string) where token is a conjunction class method
1181		* @see or__
1182		* @see and__
1183		*/
1184		function append_criteria($criteria)
1185		{
1186			$this->external_criteria_value = $criteria;
1187		}
1188
1189		/**
1190		* Allow add a link on the fly, usefull when you need add a link to a entity that is not part of your application db.
1191		*
1192		* @param string $local_entity this is the name of the entity on application db that external entity want to join.
1193		* @param string $local_field false field of $local_entity to make the join
1194		* @param string $external_entity name of entity that want to add to map for one query
1195		* @param string $external_field false field name from $enternal_entity that will be used for join
1196		* @param integer $key_type PHPGW_SQL_LAZY_KEY or PHPGW_SQL_REQUIRED_KEY.
1197		* @see set_ilinks
1198		* @see set_elinks
1199		*/
1200		function add_link($local_entity, $local_field, $external_entity, $external_field, $key_type=PHPGW_SQL_LAZY_KEY)
1201		{
1202			$local_entity  = $this->get_entity($local_field);
1203			$this->entities[$local_entity]->set_elinks($local_field, $external_entity, $external_field);
1204			$external_entity  = $this->get_entity($external_field);
1205			$this->entities[$external_entity]->set_ilinks($external_field, $local_entity, $local_field, $key_type);
1206		}
1207
1208		/**
1209		* Must raise errors for this class.
1210		*
1211		* @param $data From where error happens
1212		*/
1213		function raise_error($data = '')
1214		{
1215		}
1216
1217		function abort($data = '')
1218		{
1219			$GLOBALS['phpgw']->common->phpgw_exit();
1220		}
1221
1222		function ldebug($myfoo, $data, $type = 'string', $err = '')
1223		{
1224// 			if (!((($myfoo != '') xor
1225// 			       ($myfoo != '')) xor
1226// 			      ($myfoo == '')) xor
1227// 			    ($myfoo == 'get_sql'))
1228//   			if ($myfoo != 'SQL')
1229// 			{
1230  			return;
1231// 			}
1232
1233			$classname = '<strong>Class: '.get_class($this).
1234				"\n<br />Function: $myfoo\n<br /></strong>";
1235			switch($type)
1236			{
1237			case 'string':
1238				foreach($data as $vari => $value)
1239				{
1240					if (is_array($value))
1241					{
1242						$this->ldebug($myfoo.' recursivecall',
1243							      array('&nbsp;&nbsp;-$vari: ' => $value), 'dump');
1244					}
1245					else
1246					{
1247						$output .= "&nbsp;&nbsp;-$vari = $value \n<br />";
1248					}
1249				}
1250				break;
1251			case 'dump':
1252				foreach($data as $vari => $value)
1253				{
1254					$output .= "&nbsp;&nbsp;-$vari = ";
1255					$output .= var_dump($value)."\n<br />";
1256					//$output .= var_export($value, True)."\n<br />";
1257				}
1258				break;
1259			default:
1260				$output .= "\n<br />$data\n<br />";
1261			}
1262			if ($err != '')
1263			{
1264				$output = $classname.'Error: '.$output."\n<br />";
1265			}
1266			else
1267			{
1268				$output = $classname.$output."\n<br />";
1269			}
1270			echo $output;
1271		}
1272	}
1273?>
1274