Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
DALBaker.inc
1 <?php
13 require_once dirname(__FILE__).'/DAL.inc';
14 //require_once dirname(dirname(__FILE__)).'/Channels/Channels.inc';
15 //require_once dirname(dirname(__FILE__)).'/Channels/ChannelsBaker.inc';
16 //require_once dirname(dirname(__FILE__)).'/Libs/FileSystem/FileSystem.inc';
17 //require_once dirname(dirname(__FILE__)).'/Libs/XML/XML.inc';
18 require_once dirname(__FILE__).'/Exceptions/DALParserException.inc';
19 require_once dirname(__FILE__).'/Exceptions/DALBakerException.inc';
20 require_once dirname(__FILE__).'/Query.inc';
21 
30 class DALBaker
31 {
32 
40  private static $_whereConditions = array(
41  'equal' => '=',
42  'not-equal' => '!=',
43  'equal-or-greater' => '>=',
44  'equal-or-less' => '<=',
45  'greater' => '>',
46  'less' => '<',
47  'like' => 'LIKE',
48  'not-like' => 'NOT LIKE',
49  'in' => 'IN',
50  'not-in' => 'NOT IN',
51  'is-null' => 'IS NULL',
52  'not-null' => 'NOT NULL',
53  );
54 
61  private static $_assertionTypes = array(
62  'assert-true',
63  'assert-false',
64  'assert-empty',
65  'assert-null',
66  'assert-equal',
67  'assert-greater-than',
68  'assert-less-than',
69  );
70 
79  private static $_forbiddenVariablecharacters = array(
80  "'",
81  "\n",
82  "\t",
83  ' ',
84  '_',
85  '-',
86  '[',
87  ']',
88  '$',
89  '"',
90  ';',
91  '!',
92  );
93 
94 
103  private function __construct()
104  {
105 
106  }//end __construct()
107 
108 
109  /*
110  New System Handling
111  */
112 
113 
125  public static function getSystemSchemaDocument($systemName)
126  {
127  // Get the system's schema data.
128  $systemPath = Channels::getSystemsPath($systemName);
129  $schemaXML = $systemPath.'/DB/schema.xml';
130  if (file_exists($schemaXML) === FALSE) {
131  return NULL;
132  }
133 
134  $doc = new DomDocument();
135  $doc->load($schemaXML);
136 
137  return $doc;
138 
139  }//end getSystemSchemaDocument()
140 
141 
154  public static function addSystemSchema($systemName)
155  {
156  if (ChannelsBaker::systemExists($systemName) === FALSE) {
157  return FALSE;
158  }
159 
160  $schemaDoc = self::getSystemSchemaDocument($systemName);
161  if ($schemaDoc === NULL) {
162  return FALSE;
163  }
164 
165  $schemaNode = $schemaDoc->getElementsByTagName('schema');
166  if ($schemaNode->length === 0) {
167  return FALSE;
168  }
169 
170  $schemaNode = $schemaNode->item(0);
171  $schemaArray = self::parseSchema($schemaNode);
172  $converter = self::getConverter();
173  if (array_key_exists('CREATE', $schemaArray) === TRUE) {
174  foreach ($schemaArray['CREATE'] as $table) {
175  $tableName = $table['table'];
176  // Check if table exists.
177  $exists = self::_dbTableExists($tableName, $converter);
178  if ($exists === FALSE) {
179  $sql = $converter->convertCreateTable($table);
180  DAL::executeQueries($sql);
181  }
182  }
183  }
184 
185  $schemaOvenPath = DAL::getOvenPath($systemName);
186  if (file_exists($schemaOvenPath) === FALSE) {
187  mkdir($schemaOvenPath);
188  }
189 
190  $fileName = $schemaOvenPath.'/'.$systemName.'.schema';
191  file_put_contents($fileName, $schemaDoc->saveXML());
192 
193  return TRUE;
194 
195  }//end addSystemSchema()
196 
197 
209  protected static function _dbDropTable($tableName, DALConverter $converter=NULL)
210  {
211  if ($converter === NULL) {
212  $converter = self::getConverter();
213  }
214 
215  if (self::_dbTableExists($tableName, $converter) === TRUE) {
216  $sql = $converter->convertDropTable($tableName);
217  DAL::executeSql($sql);
218  return TRUE;
219  }
220 
221  return FALSE;
222 
223  }//end _dbDropTable()
224 
225 
235  protected static function _dbTableExists($tableName, DALConverter $converter=NULL)
236  {
237  if ($converter === NULL) {
238  $converter = self::getConverter();
239  }
240 
241  $schema = '\''.DAL::getDbName().'\'';
242  $name = '\''.$tableName.'\'';
243  $sql = $converter->handleFunctionTableExists($name, $schema);
244  $result = DAL::query($sql);
245  $row = $result->fetchAll();
246  $count = (int)current(current($row));
247  if ($count === 1) {
248  return TRUE;
249  }
250 
251  return FALSE;
252 
253  }//end _dbTableExists()
254 
255 
265  public static function addSystemQueries($systemName)
266  {
267  if (ChannelsBaker::systemExists($systemName) === FALSE) {
268  return FALSE;
269  }
270 
271  $systemPath = Channels::getSystemsPath($systemName);
272  $queryXML = $systemPath.'/DB/queries.xml';
273  if (file_exists($queryXML) === FALSE) {
274  return FALSE;
275  }
276 
277  $success = self::processQueriesFile($systemName, $queryXML);
278  return $success;
279 
280  }//end addSystemQueries()
281 
282 
293  public static function processQueriesFile($systemName, $fileName)
294  {
295  if (file_exists($fileName) === FALSE) {
296  return FALSE;
297  }
298 
299  $doc = new DomDocument();
300  $doc->load($fileName);
301  $queryNode = $doc->getElementsByTagName('queries')->item(0);
302 
303  $currentDbType = DAL::getDbType();
304 
305  foreach ($queryNode->childNodes as $child) {
306  if ($child->nodeType !== 1) {
307  // Text Node. Probably just whitespace or a comment. We don't
308  // want to deal with it.
309  continue;
310  }
311 
312  if ($child->tagName === 'query') {
313  if ($child->hasAttribute('id') === FALSE) {
314  $error = 'ID not specified for query in '.$systemName;
315  throw new DALBakerException($error);
316  }
317 
318  if ($child->hasAttribute('databases') === TRUE) {
319  $dbString = $child->getAttribute('databases');
320  $dbString = str_replace(' ', '', $dbString);
321  $dbs = explode(',', $dbString);
322  if (in_array($currentDbType, $dbs) === FALSE) {
323  $id = $child->getAttribute('id');
324  echo 'Skipped Query: '.$id." (not for this DB type)\n";
325  continue;
326  }
327  }
328 
329  if ($child->hasAttribute('hookid') === TRUE) {
330  $hookId = $child->getAttribute('hookid');
331  if (substr_count($hookId, '.') === 1) {
332  // We have a fragment.
333  self::addQueryFragment($systemName, $child);
334  } else {
335  // We have a sub query with a three part hookid (*.*.*).
336  self::addSubQuery($systemName, $child);
337  }
338  } else {
339  // New Query.
340  self::addQuery($systemName, $child);
341  }
342  }//end if
343  }//end foreach childnodes
344 
345  }//end processQueriesFile()
346 
347 
348  /*
349  Query Merging
350  */
351 
352 
366  public static function mergeQuery($systemName, $queryName)
367  {
368  $doc = self::getQueryXML($systemName, $queryName);
369 
370  /*
371  We want to traverse through the document, and determine what
372  fragments we have. If they have assertions, they must be added
373  accordingly. If they don't have assertions, then they must be
374  added to the base query, as they will always be executed regardless
375  of the state of the system.
376  */
377 
378  $completeCode = '';
379 
380  // This is our query with no fragments attached.
381  $baseQuery = $doc->getElementsByTagname('basequery')->item(0);
382  $primaryNode = $baseQuery->getElementsByTagName('primary');
383  if ($primaryNode->length === 0) {
384  $msg = 'No primary query found for query.';
385  throw new DALBakerException($msg);
386  } else if ($primaryNode->length > 1) {
387  $msg = 'Multiple primary queries found in query';
388  throw new DALBakerException($msg);
389  }
390 
391  // We have exactly 1 primary tag in our base query. This will be the
392  // SQL executed if no alternate paths are executed.
393  $primaryNode = $primaryNode->item(0);
394  $alternateNodes = $baseQuery->getElementsByTagName('alternate');
395  $alternatives = XML::nodeListToArray($alternateNodes);
396  $alternatives[] = $primaryNode;
397  $conditions = array();
398  $alternateCount = 1;
399  foreach ($alternatives as $basePath) {
400  // Make sure we get down to the actual base query, without
401  // assertions surrounding it.
402  $queryParent = $basePath;
403  $previousNode = $queryParent;
404  while ($queryParent = XML::getFirstChildElement($queryParent)) {
405  if (in_array($queryParent->tagName, self::$_assertionTypes) === FALSE) {
406  $queryParent = $previousNode;
407  break;
408  }
409 
410  $previousNode = $queryParent;
411  }
412 
413  // Stores any fragments that have assertions wrapping them.
414  $assertions = array();
415  // Loop through each fragment defined for the query.
416  $fragmentsNode = $doc->getElementsByTagName('fragments')->item(0);
417  foreach ($fragmentsNode->childNodes as $fragment) {
418  // Retrieve the assertions that exist.
419  $types = self::$_assertionTypes;
420  $assertionList = XML::getElementsByTagNames($types, $fragment);
421  if (count($assertionList) === 0) {
422  // No assertion found, so this should always be part of the base
423  // query.
424  $select = $fragment->getElementsByTagName('select')->item(0);
425  $basePath = self::_combineFragment($basePath, $select);
426  $basePath = $doc->importNode($basePath, TRUE);
427  } else {
428  // This assertion will be handled when the tree is constructed.
429  $assertions[] = $fragment;
430  }
431  }
432 
433  // Now we need to evaluate assertions.
434  // So we need to get them into a tree structure.
435  $assertTree = self::_recurseAddAssertion($assertions);
436 
437  // Print out each assertion in the assertion tree with each nodes'
438  // SQL representation.
439  $queryParent = $doc->importNode($queryParent, TRUE);
440  $phpCode = '';
441  $queryBaseId = $systemName.'_'.$queryName;
442  if ($queryParent->tagName !== 'primary') {
443  $queryBaseId .= 'Alternate'.$alternateCount;
444  $alternateCount++;
445  }
446 
447  if (count($assertions) !== 0) {
448  $phpCode = self::_recursePrintAssertions($assertTree, $queryParent, $queryBaseId);
449  } else {
450  // Just the base query to print out.
451  $phpCode = self::_printNodeSql($queryParent, $queryBaseId);
452  }
453 
454  $alternateCount++;
455 
456  // If it is primary with alternates, print else, otherwise print just
457  // assign, otherwise it is an alternate, and we need to evaluate
458  // assertions.
459  if ($basePath->tagName === 'primary') {
460  if (count($alternatives) > 1) {
461  $phpCode = "{\n$phpCode\n}\n";
462  } else {
463  $phpCode .= "\n";
464  }
465  } else {
466  $phpCode = self::_printAlternateAssertions($basePath, $phpCode);
467  }
468 
469  $conditions[] = $phpCode;
470  }//end foreach
471 
472  $completeCode = implode(' else ', $conditions);
473  return $completeCode;
474 
475  }//end mergeQuery()
476 
477 
478  /*
479  Assertions
480  */
481 
482 
494  protected static function _printAlternateAssertions(DomElement $alternate, $contents)
495  {
496  $asserts = XML::getElementsByTagNames(self::$_assertionTypes, $alternate);
497  $assertVars = array();
498  foreach ($asserts as $assertion) {
499  $varName = self::_getAssertionVariableName($assertion);
500  $assertVars[] = "($varName === TRUE)";
501  }
502 
503  $condition = 'if ('.implode(' && ', $assertVars).") {\n";
504  return $condition.$contents."\n}";
505 
506  }//end _printAlternateAssertions()
507 
508 
535  protected static function _recurseAddAssertion(array $assertions, array $currentStack=array())
536  {
537  $retval = array();
538  if (empty($assertions) === TRUE) {
539  return $retval;
540  }
541 
542  $currentAssertion = array_shift($assertions);
543  // Setup the else condition first, as it doesn't have the assertion
544  // on the current level. It should just pass through the current stack.
545  $elseChildren = self::_recurseAddAssertion($assertions, $currentStack);
546  $elseCondition = array(
547  'assertions' => $currentStack,
548  'children' => $elseChildren,
549  );
550 
551  // Add the current assertion for the IF condition.
552  $currentStack[] = $currentAssertion;
553 
554  $ifChildren = self::_recurseAddAssertion($assertions, $currentStack);
555 
556  $ifCondition = array(
557  'current_assertion' => $currentAssertion,
558  'assertions' => $currentStack,
559  'children' => $ifChildren,
560  );
561 
562  $retval['if_condition'] = $ifCondition;
563  $retval['else_condition'] = $elseCondition;
564 
565  return $retval;
566 
567  }//end _recurseAddAssertion()
568 
569 
589  protected static function _recursePrintAssertions(array $tree, DomElement $baseQuery, $baseQueryId, $level=0)
590  {
591  $contents = '';
592  if (empty($tree) === TRUE) {
593  return '';
594  }
595 
596  $ifCondition = $tree['if_condition'];
597  $elseCondition = $tree['else_condition'];
598  $currentAssert = $ifCondition['current_assertion'];
599 
600  // Find the assertion, and determine what variable to test for in the
601  // if condition.
602  $types = self::$_assertionTypes;
603  $assertionList = XML::getElementsByTagNames($types, $currentAssert);
604  $assertionNode = array_shift($assertionList);
605  $assertionChannel = $assertionNode->getAttribute('channel');
606  $assertVariable = self::_getAssertionVariableName($assertionNode);
607 
608  // Add the code to the string, for the if and else contents.
609  $contents .= "\nif ($assertVariable === TRUE) {\n";
610  if (empty($ifCondition['children']) === TRUE) {
611  // Empty children, we need to print out the query.
612  $fragments = $ifCondition['assertions'];
613  $ifQuery = self::_mergeAllAssertions($baseQuery, $fragments);
614  $idParts = array();
615  foreach ($fragments as $queryPart) {
616  $idParts[] = $queryPart->getAttribute('id');
617  }
618 
619  $contents .= self::_printNodeSql($ifQuery, $baseQueryId, $idParts);
620  } else {
621  // It has children, we need to recurse again.
622  $contents .= self::_recursePrintAssertions($ifCondition['children'], $baseQuery, $baseQueryId, ($level + 1));
623  }
624 
625  $contents .= "\n} else {\n";
626  if (empty($ifCondition['children']) === TRUE) {
627  // Empty children, we need to print out the query.
628  $fragments = $elseCondition['assertions'];
629  $elseQuery = self::_mergeAllAssertions($baseQuery, $fragments);
630  $idParts = array();
631  foreach ($fragments as $queryPart) {
632  $idParts[] = $queryPart->getAttribute('id');
633  }
634 
635  $contents .= self::_printNodeSql($elseQuery, $baseQueryId, $idParts);
636  } else {
637  // It has children, we need to recurse again.
638  $contents .= self::_recursePrintAssertions($elseCondition['children'], $baseQuery, $baseQueryId, ($level + 1));
639  }
640 
641  $indentSpace = ' ';
642  $contents = str_replace("\n", "\n$indentSpace", $contents);
643  $contents .= "}\n";
644  return $contents;
645 
646  }//end _recursePrintAssertions()
647 
648 
663  protected static function _mergeAllAssertions(DomElement $baseQuery, array $assertions)
664  {
665  $doc = new DomDocument();
666  // We want a new copy of the base node, that we can add all assertions
667  // to, without altering the original.
668  $returnQuery = $doc->importNode($baseQuery, TRUE);
669  foreach ($assertions as $fragment) {
670  // Add each fragment to the query, making sure we only pass the
671  // select part of the fragment to _combineFragment().
672  $select = $fragment->getElementsByTagName('select')->item(0);
673  $returnQuery = self::_combineFragment($returnQuery, $select);
674  }
675 
676  return $returnQuery;
677 
678  }//end _mergeAllAssertions()
679 
680 
690  protected static function _getQueryAssertions($systemName, $queryName)
691  {
692  $doc = self::getQueryXML($systemName, $queryName);
693 
694  // Find a list of all assertions and return their array.
695  $types = self::$_assertionTypes;
696  $assertions = XML::getElementsByTagNames($types, $doc);
697 
698  return $assertions;
699 
700  }//end _getQueryAssertions()
701 
702 
721  protected static function _getAssertionVariableName(DomElement $assertion)
722  {
723  $channelName = '';
724  $variableName = '';
725  if ($assertion->hasAttribute('channel') === TRUE) {
726  $channelName = $assertion->getAttribute('channel');
727  if (trim($channelName) === '') {
728  throw new DALBakerException('Channel name is empty');
729  }
730 
731  list($system, $event) = explode('::', $channelName);
732  $variableName = '$'.strtolower($system).ucfirst($event);
733  } else if ($assertion->hasAttribute('data') === TRUE) {
734  $variableName = '$'.$assertion->getAttribute('data').'ChannelData';
735  $variableName .= str_replace('assert-', '', $assertion->tagName);
736  // Shorten the value if need be.
737  $value = strval($assertion->getAttribute('value'));
738  if (strlen($value) > 5) {
739  $value = substr($value, 0, 5);
740  }
741 
742  $variableName .= $value;
743  } else {
744  $functionCall = $assertion->getElementsByTagName('function-call');
745  if ($functionCall->length === 0) {
746  $error = 'No function call specified for assertion.';
747  throw new DALBakerException($error);
748  }
749 
750  $functionCall = $functionCall->item(0);
751  $argList = $functionCall->getElementsByTagName('arg');
752  $variableName = $functionCall->getAttribute('function');
753  foreach ($argList as $argNode) {
754  // Get the text and replace any forbidden characters.
755  $argText = $argNode->nodeValue;
756  $replace = self::$_forbiddenVariablecharacters;
757  $argText = str_replace($replace, ' ', $argText);
758  $argText = str_replace(' ', '', ucwords($argText));
759  // Add the text to the variable name.
760  $variableName .= ucfirst($argText);
761  }
762 
763  $variableName = '$'.$variableName;
764  }//end if
765 
766  return $variableName;
767 
768  }//end _getAssertionVariableName()
769 
770 
771  /*
772  XML Handling
773  */
774 
775 
786  public static function generateQueryXML($systemName, $queryName)
787  {
788  $systemPath = dirname(__FILE__).'/Oven/'.$systemName;
789  $fileName = $systemPath.'/'.$queryName.'.xml';
790 
791  if (file_exists($systemPath) === FALSE) {
792  mkdir($systemPath);
793  }
794 
795  $contents = '<querychannel system="'.$systemName.'" query="';
796  $contents .= $queryName.'">';
797  $contents .= '<basequery />';
798  $contents .= '<fragments />';
799  $contents .= '<sub-queries />';
800  $contents .= '</querychannel>';
801 
802  return file_put_contents($fileName, $contents);
803 
804  }//end generateQueryXML()
805 
806 
817  public static function queryXMLExists($systemName, $queryName)
818  {
819  $systemPath = dirname(__FILE__).'/Oven/'.$systemName;
820  $fileName = $systemPath.'/'.$queryName.'.xml';
821 
822  return file_exists($fileName);
823 
824  }//end queryXMLExists()
825 
826 
837  public static function getQueryXML($systemName, $queryName)
838  {
839  $systemPath = dirname(__FILE__).'/Oven/'.$systemName;
840  $fileName = $systemPath.'/'.$queryName.'.xml';
841 
842  if (self::queryXMLExists($systemName, $queryName) === FALSE) {
843  self::generateQueryXML($systemName, $queryName);
844  }
845 
846  $doc = new DomDocument();
847  $doc->load($fileName);
848  return $doc;
849 
850  }//end getQueryXML()
851 
852 
864  public static function saveQueryXML($system, $queryName, DomDocument $doc)
865  {
866  $systemPath = dirname(__FILE__).'/Oven/'.$system;
867  $fileName = $systemPath.'/'.$queryName.'.xml';
868 
869  if (file_exists($fileName)) {
870  unlink($fileName);
871  }
872 
873  $doc->save($fileName);
874 
875  }//end saveQueryXML()
876 
877 
878  /*
879  Adding Query Fragments
880  */
881 
882 
895  public static function addQuery($systemName, DomElement $query)
896  {
897  $queryId = $query->getAttribute('id');
898 
899  //if (self::queryXMLExists($systemName, $queryId) === FALSE) {
900  self::generateQueryXML($systemName, $queryId);
901  //}
902 
903  $doc = self::getQueryXML($systemName, $queryId);
904  $queryNode = $doc->getElementsByTagName('basequery')->item(0);
905 
906  if ($queryNode !== NULL) {
907  if ($queryNode->getElementsByTagName('query')->length !== 0) {
908  return FALSE;
909  }
910  }
911 
912  // Extract and replace any hard-coded news in the query, with place
913  // holder variables, after setting the system for use by the
914  // _prepareFragment() method.
915  $query->setAttribute('system', $systemName);
916  $importedNode = self::_prepareFragment($query);
917  // Import the node to the document.
918  $importedNode = $doc->importNode($importedNode, TRUE);
919 
920  $queryNode->appendChild($importedNode);
921  self::saveQueryXML($systemName, $queryId, $doc);
922 
923  return TRUE;
924 
925  }//end addQuery()
926 
927 
940  public static function addQueryFragment($systemName, DomElement $fragment)
941  {
942  // We need to add the originating system to the fragment, so that we
943  // know where it comes from when it's out in the wild.
944  $fragment->setAttribute('system', $systemName);
945 
946  // We need to parse this fragment, and convert all hard coded values to
947  // placeholders.
948  $fragment = self::_prepareFragment($fragment);
949 
950  $hookId = $fragment->getAttribute('hookid');
951  list($targetSystem, $targetQuery) = explode('.', $hookId);
952 
953  //if (self::queryXMLExists($targetSystem, $targetQuery) === FALSE) {
954  self::generateQueryXML($targetSystem, $targetQuery);
955  //}
956 
957  $doc = self::getQueryXML($targetSystem, $targetQuery);
958  $fragmentsNode = $doc->getElementsByTagName('fragments')->item(0);
959 
960  /*
961  Have to check if the query fragment exists. If it does, remove it.
962  */
963 
964  // Import the node to the document.
965  $importedNode = $doc->importNode($fragment, TRUE);
966  $fragmentsNode->appendChild($importedNode);
967  self::saveQueryXML($targetSystem, $targetQuery, $doc);
968 
969  }//end addQueryFragment()
970 
971 
984  public static function addSubQuery($systemName, DomElement $subQuery)
985  {
986  // We need to add the originating system to the fragment, so that we
987  // know where it comes from when it's out in the wild.
988  $subQuery->setAttribute('system', $systemName);
989 
990  // We need to parse this sub-query, and convert all hard coded values to
991  // placeholders.
992  $subQuery = self::_prepareFragment($subQuery);
993 
994  $hookId = $subQuery->getAttribute('hookid');
995  list($targetSystem, $targetQuery) = explode('.', $hookId);
996 
997  //if (self::queryXMLExists($targetSystem, $targetQuery) === FALSE) {
998  self::generateQueryXML($targetSystem, $targetQuery);
999  //}
1000 
1001  $doc = self::getQueryXML($targetSystem, $targetQuery);
1002  $subQueriesNode = $doc->getElementsByTagName('sub-queries')->item(0);
1003 
1004  /*
1005  Have to check if the query fragment exists. If it does, remove it.
1006  */
1007 
1008  // Import the node to the document.
1009  $importedNode = $doc->importNode($subQuery, TRUE);
1010  $subQueriesNode->appendChild($importedNode);
1011  self::saveQueryXML($targetSystem, $targetQuery, $doc);
1012 
1013  }//end addSubQuery()
1014 
1015 
1038  protected static function _combineFragment(DomElement $query, DomElement $fragment)
1039  {
1040  $doc = new DomDocument();
1041  // Import both nodes, so we are working in the same document.
1042  $query = $doc->importNode($query, TRUE);
1043  $fragment = $doc->importNode($fragment, TRUE);
1044 
1045  foreach ($fragment->childNodes as $section) {
1046  if ($section->nodeType !== 1) {
1047  // Text node, disregard.
1048  continue;
1049  }
1050 
1051  // See if the existing query has the same section that we can add
1052  // our new data too.
1053  $currentNode = $query->getElementsByTagName($section->tagName);
1054  if ($currentNode->length === 0) {
1055  // We don't have this section in our base query yet, we can go
1056  // ahead and clone it over.
1057  $select = $query->getElementsByTagName('select')->item(0);
1058  $clone = $section->cloneNode(TRUE);
1059  $select->appendChild($clone);
1060  } else {
1061  $currentNode = $currentNode->item(0);
1062  // Existing section, traverse the nodes of the fragment and
1063  // import them under the existing section.
1064  foreach ($section->childNodes as $condition) {
1065  if ($condition->nodeType !== 1) {
1066  continue;
1067  }
1068 
1069  // Clone the node and append it to the query's Element.
1070  // Cloning is done, as appending the original element seems
1071  // to stuff up the loop we are in.
1072  $copy = $condition->cloneNode(TRUE);
1073  $currentNode->appendChild($copy);
1074  }
1075  }//end if
1076  }//end foreach
1077 
1078  return $query;
1079 
1080  }//end _combineFragment()
1081 
1082 
1098  protected static function _prepareFragment(DomElement $query)
1099  {
1100  $doc = new DomDocument();
1101 
1102  // Import the node into our new doc.
1103  $returnQuery = $doc->importNode($query, TRUE);
1104  // Extract any hardcoded values that are in the query.
1105  $returnQuery = self::_extractPlaceHolders($returnQuery);
1106  $returnQuery = self::_extractKeywords($returnQuery);
1107  // Extract any bindings that are in the query.
1108  $returnQuery = self::_extractBindings($returnQuery);
1109  $returnQuery = $doc->importNode($returnQuery, TRUE);
1110 
1111  return $returnQuery;
1112 
1113  }//end _prepareFragment()
1114 
1115 
1116  /*
1117  PHP Code Handling
1118  */
1119 
1120 
1134  public static function bakeQueriesFile($systemName)
1135  {
1136  if (trim($systemName) === '') {
1137  throw new DALBakerException('System name is empty');
1138  }
1139 
1140  // Write the file.
1141  $ovenDir = dirname(__FILE__).'/Oven/'.$systemName;
1142 
1143  if (file_exists($ovenDir) === FALSE) {
1144  mkdir($ovenDir);
1145  }
1146 
1147  $fileName = $ovenDir.'/'.$systemName.'Queries.inc';
1148 
1149  $contents = '<?'."php\n\n";
1150  $contents .= "require_once dirname(dirname(dirname(__FILE__))).'/DAL.inc';\n";
1151  $contents .= "class $systemName"."Queries\n{\n\n";
1152  // Determine what XML files are present in the oven directory for this
1153  // system, and print out the method for each.
1154  $types = array('.xml');
1155  $xmlFiles = FileSystem::listDirectory($ovenDir, $types, TRUE, FALSE);
1156  foreach ($xmlFiles as $queryName) {
1157  $queryName = basename($queryName, '.xml');
1158  $contents .= self::bakeQueryMethod($systemName, $queryName);
1159  }
1160 
1161  $contents .= "\n\n}\n?>";
1162 
1163  file_put_contents($fileName, $contents);
1164 
1165  }//end bakeQueriesFile()
1166 
1167 
1183  public static function bakeQueryMethod($systemName, $queryName)
1184  {
1185  // Output the method signature.
1186  $methodName = 'prepare'.ucwords($queryName).'Query';
1187  $contents = "public static function $methodName(array \$data, \$bind)\n{\n";
1188  // Print out the initialisation of hard coded values.
1189  $contents .= self::_printPlaceHolderVariables($systemName, $queryName);
1190  // Get the values required for our assertions.
1191  $contents .= "try {\n";
1192  // Loop through each assertion, and output a call that assigns its
1193  // value to a variable.
1194  $assertions = self::_getQueryAssertions($systemName, $queryName);
1195  // Print out the code for including the assertions systems and
1196  // initialising their values.
1197  $contents .= self::_printAssertionCalls($assertions);
1198  $contents .= "} catch (ChannelException \$e) {\n";
1199  $contents .= " // Exception thrown in the called channel.\n";
1200  $contents .= "}\n";
1201 
1202  // Get all the conditions that represent all of the query's
1203  // combinations, and add it to the contents.
1204  $queryBody = self::mergeQuery($systemName, $queryName);
1205  $contents .= $queryBody;
1206  $subQueries = self::printSubQueries($systemName, $queryName);
1207  $contents .= $subQueries;
1208  $contents .= self::_printKeywords($systemName, $queryName);
1209  $contents .= self::_printBindings($systemName, $queryName);
1210  $contents .= "\n}//end $methodName()\n";
1211 
1212  return $contents;
1213 
1214  }//end bakeQueryMethod()
1215 
1216 
1247  protected static function _printAssertionCalls(array $assertions)
1248  {
1249  // Will hold the return value.
1250  $contents = '';
1251  // Holds the assignment calls.
1252  $assignments = "\n// Initialise values for use in the conditions\n";
1253  // Holds the calls to include the appopriate system files for assertion
1254  // plug calls.
1255  $includes = '';
1256  // Keep a track of what files we have included, so we don't print the
1257  // call twice.
1258  $includedFiles = array();
1259 
1260  foreach ($assertions as $assertion) {
1261  $assignments .= self::_printAssertionInitialisation($assertion)."\n";
1262  if ($assertion->hasAttribute('channel') === TRUE) {
1263  // This has a file that needs to be included, and we have to
1264  // investigate the node and find the system it requires.
1265  $channel = $assertion->getAttribute('channel');
1266  list($system, $methodName) = explode('::', $channel);
1267  // Add the require for the system if it hasn't been done by a
1268  // previous assertion.
1269  if (in_array($system, $includedFiles) === FALSE) {
1270  // Add the include for the system.
1271  $includes .= "Channels::includeSystem('$system');\n";
1272  // Actually include the file for our use in this method.
1273  Channels::requireSystemActions($system);
1274  $includedFiles[] = $system;
1275  }
1276  }
1277  }//end foreach
1278 
1279  $comment = "\n// Include required files for the assertion values.\n";
1280  if ($includes !== '') {
1281  $includes = $comment.$includes;
1282  }
1283 
1284  $contents = $includes.$assignments;
1285  return $contents;
1286 
1287  }//end _printAssertionCalls()
1288 
1289 
1304  protected static function _printAssertionInitialisation(DomElement $assertion)
1305  {
1306  $methodCall = '';
1307  if ($assertion->hasAttribute('channel') === TRUE) {
1308  // Standard assertion, with a call to a channel to exectute.
1309  $channel = $assertion->getAttribute('channel');
1310  list($system, $methodName) = explode('::', $channel);
1311 
1312  // Use the actions.xml file for the system to work out parameters.
1313  $actionsPath = Channels::getSystemsPath($system);
1314  $actionsPath .= '/actions.xml';
1315 
1316  $eventDoc = new DomDocument();
1317  $eventDoc->load($actionsPath);
1318 
1319  $parentNode = NULL;
1320 
1321  $actionNodes = $eventDoc->getElementsByTagName('action');
1322  foreach ($actionNodes as $node) {
1323  // Attempt to find the xml defninition for this channel.
1324  if ($node->getAttribute('name') === $methodName) {
1325  $parentNode = $node;
1326  break;
1327  }
1328  }
1329 
1330  $query = $parentNode->getElementsByTagName('query');
1331  $params = $parentNode->getElementsByTagName('param');
1332 
1333  // $paramIndexes holds references to this data in the "$data" array.
1334  $paramIndexes = array();
1335  foreach ($params as $param) {
1336  $paramIndexes[] = '$data[\''.$param->getAttribute('name').'\']';
1337  }
1338 
1339  // Construct the method call for the channel.
1340  $paramString = '('.implode(', ', $paramIndexes).')';
1341  $methodCall = $channel.$paramString;
1342 
1343  // Maybe query channel :-) Query object itself isn't very useful..
1344  if ($query->length > 0) {
1345  $methodCall = 'DAL::executeDALQuery('.$methodCall.')';
1346  }
1347  } else if ($assertion->hasAttribute('data') === TRUE) {
1348  // Has a channel data index specified.
1349  $methodCall = '$data[\''.$assertion->getAttribute('data').'\']';
1350  } else {
1351  // This is probably a function call (no channel specified) so the
1352  // method call has to be extracted.
1353  $functionCall = $assertion->getElementsByTagName('function-call');
1354  if ($functionCall->length === 0) {
1355  // No channel, and no function call. Nothing to assert.
1356  $error = 'No function call specified for assertion';
1357  throw new DALBakerException($error);
1358  }
1359 
1360  $functionCall = $functionCall->item(0);
1361 
1362  if ($functionCall->hasAttribute('function') === FALSE) {
1363  // No function specified for the function-call, which makes it
1364  // pretty tough to do anything.
1365  $error = 'No function call specified for assertion';
1366  throw new DALBakerException($error);
1367  }
1368 
1369  $functionName = $functionCall->getAttribute('function');
1370  $argList = $functionCall->getElementsByTagName('arg');
1371  $args = array();
1372  foreach ($argList as $argNode) {
1373  $args[] = self::_printAssertionArgument($argNode);
1374  }
1375 
1376  $argString = implode(', ', $args);
1377  $methodCall = "$functionName($argString)";
1378  }//end if
1379 
1380  $initCode = '';
1381 
1382  // Add the assignment call to the contents.
1383  switch ($assertion->tagName) {
1384  case 'assert-true':
1385  $methodCall = "($methodCall === TRUE)";
1386  break;
1387  case 'assert-false':
1388  $methodCall = "($methodCall === FALSE)";
1389  break;
1390  case 'assert-null':
1391  $methodCall = "($methodCall === NULL)";
1392  break;
1393  case 'assert-empty':
1394  // Can't call empty on function return value, do extra stuffs.
1395  $initCode = '$empty = '.$methodCall.';'."\n";
1396  $methodCall = '(empty($empty) === TRUE)';
1397  break;
1398  case 'assert-equal':
1399  case 'assert-greater-than':
1400  case 'assert-less-than':
1401  if ($assertion->hasAttribute('value') === FALSE) {
1402  $msg = 'No value specified for comparison assertion.';
1403  throw new DALBakerException($msg);
1404  }
1405 
1406  $assertValue = $assertion->getAttribute('value');
1407  if ($assertion->tagName === 'assert-equal') {
1408  $operator = '===';
1409  } else if ($assertion->tagName === 'assert-greater-than') {
1410  $operator = '>';
1411  } else {
1412  $operator = '<';
1413  }
1414 
1415  $methodCall = "($methodCall $operator $assertValue)";
1416  break;
1417  default:
1418  $error = 'Unknown Assertion Type: '.$assertion->tagName;
1419  throw new DALBakerException($error);
1420  }//end switch
1421 
1422  $variableName = self::_getAssertionVariableName($assertion);
1423 
1424  return $initCode.$variableName.' = '.$methodCall.';';
1425 
1426  }//end _printAssertionInitialisation()
1427 
1428 
1440  protected static function _printAssertionArgument(DomElement $argNode)
1441  {
1442  $type = $argNode->getAttribute('type');
1443  $data = $argNode->nodeValue;
1444  $argText = '';
1445 
1446  switch ($type) {
1447  case 'string':
1448  $argText = "'$data'";
1449  break;
1450  default:
1451  $argText = "$data";
1452  }
1453 
1454  return $argText;
1455 
1456  }//end _printAssertionArgument()
1457 
1458 
1474  protected static function _printNodeSql(DomElement $query, $baseId='', array $idParts=array())
1475  {
1476  // Call the DAL to convert the XML to SQL.
1477  sort($idParts);
1478  $sqlArray = self::constructSql($query);
1479  $sql = self::convertToSql($query, $sqlArray);
1480  $id = $baseId.md5(implode('_', $idParts));
1481  self::_writeQueryObject($id, $sqlArray, $sql);
1482  $returnQuery = "\$query = DAL::getQueryObject('$id');\n";
1483  return $returnQuery;
1484 
1485  }//end _printNodeSql()
1486 
1487 
1499  protected static function _writeQueryObject($id, array $sqlArray, $sql)
1500  {
1501  $queryObj = new Query($id, $sqlArray, $sql);
1502  $objData = serialize($queryObj);
1503  $fileName = $id.'.qob';
1504  $path = DAL::getQueryStorePath().'/'.$fileName;
1505  if (file_exists($path) === TRUE) {
1506  if (unlink($path) === FALSE) {
1507  throw new DALException('Query object already exists, and was unable to be removed');
1508  }
1509  }
1510 
1511  file_put_contents($path, $objData);
1512 
1513  }//end _writeQueryObject()
1514 
1515 
1529  protected static function _printPlaceHolderVariables($systemName, $queryName)
1530  {
1531  $doc = self::getQueryXML($systemName, $queryName);
1532 
1533  $contents = "\n// Values that were hard-coded in query definitions.\n";
1534 
1535  $placeHolders = $doc->getElementsByTagName('placeholder');
1536  foreach ($placeHolders as $element) {
1537  $varName = substr($element->getAttribute('var_name'), 1);
1538  $varValue = $element->getAttribute('value');
1539  $contents .= "\$$varName = '$varValue';\n";
1540  }
1541 
1542  $contents .= "\n";
1543  return $contents;
1544 
1545  }//end _printPlaceHolderVariables()
1546 
1547 
1557  public static function printSubQueries($systemName, $queryName)
1558  {
1559  $hookIds = self::_generateHookArray($systemName, $queryName);
1560  return self::_printHookCode($hookIds);
1561 
1562  }//end printSubQueries()
1563 
1564 
1575  protected static function _generateHookArray($systemName, $queryName)
1576  {
1577  $doc = self::getQueryXML($systemName, $queryName);
1578 
1579  $hookIds = array();
1580  $hooks = $doc->getElementsByTagName('hook');
1581  foreach ($hooks as $hook) {
1582  $id = $hook->getAttribute('id');
1583  $hooksIds[$id] = array();
1584  $parent = $hook->parentNode;
1585  $hookIds[$id]['parent-type'] = $parent->tagName;
1586  $hookIds[$id]['sub-queries'] = array();
1587  }
1588 
1589  $subNode = $doc->getElementsByTagName('sub-queries')->item(0);
1590  $queries = $subNode->getElementsByTagName('query');
1591  foreach ($queries as $subQuery) {
1592  $origHookId = $subQuery->getAttribute('hookid');
1593  $hookSys = $subQuery->getAttribute('system');
1594  $hookId = substr($origHookId, (strrpos($origHookId, '.') + 1));
1595  // Check this hook exists.
1596  if (array_key_exists($hookId, $hookIds) === FALSE) {
1597  throw new DALBakerException('Hook: '.$hookId.' does not exist');
1598  }
1599 
1600  // Setup an array to hold our sub-query data.
1601  $subData = array(
1602  'assertion_var' => '',
1603  'sub-query' => NULL,
1604  );
1605 
1606  // Get any assertions, along with the actual SQL query.
1607  $assertions = XML::getElementsByTagNames(self::$_assertionTypes, $subQuery);
1608  $select = $subQuery->getElementsByTagName('select')->item(0);
1609 
1610  if ($select === NULL) {
1611  $select = $subQuery->getElementsByTagName('hookQuery')->item(0);
1612  $select = XML::getFirstChildElement($select);
1613  }
1614 
1615  if (count($assertions) !== 0) {
1616  // Assertion present, so we only add it if the assertion var is
1617  // true.
1618  // Determine the assertion var to be used.
1619  $assertionVar = self::_getAssertionVariableName($assertions[0]);
1620  // Add the data to our subData array.
1621  $subData['assertion_var'] = $assertionVar;
1622  }
1623 
1624  // We need to add a temporary parent for our select, as convertToSql
1625  // expects the select to be the first child of the passed element.
1626  $tempDoc = new DomDocument();
1627  $newSub = "<query hookid=\"$origHookId\" system=\"$hookSys\">";
1628  $newSub .= $doc->saveXML($select).'</query>';
1629  $tempDoc->loadXML($newSub);
1630  $queryNode = $tempDoc->getElementsByTagName('query')->item(0);
1631  // Add the newly created node, containing the sub-query, into the
1632  // array.
1633  $subData['sub-query'] = $queryNode;
1634  // Add the data for this sub-query to the array for the whole query
1635  // here.
1636  $hookIds[$hookId]['sub-queries'][] = $subData;
1637  }//end foreach
1638 
1639  return $hookIds;
1640 
1641  }//end _generateHookArray()
1642 
1643 
1668  protected static function _printHookCode(array $hookIds)
1669  {
1670  // Print out the contents.
1671  $contents = '';
1672 
1673  // For each hook id that we have, we need to determine the parent type
1674  // and how to seperate each SQL fragment. We also need to add each of
1675  // the sub-queries to the array that will be imploded with this
1676  // seperator.
1677  foreach ($hookIds as $id => $hookData) {
1678  $codeId = self::getHookCodeId($id);
1679  $arrayId = self::getHookArrayId($id);
1680  $contents .= "\n// Setting up hook data for $id.\n\n";
1681  $contents .= "$arrayId = array();\n";
1682  foreach ($hookData['sub-queries'] as $subQuery) {
1683  // Store a query object for this sub query.
1684  $subQueryNode = $subQuery['sub-query'];
1685  $subQueryId = $subQueryNode->getAttribute('id');
1686  $subQuerySys = $subQueryNode->getAttribute('system');
1687  $subQueryHook = $subQueryNode->getAttribute('hookid');
1688  $subObjectId = 'subquery_'.$id.'_';
1689  $subObjectId .= md5($subQuerySys.$subQueryId.$subQueryHook);
1690  if ($subQuery['assertion_var'] !== '') {
1691  $assertionVar = $subQuery['assertion_var'];
1692  $contents .= "if ($assertionVar === TRUE) {\n ";
1693  }
1694 
1695  $sqlArray = self::constructSql($subQuery['sub-query']);
1696  $sql = self::convertToSql($subQuery['sub-query'], $sqlArray);
1697  self::_writeQueryObject($subObjectId, $sqlArray, $sql);
1698  $contents .= $arrayId."[] = DAL::getQueryObject('$subObjectId');\n";
1699 
1700  if ($subQuery['assertion_var'] !== '') {
1701  $contents .= "}\n";
1702  }
1703  }//end foreach
1704 
1705  // Implode the data and add seperators.
1706  $contents .= "\$query->subQueries('$id', $arrayId);\n";
1707 
1708  }//end foreach
1709 
1710  return $contents;
1711 
1712  }//end _printHookCode()
1713 
1714 
1725  public static function getHookCodeId($hookId)
1726  {
1727  return 'HOOKID:'.$hookId;
1728 
1729  }//end getHookCodeId()
1730 
1731 
1743  public static function getHookArrayId($hookId)
1744  {
1745  $str = ucwords(str_replace('_', ' ', $hookId));
1746  return '$'.str_replace(' ', '', $str).'Array';
1747 
1748  }//end getHookArrayId()
1749 
1750 
1759  public static function getSeperator($tagName)
1760  {
1761  // All tags will use a comma, unless specified in the switch below.
1762  $seperator = ', ';
1763 
1764  switch (strtolower($tagName)) {
1765  case 'union-all':
1766  $seperator = '\nUNION-ALL\n';
1767  break;
1768  case 'and':
1769  $seperator = '\nAND\n';
1770  break;
1771  case 'or':
1772  $seperator = '\nOR\n';
1773  break;
1774  default:
1775  }
1776 
1777  return $seperator;
1778 
1779  }//end getSeperator()
1780 
1781 
1795  protected static function _printKeywords($systemName, $queryName)
1796  {
1797  $doc = self::getQueryXML($systemName, $queryName);
1798  $keywords = $doc->getElementsByTagName('keyword');
1799  $processed = array();
1800  $arrayInit = FALSE;
1801  $suffix = '';
1802  $contents = '';
1803  foreach ($keywords as $keyword) {
1804  if (in_array($keyword->nodeValue, $processed) === FALSE) {
1805  if ($arrayInit === FALSE) {
1806  // Initialise array.
1807  $contents = "\$queryKeywordsArray = array();\n";
1808  $suffix = "\$query->keywords(\$queryKeywordArray);\n";
1809  $arrayInit = TRUE;
1810  }
1811 
1812  $processed[] = $keyword->nodeValue;
1813  $contents .= '$queryKeywordArray[\''.$keyword->nodeValue.'\'] = $data[\''.$keyword->nodeValue."'];\n";
1814  }
1815  }
1816 
1817  // If a keyword is printed.
1818  $contents .= $suffix;
1819 
1820  return $contents;
1821 
1822  }//end _printKeywords()
1823 
1824 
1844  protected static function _printBindings($systemName, $queryName, $printBindings=TRUE)
1845  {
1846  $doc = self::getQueryXML($systemName, $queryName);
1847  $contents = '';
1848 
1849  $bindingList = $doc->getElementsByTagName('binding');
1850  // Create an array containing our place holder names, so we can easily
1851  // find if a variable name has been generated by the system.
1852  $placeHolders = array();
1853  $placeHoldersList = $doc->getElementsByTagName('placeholder');
1854  foreach ($placeHoldersList as $element) {
1855  $varName = $element->getAttribute('var_name');
1856  $varValue = $element->getAttribute('value');
1857  $placeHolders[$varName] = $varValue;
1858  }
1859 
1860  $usedNames = array();
1861  foreach ($bindingList as $binding) {
1862  $bindName = $binding->getAttribute('name');
1863 
1864  // We need to determine what data type the column is.
1865  $columnType = $binding->getAttribute('column_type');
1866  if ($columnType !== '') {
1867  $dataType = self::_getPdoDataType($system, $table, $column, $columnType);
1868  } else {
1869  $column = $binding->getAttribute('column');
1870  $table = $binding->getAttribute('table');
1871  $system = $binding->getAttribute('system');
1872  $dataType = self::_getPdoDataType($system, $table, $column);
1873  }
1874 
1875  $varName = '';
1876  $index = substr($bindName, 1);
1877  // Check if the binding is a hard coded value. If it is, set its'
1878  // variable to be the same as defined in _printPlaceHoldersVariables
1879  // otherwise print an index into the $data array.
1880  if (isset($placeHolders[$bindName]) === TRUE) {
1881  // This is a place holder for a hard-coded value.
1882  $varName = '$'.substr($bindName, 1);
1883  } else {
1884  // This is not hard-coded, so should be referenced in the $data
1885  // array.
1886  $varName = "\$data['$index']";
1887  }
1888 
1889  if ($printBindings === TRUE) {
1890  // Add check for array values.
1891  $bindCall = '';
1892  if (isset($placeHolders[$bindName]) === FALSE) {
1893  $bindCall = "\nif (array_key_exists('$index',\$data) === TRUE) {\n";
1894  }
1895 
1896  $bindCall .= "\$query->bind('$bindName', $varName, $dataType);\n";
1897 
1898  if (isset($placeHolders[$bindName]) === FALSE) {
1899  $bindCall .= "}\n\n";
1900  }
1901 
1902  // Add this binding to the contents.
1903  $contents .= $bindCall;
1904  }
1905 
1906  }//end foreach
1907 
1908  $contents .= "return \$query;\n";
1909 
1910  return $contents;
1911 
1912  }//end _printBindings()
1913 
1914 
1938  protected static function _extractBindings(DomElement $fragment)
1939  {
1940  $doc = new DomDocument();
1941  $query = $doc->importNode($fragment, TRUE);
1942  // Add the bindings element to the the base query element.
1943  $bindingsParent = $doc->createElement('bindings');
1944  $query->appendChild($bindingsParent);
1945  $systemName = $fragment->getAttribute('system');
1946  // Get the XML contents, and determine if there are any bindings used.
1947  $xmlString = $doc->saveXML($query);
1948  $matches = array();
1949  preg_match_all('/:[a-zA-Z\d_]+/i', $xmlString, $matches);
1950  $bindings = $matches[0];
1951  $bindingsCount = count($bindings);
1952  if ($bindingsCount === 0) {
1953  // None present.
1954  return $query;
1955  }
1956 
1957  // There are some bindings needed in the query, we need to determine
1958  // where.
1959  $allElements = $query->getElementsByTagName('*');
1960  foreach ($allElements as $element) {
1961  if ($element->childNodes->length === 1) {
1962  // One child node, see if the nodeValue is a place-holder.
1963  $nodeValue = trim($element->nodeValue);
1964  $bindMatches = array();
1965  preg_match('/:[a-zA-Z\d_]+/i', $nodeValue, $bindMatches);
1966  if (empty($bindMatches) === FALSE) {
1967  // Check if this is really just a text node with a binding,
1968  // or it could still get here if this is a <value></value>
1969  // tag. If it is a value tag, we will get it on the next
1970  // iteration. Using the firstChild rather than nodeValue
1971  // is used here, as if nodeValue will fail this test always.
1972  $fullTagValue = $doc->saveXML($element->firstChild);
1973  if (substr(trim($fullTagValue), 0, 1) !== ':') {
1974  continue;
1975  }
1976 
1977  // Setup variables from the attributes, and create a new
1978  // binding element representing this data.
1979  $bindColumn = '';
1980  $bindColumnType = '';
1981  $bindTable = '';
1982  $bindSystem = $systemName;
1983  $bindName = $bindMatches[0];
1984 
1985  $targetNode = NULL;
1986  // If this node is a field or another element that doesn't
1987  // have a table specified, we need to determine where it
1988  // is targeted, so that we can set its' data type.
1989  if ($element->hasAttribute('table') === FALSE) {
1990  if ($element->parentNode->hasAttribute('table') === TRUE) {
1991  $targetNode = $element->parentNode;
1992  } else if ($element->tagName === 'value') {
1993  // Values only have column specified, so we need to
1994  // find the fields node, underneath our parent.
1995  $parent = $element->parentNode->parentNode;
1996  $fieldNode = $parent->getElementsByTagName('fields');
1997  if ($fieldNode->length !== 0) {
1998  $targetNode = $fieldNode->item(0);
1999  }
2000  }
2001  } else {
2002  // Element has its' table attribute specified.
2003  $targetNode = $element;
2004  }
2005 
2006  $bindColumnType = $element->getAttribute('column_type');
2007 
2008  if ($targetNode !== NULL) {
2009  $bindColumn = $targetNode->getAttribute('column');
2010  if ($bindColumn === '') {
2011  $bindColumn = $element->getAttribute('column');
2012  }
2013 
2014  $bindTable = $targetNode->getAttribute('table');
2015  }
2016 
2017  // Create the binding element, set its' attributes and
2018  // append it to the bindings tag of the fragment.
2019  $newBinding = $doc->createElement('binding');
2020  $newBinding->setAttribute('table', $bindTable);
2021  $newBinding->setAttribute('column', $bindColumn);
2022  $newBinding->setAttribute('column_type', $bindColumnType);
2023 
2024  $newBinding->setAttribute('system', $bindSystem);
2025  $newBinding->setAttribute('name', $bindName);
2026  $bindingsParent->appendChild($newBinding);
2027  }//end if $bindMatches not empty
2028  }//end if childNodes->length === 1
2029  }//end foreach $allElements
2030 
2031  return $query;
2032 
2033  }//end _extractBindings()
2034 
2035 
2049  protected static function _extractKeywords(DomElement $fragment)
2050  {
2051  $doc = new DomDocument();
2052  $query = $doc->importNode($fragment, TRUE);
2053 
2054  // Add the placeholder element to the the base query element.
2055  $keywordParent = $doc->createElement('keywords');
2056  $query->appendChild($keywordParent);
2057  $xmlString = $doc->saveXML($query);
2058  $matches = array();
2059 
2060  preg_match_all('/\[([a-zA-Z0-9]+)\]/m', $xmlString, $matches);
2061 
2062  if (isset($matches[1]) === TRUE && empty($matches[1]) === FALSE) {
2063  foreach ($matches[1] as $keyword) {
2064  $newKeyword = $doc->createElement('keyword');
2065  $newKeyword->nodeValue = $keyword;
2066  $keywordParent->appendChild($newKeyword);
2067  }
2068  }
2069 
2070  return $query;
2071 
2072  }//end _extractKeywords()
2073 
2074 
2092  protected static function _extractPlaceHolders(DomElement $fragment)
2093  {
2094  $doc = new DomDocument();
2095  $query = $doc->importNode($fragment, TRUE);
2096  // Add the placeholder element to the the base query element.
2097  $placeParent = $doc->createElement('placeholders');
2098  $query->appendChild($placeParent);
2099  $systemName = $fragment->getAttribute('system');
2100  $queryId = $fragment->getAttribute('id');
2101 
2102  $allElements = $query->getElementsByTagName('value');
2103  foreach ($allElements as $element) {
2104  if ($element->childNodes->length === 1) {
2105  // One child node, see if the nodeValue is a place-holder.
2106  $nodeValue = trim($element->nodeValue);
2107  if ($nodeValue === '') {
2108  continue;
2109  }
2110 
2111  if ($element->tagName === 'table') {
2112  continue;
2113  }
2114 
2115  if (substr($nodeValue, 0, 1) === ':') {
2116  // This is already a binding.
2117  continue;
2118  }
2119 
2120  if (substr($nodeValue, 0, 1) === '[') {
2121  // This is a keyword.
2122  continue;
2123  }
2124 
2125  // Setup variables from the attributes, and create a new
2126  // binding element representing this data.
2127  $placeColumn = '';
2128  $placeTable = '';
2129  $targetNode = NULL;
2130  // If this node is a field or another element that doesn't
2131  // have a table specified, we need to determine where it
2132  // is targeted, so that we can set its' data type.
2133  if ($element->hasAttribute('table') === FALSE) {
2134  if ($element->parentNode->hasAttribute('table') === TRUE) {
2135  $targetNode = $element->parentNode;
2136  } else if ($element->tagName === 'value') {
2137  // Values only have column specified, so we need to
2138  // find the fields node, underneath our parent.
2139  $parent = $element->parentNode->parentNode;
2140  $fieldNode = $parent->getElementsByTagName('fields');
2141  if ($fieldNode->length !== 0) {
2142  $targetNode = $fieldNode->item(0);
2143  }
2144  } else if ($element->tagName === 'table') {
2145  $placeTable = $nodeValue;
2146  $targetNode = $element;
2147  }
2148  } else {
2149  // Element has its' table attribute specified.
2150  $targetNode = $element;
2151  }
2152 
2153  if ($targetNode === NULL) {
2154  continue;
2155  }
2156 
2157  if ($placeColumn === '') {
2158  if ($targetNode->hasAttribute('column') === TRUE) {
2159  $placeColumn = $targetNode->getAttribute('column');
2160  } else {
2161  // Column not specified, use the query name instead.
2162  $placeColumn = $queryId;
2163  }
2164  }
2165 
2166  if ($placeTable === '') {
2167  if ($targetNode->hasAttribute('table') === TRUE) {
2168  $placeTable = $targetNode->getAttribute('table');
2169  } else {
2170  // Table not specified, use the system name instead.
2171  $placeTable = $systemName;
2172  }
2173  }
2174 
2175  // Create the binding element, set its' attributes and
2176  // append it to the bindings tag of the fragment.
2177  $newPlaceHolder = $doc->createElement('placeholder');
2178  $varName = $placeTable.' '.$placeColumn;
2179  $varName = ucwords(str_replace('_', ' ', $varName));
2180  $varName = ':'.str_replace(' ', '', $varName);
2181  $newPlaceHolder->setAttribute('value', $element->nodeValue);
2182  $newPlaceHolder->setAttribute('var_name', $varName);
2183  $placeParent->appendChild($newPlaceHolder);
2184  // Now replace the original node with the new var name.
2185  $element->nodeValue = $varName;
2186  }//end if childNodes->length === 1
2187  }//end foreach $allElements
2188 
2189  return $query;
2190 
2191  }//end _extractPlaceHolders()
2192 
2193 
2208  protected static function _getPdoDataType($systemId, $table, $column, $dataType=NULL)
2209  {
2210  if ($dataType === NULL) {
2211  $dataType = self::getTableColumnTypes($systemId, $table, $column);
2212  }
2213 
2214  switch (strtoupper($dataType)) {
2215  case 'NUMERIC':
2216  case 'DECIMAL':
2217  case 'INTEGER':
2218  case 'SMALLINT':
2219  case 'REAL':
2220  case 'DOUBLE PRECISION':
2221  case 'FLOAT':
2222  $pdoType = 'PDO::PARAM_INT';
2223  break;
2224  case 'BOOLEAN':
2225  $pdoType = 'PDO::PARAM_BOOL';
2226  break;
2227  default:
2228  $pdoType = 'PDO::PARAM_STR';
2229  }
2230 
2231  return $pdoType;
2232 
2233  }//end _getPdoDataType()
2234 
2235 
2244  public static function parseSchema(DomElement $schema)
2245  {
2246  require_once 'DAL/Parsers/DALSchemaParser.inc';
2247 
2248  return DALSchemaParser::parse($schema);
2249 
2250  }//end parseSchema()
2251 
2252 
2253  /*
2254  VALIDATE SQL QUERY
2255  */
2256 
2257 
2270  public static function validateQueries(DomElement $queries)
2271  {
2272  // Queries tag must have system attribute.
2273  if ($queries->getAttribute('system') === '') {
2274  throw new DALParserException('queries tag must have system attr.');
2275  }
2276 
2277  // Check that each query in this sytem have unique id.
2278  $ids = array();
2279  $qs = $queries->getElementsByTagName('query');
2280  foreach ($qs as $query) {
2281  $id = $query->getAttribute('id');
2282  if (in_array($id, $ids) === TRUE) {
2283  throw new DALParserException('Query must have a unique id.');
2284  }
2285 
2286  $ids[] = $id;
2287  }
2288 
2289  }//end validateQueries()
2290 
2291 
2301  public static function queryIdIsUnique(DomDocument $doc, DomElement $query)
2302  {
2303  $xp = new DOMXPath($doc);
2304  $qry = '//query[@id = "'.$query->getAttribute('id').'"]';
2305  $nodes = $xp->query($qry);
2306 
2307  foreach ($nodes as $node) {
2308  if ($node !== $query) {
2309  return FALSE;
2310  }
2311  }
2312 
2313  return TRUE;
2314 
2315  }//end queryIdIsUnique()
2316 
2317 
2331  public static function validateQuery(DomElement $query)
2332  {
2333  $doc = $query->ownerDocument;
2334  $queryId = $query->getAttribute('id');
2335  // Query must have id attribute.
2336  if ($queryId === '') {
2337  throw new DALParserException('Query must have id attribute.');
2338  } else if (self::queryIdIsUnique($doc, $query) === FALSE) {
2339  $msg = 'Query must have a unique id. Id "'.$queryId;
2340  $msg .= '" already used by another query.';
2341  throw new DALParserException($msg);
2342  }
2343 
2344  $type = self::getQueryType($query);
2345  $fn = 'validate'.ucwords($type).'Query';
2346  if (method_exists('DALBaker', $fn) === FALSE) {
2347  throw new DALParserException('Invalid validate query. Type:'.$type);
2348  } else {
2349  self::$fn($query);
2350  }
2351 
2352  }//end validateQuery()
2353 
2354 
2363  public static function getQueryType(DomElement $query)
2364  {
2365  foreach ($query->childNodes as $child) {
2366  if ($child->nodeType !== XML_ELEMENT_NODE) {
2367  continue;
2368  }
2369 
2370  return $child->tagName;
2371  }
2372 
2373  }//end getQueryType()
2374 
2375 
2386  public static function fragmentExists($system, $queryid, $hookid)
2387  {
2388  // Get hooks XML.
2389  list($hsysid, $hqid) = explode('.', $hookid);
2390 
2391  $doc = self::loadBakedQuery($hsysid, $hqid);
2392  if ($doc === NULL) {
2393  return FALSE;
2394  }
2395 
2396  $queries = $doc->getElementsByTagName('query');
2397 
2398  foreach ($queries as $query) {
2399  if ($query->getAttribute('id') === $queryid) {
2400  if ($query->getAttribute('system') === $system) {
2401  return TRUE;
2402  }
2403  }
2404  }
2405 
2406  return FALSE;
2407 
2408  }//end fragmentExists()
2409 
2410 
2421  public static function convertToSql(DomElement $xmlQuery, array $sqlArray=array())
2422  {
2423  if (empty($sqlArray) === TRUE) {
2424  $sqlArray = self::constructSql($xmlQuery);
2425  }
2426 
2427  $converter = self::getConverter();
2428  return $converter->convertToSql($sqlArray['query']);
2429 
2430  }//end convertToSql()
2431 
2432 
2443  public static function getConverter($dbType=NULL)
2444  {
2445  if ($dbType === NULL) {
2446  $dbType = ucfirst(self::getDatabaseType());
2447  }
2448 
2449  // If there is no database type returned, the processing will still be
2450  // able to proceed, as the base type: "DALConverter" will be used.
2451  $converterClass = 'DAL'.$dbType.'Converter';
2452  require_once 'Converters/'.$converterClass.'.inc';
2453  // Here we will get the current DB type and use its converter class.
2454  $converter = eval("return new DAL$dbType".'Converter();');
2455  return $converter;
2456 
2457  }//end getConverter()
2458 
2459 
2474  public static function getTableColumnTypes($systemid, $table, $column=NULL)
2475  {
2476  if ($column === NULL) {
2477  $types = array();
2478  } else {
2479  $column = strtolower($column);
2480  $types = NULL;
2481  }
2482 
2483  $doc = self::loadSystemSchemaXML($systemid);
2484  if ($doc === NULL) {
2485  return NULL;
2486  }
2487 
2488  // Find the table that we are looking for.
2489  $tables = $doc->getElementsByTagName('table');
2490  foreach ($tables as $currTable) {
2491  if ($currTable->getAttribute('name') === $table) {
2492  // Get table columns tag.
2493  $colsTag = $currTable->getElementsByTagName('columns')->item(0);
2494  $cols = $colsTag->getElementsByTagName('column');
2495  foreach ($cols as $col) {
2496  if ($column === strtolower($col->nodeValue)) {
2497  return $col->getAttribute('type');
2498  } else if ($column === NULL) {
2499  $types[strtolower($col->nodeValue)] = $col->getAttribute('type');
2500  }
2501  }
2502 
2503  return $types;
2504  }
2505  }//end foreach
2506 
2507  return NULL;
2508 
2509  }//end getTableColumnTypes()
2510 
2511 
2524  public static function getQuerySource($systemid, $queryid)
2525  {
2526  $queriesXML = self::loadQueriesXML($systemid);
2527 
2528  if ($queriesXML !== NULL) {
2529  // Get all query tags and check their id attr.
2530  $queries = $queriesXML->getElementsByTagName('query');
2531  foreach ($queries as $query) {
2532  if ($query->getAttribute('id') === $queryid) {
2533  return $query;
2534  }
2535  }
2536  }
2537 
2538  return NULL;
2539 
2540  }//end getQuerySource()
2541 
2542 
2555  public static function createSystemOvenPath($system)
2556  {
2557  $ovenPath = DAL::getOvenPath($system);
2558  if (file_exists($ovenPath) === TRUE) {
2559  return FALSE;
2560  } else {
2561  if (mkdir($ovenPath, 0775, TRUE) === FALSE) {
2562  $msg = 'Could not create directory: '.$ovenPath;
2563  throw new ChannelException($msg);
2564  }
2565  }
2566 
2567  return TRUE;
2568 
2569  }//end createSystemOvenPath()
2570 
2571 
2582  public static function loadQueriesXML($systemid)
2583  {
2584  $fileName = Channels::getSystemsPath($systemid).'/DB/queries.xml';
2585  if (file_exists($fileName) === FALSE) {
2586  return NULL;
2587  }
2588 
2589  $doc = new DOMDocument();
2590  $doc->load($fileName);
2591 
2592  return $doc;
2593 
2594  }//end loadQueriesXML()
2595 
2596 
2606  public static function getBakedQueryFileName($systemid, $queryid)
2607  {
2608  $fileName = DAL::getOvenPath($systemid).'/'.$queryid.'.xml';
2609  return $fileName;
2610 
2611  }//end getBakedQueryFileName()
2612 
2613 
2623  public static function loadBakedQuery($systemid, $queryid)
2624  {
2625  $fileName = self::getBakedQueryFileName($systemid, $queryid);
2626  if (file_exists($fileName) === FALSE) {
2627  return NULL;
2628  }
2629 
2630  $doc = new DOMDocument();
2631  $doc->load($fileName);
2632 
2633  return $doc;
2634 
2635  }//end loadBakedQuery()
2636 
2637 
2648  public static function loadSystemSchemaXML($systemid)
2649  {
2650  $fileName = Channels::getSystemsPath($systemid).'/DB/schema.xml';
2651  if (file_exists($fileName) === FALSE) {
2652  return NULL;
2653  }
2654 
2655  $doc = new DOMDocument();
2656  $doc->load($fileName);
2657 
2658  return $doc;
2659 
2660  }//end loadSystemSchemaXML()
2661 
2662 
2663  /*
2664  XQD to PHP Query Conversions
2665  */
2666 
2667 
2679  public static function constructSql(DomElement $query)
2680  {
2681  $sql = array();
2682 
2683  // Require parser..
2684  require_once dirname(__FILE__).'/Parsers/DALQueryParser.inc';
2685 
2686  // Parse.
2687  $sql = DALQueryParser::parse($query);
2688 
2689  return $sql;
2690 
2691  }//end constructSql()
2692 
2693 
2703  public static function getHookPrefix()
2704  {
2705  $prefix = 'HOOKID:';
2706  return $prefix;
2707 
2708  }//end getHookPrefix()
2709 
2710 
2719  public static function getComparisonOperators($type=NULL)
2720  {
2721  if ($type !== NULL) {
2722  if (isset(self::$_whereConditions[$type]) === TRUE) {
2723  return self::$_whereConditions[$type];
2724  } else {
2725  return '';
2726  }
2727  }
2728 
2729  return self::$_whereConditions;
2730 
2731  }//end getComparisonOperators()
2732 
2733 
2743  public static function getDatabaseType($dsn=NULL)
2744  {
2745  if ($dsn === NULL) {
2746  $dbType = DAL::getDbType();
2747  } else {
2748  $dbType = $dsn['type'];
2749  }
2750 
2751  switch ($dbType) {
2752  case 'pgsql':
2753  $fullType = 'postgres';
2754  break;
2755  case 'oci':
2756  $fullType = 'oracle';
2757  break;
2758  case 'odbc':
2759  $fullType = 'db2';
2760  break;
2761  case 'dblib':
2762  $fullType = 'mssql';
2763  break;
2764  case 'mysql':
2765  $fullType = $dbType;
2766  break;
2767  default:
2768  $fullType = '';
2769  }
2770 
2771  return $fullType;
2772 
2773  }//end getDatabaseType()
2774 
2775 
2776 }//end class
2777 
2778 ?>