Framework  3.9
grouped_data_view.inc
Go to the documentation of this file.
1 <?php
5 /**************************************************************
6 
7  Copyright (c) 2007-2010 Sonjara, Inc
8 
9  Permission is hereby granted, free of charge, to any person
10  obtaining a copy of this software and associated documentation
11  files (the "Software"), to deal in the Software without
12  restriction, including without limitation the rights to use,
13  copy, modify, merge, publish, distribute, sublicense, and/or sell
14  copies of the Software, and to permit persons to whom the
15  Software is furnished to do so, subject to the following
16  conditions:
17 
18  The above copyright notice and this permission notice shall be
19  included in all copies or substantial portions of the Software.
20 
21  Except as contained in this notice, the name(s) of the above
22  copyright holders shall not be used in advertising or otherwise
23  to promote the sale, use or other dealings in this Software
24  without prior written authorization.
25 
26  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
28  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
30  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
31  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
32  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
33  OTHER DEALINGS IN THE SOFTWARE.
34 
35 *****************************************************************/
36 
37 require_once realpath(dirname(__FILE__))."/data_column.inc";
38 require_once realpath(dirname(__FILE__))."/join.inc";
39 require_once realpath(dirname(__FILE__))."/excel.inc";
40 
45 class DataGroup
46 {
47  var $parent;
48  var $title;
49  var $key;
50  var $expanded;
51  var $fixed;
52 
61  function DataGroup($parent, $title, $key, $expanded = false, $fixed = false)
62  {
63  $this->parent = $parent;
64  $this->title = $title;
65  $this->key = $key;
66  $this->expanded = $expanded;
67  }
68 
72  function draw()
73  {
74  $cssClass = ($this->fixed || $this->parent->mode == 'fixed') ? "fixed" : ($this->expanded) ? "expanded" : "collapsed";
75  $select = ($this->parent->selectable && $this->parent->showSelectAll) ? "<a href='#' class='button' style='float: right' onclick='new DOMEvent(event).stop(); GroupingTable.toggleGroup(this, \"{$this->parent->groupClass}\"); return false;'>Select All</a>" : "";
76 ?>
77  <tr class="<?echo $this->parent->groupClass?> <?echo $cssClass?>">
78  <td colspan="<?echo $this->parent->columnCount?>"><?echo $select?><?echo $this->title?></td>
79  </tr>
80 <?
81  }
82 }
83 
101 {
102  var $columns;
104  var $cssClass;
106  var $id;
107  var $cssStyle;
111  var $mode;
112  var $groups;
113  var $items;
115  var $summary;
116  var $selectable = false;
117  var $showSelectAll = true;
118  var $rowId = false; // whether to include an id tag for each tr row
119  var $rowIdFormat = null;
120  var $groupFooterColumns = array(); // subtotal footer for group
121  var $totalCallbacks = array(); // the onStartRow callbacks for totalling
122  var $groupAsWorksheets = false;
123  var $menu = null;
124  var $commandMenuIcon = "/fakoli/images/data_view_menu.png";
125  var $hideExcelIcon = false;
126  var $tagRowCallbacks = array();
127  // Scrolling Table parameters
128 
129  var $height;
130  var $scrollable = false;
131  var $resizable = true;
132 
143  function GroupedDataListView($items, $id, $cssClass = "list", $groupClass = "subheading")
144  {
145  $this->items = $items;
146  $this->id = $id;
147  $this->cssClass = $cssClass;
148  $this->groupClass = $groupClass;
149  $this->cssStyle = null;
150  $this->emptyMessage = "No matching items.";
151  $this->onStartRow = null;
152  $this->onDrawFooter = false;
153  $this->mode = "fixed";
154  $this->excelFile = null;
155  }
156 
157  function getID()
158  {
159  return $this->id;
160  }
161 
166  function addFacetTaggingHandler($handler)
167  {
168  $this->tagRowCallbacks[] = $handler;
169  }
170 
178  function group($title, $key, $expanded = false, $fixed = false)
179  {
180  $this->groups[] = new DataGroup($this, $title, $key, $expanded);
181  return $this;
182  }
183 
192  function groupBy($groups, $format = null, $field = "", $expandedFn = null, $fixedFn = null)
193  {
194  if (count($groups) == 0) return;
195  if (!$field) $field = $groups[0]->primary_key;
196 
197  foreach($groups as $group)
198  {
199  $expanded = ($expandedFn) ? $expandedFn($group) : false;
200  $fixed = ($fixedFn) ? $fixedFn($group) : false;
201 
202  $this->group($this->format($group, $format), $group->$field, $expanded, $fixed);
203  }
204  }
205 
206  function autoGroup()
207  {
208  $categories = array_keys($this->items);
209  foreach($categories as $category)
210  {
211  $this->group($category, $category);
212  }
213  }
214 
223  function format($group, $template)
224  {
225  if(!is_callable($template))
226  return $group->format($template);
227  else
228  return call_user_func($template, $group);
229  }
230 
231  function scrollable($height = 300, $resizable = true)
232  {
233  $this->scrollable = true;
234  $this->height = $height;
235  $this->resizable = $resizable;
236  }
237 
250  function column($title, $format, $sortable = false, $style = null, $typeHint = null, $onExport = null, $sortFormat = null)
251  {
252  $this->columns[] = new DataColumn($title, $format, $sortable, $style, $typeHint, $onExport, $sortFormat);
253  return $this;
254  }
255 
270  function exportColumn($title, $format, $sortable = false, $style = null, $typeHint = null, $onExport = null, $sortFormat = null)
271  {
272  $column = new DataColumn($title, $format, $sortable, $style, $typeHint, $onExport, $sortFormat);
273  $column->exportOnly = true;
274  $this->columns[] = $column;
275  return $this;
276  }
277 
283  function selector($format = "")
284  {
285  if(count($this->items))
286  {
287  foreach($this->items as $groupKey => $items)
288  {
289  $pk = $items[0]->getPrimaryKey();
290  break;
291  }
292  }
293 
294  if (!$format && count($items))
295  $format = "<input type='checkbox' class='checkbox' name='{$pk}[]' id='{$pk}_{{$pk}}' value='{{$pk}}'/>";
296 
297  if($format == "radio" && count($items))
298  $format = "<input type='radio' class='radio' name='{$pk}[]' id='{$pk}_{{$pk}}' value='{{$pk}}'/>";
299 
300  $this->column("&nbsp;", $format, false, "text-align: center");
301  $this->selectable = true;
302  return $this;
303  }
304 
305  function formatRowId($item)
306  {
307  if ($this->rowIdFormat)
308  {
309  return $item->format($this->rowIdFormat);
310  }
311 
312  $pk = $item->getPrimaryKey();
313  return "id='{$pk}_" . $item->$pk . "'";
314  }
315 
323  function footerText($text = "", $style = "", $colspan = 1)
324  {
325  $this->footerColumns[] = new FooterTextColumn($text, $style, $colspan);
326  return $this;
327  }
328 
336  function footerValue($callback, $style = "", $colspan = 1)
337  {
338  $this->footerColumns[] = new FooterValueColumn($callback, $style, $colspan);
339  return $this;
340  }
341 
349  function footerTotal($field, $style = "text-align: right", $colspan = 1)
350  {
351  $column = new FooterTotalColumn($field, $template = "", $style, $colspan);
352  $this->footerColumns[] = $column;
353  $this->totalCallbacks[] = array($column, onStartRow);
354 
355  return $this;
356  }
357 
365  function groupFooterText($text = "", $style = "", $colspan = 1)
366  {
367  $this->groupFooterColumns[] = new FooterTextColumn($text, $style, $colspan);
368  return $this;
369  }
370 
371 
372  function groupFooterTotal($field, $style = "text-align: right", $colspan = 1)
373  {
374  $column = new FooterTotalColumn($field, $template = "", $style, $colspan);
375  $this->groupFooterColumns[] = $column;
376  $this->totalCallbacks[] = array($column, onStartRow);
377 
378  return $this;
379  }
380 
381 
386  function setID($id = null)
387  {
388  if ($this->id) return;
389  if ($id)
390  {
391  $this->id = $id;
392  return;
393  }
394 
395  $this->id = makeRandomString(6);
396  }
397 
403  function autoPopulate($item)
404  {
405  if (count($this->columns) > 0) return;
406 
407  $fields = $item->getFieldArray();
408  foreach($fields as $field)
409  {
410  if ($item->primary_key == $field) continue;
411 
412  $this->column($item->prettifyFieldName($field), "{".$field."}", $true);
413  }
414  }
415 
421  function commandMenu()
422  {
423  $this->menu = new ContextMenu($this->id . "_menu", "#{$this->id}_menu_button");
424  $this->menu->trigger = "click";
425  $this->menu->position = "element";
426 
427  return $this->menu;
428  }
429 
433  function getExcelLink()
434  {
435  $qs = appendToQueryString(getFullQueryString(), "__excel={$this->id}");
436  return baseURI().$qs;
437  }
438 
443  function writeScript()
444  {
445  $this->setID(); // Set to a default ID if none was specified.
446 
447  if ($this->excelFile && $_REQUEST["__excel"] == $this->id)
448  {
449  $this->writeExcelFile($this->excelFile);
450  }
451 
452  if (count($this->items) == 0) return;
453 
454  $script = "<script type='text/javascript' src='/fakoli/js/grouping_table.js'></script>\n";
455 
456  $constructor = "";
457 
458  $constructor = "\n\t\tvar {$this->id} = new GroupingTable('{$this->id}', {mode: '{$this->mode}'});";
459 
460  if ($this->selectable)
461  {
462  $constructor .= "\n\t\tdocument.id('{$this->id}').getSelectedValues = function() { var values = []; this.getElements(\"input[type='checkbox']\").each(function(e) { if (e.checked) values.push(e.value); }); return values; };";
463  }
464 
465  if ($this->scrollable)
466  {
467  $constructor .= "\n\t\tnew ScrollingTable('{$this->id}');";
468  }
469 
470  if ($constructor)
471  {
472  $script .= "\t<script type='text/javascript'>\n\twindow.addEvent('domready', function()\n\t{";
473  $script .= $constructor;
474  $script .= "\n\t});\n\t</script>\n";
475  }
476 
477  if ($this->menu) $script .= $this->menu->writeScript();
478 
479  return $script;
480  }
481 
485  function drawView()
486  {
487  if (count($this->items) > 0)
488  {
489  if (count($this->groups) == 0)
490  {
491  $this->autoGroup();
492  }
493 
494  $this->autoPopulate($this->items[0]); // Autopopulate the column list from the first object, if not specified
495  $attrs = "";
496  if ($this->id)
497  {
498  $attrs .= " id='{$this->id}'";
499  }
500 
501  if ($this->cssClass)
502  {
503  $attrs .= " class='{$this->cssClass}";
504  if ($this->sortable) $attrs .=" sortable";
505  if ($this->scrollable) $attrs.= " scroll";
506  $attrs .= "'";
507  }
508 
509  if ($this->cssStyle)
510  {
511  $attrs .= " style='{$this->cssStyle}'";
512  }
513 
514  if ($this->summary)
515  {
516  $attrs .= " summary='".htmlSafe($this->summary)."'";
517  }
518 
519  $this->columnCount = count($this->columns);
520 
521  if ($this->scrollable)
522  {
523  $tbodyAttrs = " style='height: ".($this->height - 24)."px'";
524  echo "<div class='scrollContainer' style='height: {$this->height}px'>";
525  }
526 
527 ?>
528  <table<?echo $attrs?>>
529  <thead>
530  <tr>
531 <?
532  $first = true;
533 
534  foreach($this->columns as $column)
535  {
536  if ($column->exportOnly) continue;
537 
538  $attrs = "";
539  $cssClass = "nosort";
540  if ($column->style)
541  {
542  if (preg_match("/^[\\w_]*$/", $column->style))
543  {
544  $cssClass = $column->style;
545  }
546  else
547  {
548  $attrs .= " style='{$column->style}'";
549  }
550  }
551 
552  if ($cssClass)
553  {
554  $attrs .= " class='$cssClass'";
555  }
556 ?>
557  <th<?echo $attrs?>>
558 <?
559  if ($first)
560  {
561  if ($this->excelFile)
562  {
563  $qs = $this->getExcelLink();
564  }
565 
566  if ($this->excelFile && !$this->menu && !$this->hideExcelIcon)
567  {
568 ?>
569  <a href='<?echo $qs?>' style="float: left"><img src="/fakoli/images/msexcel_icon_small.png" alt="Excel" style="border: none; display:inline-block;vertical-align: middle"/></a>
570 <?
571  }
572  else if ($this->menu)
573  {
574  if ($this->excelFile)
575  {
576  $this->menu->command($this->id."_excel", "Save as Excel...", $qs, true, "/fakoli/images/msexcel_icon_small.png");
577  }
578 ?>
579  <img id="<?echo $this->id?>_menu_button" src="<?echo $this->commandMenuIcon?>" alt="Table Menu" style="border: none; display:inline-block;vertical-align: middle"/>
580 <?
581  }
582 
583  $first = false;
584  }
585 ?>
586 <?echo $column->title?></th>
587 <?
588  }
589 ?>
590  </tr>
591  </thead>
592  <tbody<?php echo $tbodyAttrs?>>
593 <?
594  foreach($this->groups as $group)
595  {
596  if (array_key_exists($group->key, $this->items))
597  {
598  $group->draw();
599 
600  foreach($this->items[$group->key] as $item)
601  {
602  $cl = "";
603 
604  if($this->rowId)
605  $rowId = $this->formatRowId($item);
606 
607  if ($this->onStartRow)
608  {
609  $retval = call_user_func($this->onStartRow, $item);
610  if ($retval === false) continue; // return false to indicate row should be skipped
611  if ($retval) $cl = $retval; // return a string to indicate a css class for the row
612  }
613 
614  $dataAttrs = array();
615  foreach($this->tagRowCallbacks as $cb)
616  {
617  $dataAttrs = call_user_func($cb, $item, $dataAttrs);
618  }
619 
620  $rowAttrs = "";
621  foreach($dataAttrs as $name => $value)
622  {
623  $rowAttrs .= " ".$name."='".$value."'";
624  }
625 
626  foreach($this->totalCallbacks as $cb)
627  {
628  call_user_func($cb, $item);
629  }
630 
631  if ($cl) $cl = "class='$cl' ";
632 ?>
633  <tr<?echo $rowAttrs?> <?php echo $cl.$rowId ?>>
634 <?
635  foreach($this->columns as $column)
636  {
637  if ($column->exportOnly) continue;
638 
639  $attrs = "";
640  if ($column->style)
641  {
642  $attrs .= " style='{$column->style}'";
643  }
644 ?>
645  <td<?echo $attrs?>><?echo $column->format($item)?></td>
646 <?
647  }
648 ?>
649  </tr>
650 <?
651  } // end foreach group items as items
652  $this->drawSubFooterColumns();
653  } // end if group key is in items array
654  } // end for each groups as group
655 ?>
656  </tbody>
657 <?
658  if ($this->onDrawFooter) call_user_func($this->onDrawFooter);
659 
660  if (count($this->footerColumns) > 0)
661  {
662 ?>
663  <tfoot>
664  <tr>
665 <?
666  foreach($this->footerColumns as $column)
667  {
668  $attrs = $this->getFooterColumnFormatting($column);
669  ?>
670  <td<?echo $attrs?>><?echo $column->format()?></td>
671 <?
672  }
673 ?>
674  </tr>
675  </tfoot>
676 <?
677  }
678 ?>
679  </table>
680 <?
681  if ($this->scrollable) echo "</div>\n";
682  if ($this->menu) echo $this->menu->writeMenu();
683  }
684  else
685  {
686  echo "<p><em>{$this->emptyMessage}</em></p>";
687  }
688  }
689 
694  function drawViewToString()
695  {
696  ob_start();
697  $this->drawView();
698  $out = ob_get_contents();
699  ob_end_clean();
700  return $out;
701  }
702 
703 
704  function getFooterColumnFormatting($column)
705  {
706  $attrs = "";
707  $cssClass = "";
708  if ($column->style)
709  {
710  if (preg_match("/^[\\w_]*$/", $column->style))
711  {
712  $attrs .= " class='{$column->style}'";
713  }
714  else
715  {
716  $attrs .= " style='{$column->style}'";
717  }
718  }
719 
720  if ($column->colspan != 1)
721  {
722  $attrs .= " colspan='{$column->colspan}'";
723  }
724 
725  return $attrs;
726  }
727 
729  {
730  if (count($this->groupFooterColumns) == 0)
731  return;
732 ?>
733  <tr>
734 <?
735  foreach($this->groupFooterColumns as $column)
736  {
737  $attrs = $this->getFooterColumnFormatting($column);
738 ?>
739  <td<?echo $attrs?>><?echo $column->format()?></td>
740 <?
741  // reset to 0 before start of next group
742  $column->total = 0;
743  }
744 ?>
745  </tr>
746 <?
747  }
748 
756  function writeExcelFile($file)
757  {
758  $x = ExcelFileWriter::create($file);
759 
760  if (count($this->groups) == 0)
761  {
762  $this->autoGroup();
763  }
764 
765  $writeColumnHeadings = true;
766  $firstGroup = true;
767 
768  foreach($this->groups as $group)
769  {
770  if ($this->groupAsWorksheets)
771  {
772  //AJG: Excel can't cope with slashes in worksheet names
773  $title = str_replace("/", " ", $group->title);
774 
775  if ($firstGroup)
776  {
777  $x->setWorksheetTitle($title);
778  }
779  else
780  {
781  $x->addWorksheet($title);
782  }
783 
784  $writeColumnHeadings = true;
785  $firstGroup = false;
786  }
787 
788  $c = 0;
789 
790  if ($writeColumnHeadings)
791  {
792  foreach($this->columns as $column)
793  {
794  $x->writeHeading(0, $c, $column->title);
795  ++$c;
796  }
797 
798  $r = 1;
799  $writeColumnHeadings = false;
800  }
801 
802  if (array_key_exists($group->key, $this->items))
803  {
804  if (!$this->groupAsWorksheets)
805  {
806  $x->writeSubheading($r, 0, stripHTML($group->title));
807  $r++;
808  }
809 
810  foreach($this->items[$group->key] as $item)
811  {
812  $c = 0;
813  if ($this->onStartRow)
814  {
815  call_user_func($this->onStartRow, $item);
816  }
817 
818  foreach($this->columns as $column)
819  {
820 
821  if (is_callable($column->onExport))
822  {
823  $val = call_user_func($column->onExport, $item, $column);
824  }
825  else if ($column->onExport)
826  {
827  $val = $item->format($column->onExport);
828  }
829  else
830  {
831  $val = $column->format($item);
832  }
833 
834  switch($column->typeHint)
835  {
836  case String:
837 
838  $x->writeText($r, $c, stripHTMLTags(stripHTML($val)));
839  break;
840 
841  case Number:
842 
843  $x->writeNumber($r, $c, $val);
844  break;
845 
846  case Currency:
847 
848  $val = str_replace(array("$", ","), array("", ""), $val);
849  $x->writeCurrency($r, $c, $val);
850  break;
851 
852  case Percentage:
853 
854  $x->writePercentage($r, $c, $val);
855  break;
856 
857  default:
858 
859  /* JDG 5/11/10 currency export fix */
860  if (preg_match("/^[\\d\\,\\.\\$]+$/", $val))
861  {
862  $val = str_replace(array("$", ","), array("", ""), $val);
863  $x->writeNumber($r, $c, $val);
864  }
865  else
866  {
867  // No HTML required in Excel output
868  $x->writeText($r, $c, stripHTML($val));
869  }
870  }
871 
872  ++$c;
873  }
874  ++$r;
875  }
876  }
877  }
878 
879  $c = 0;
880 
881  if ($this->footerColumns)
882  {
883  foreach($this->footerColumns as $column)
884  {
885  $val = $column->format();
886  if (preg_match("/^[\\d\\.]+$/", $val))
887  {
888  $x->writeFooterNumber($r, $c, $val);
889  }
890  else
891  {
892  // No HTML required in Excel output
893  $x->writeFooter($r, $c, stripHTML($val));
894  }
895  $c += $column->colspan;
896  }
897  }
898 
899  $x->send();
900  die();
901  }
902 }?>
Represents a column in a DataListView output table.
Definition: data_column.inc:39
Represents a grouped collection of data within a table.
$fixed
Specifies whether this group should be fixed (i.e. exempt from expand/collapse toggling).
$key
The key value for this data group.
DataGroup($parent, $title, $key, $expanded=false, $fixed=false)
Creates a new DataGroup.
$title
The text to display in the title for this data group.
draw()
Draws the data group subheading within the table.
$expanded
Boolean indicating whether this group should initially be expanded.
$parent
The parent GroupedDataListView object.
static create($filename)
Definition: excel.inc:48
GroupedDataListView displays a list of DataItems (or InnerJoinResults) in tabular format grouped by a...
addFacetTaggingHandler($handler)
Adds a row tagging handler.
$onStartRow
Callback hook that gets called at the start of each row.
scrollable($height=300, $resizable=true)
$cssStyle
Optional additional inline styles to apply to the table.
$items
The items to be displayed.
$footerColumns
The footer column definitions for the table.
$onDrawFooter
Callback hook that gets called after all rows have been drawn.
$rowIdFormat
Optionally specify a custom row ID format.
$showSelectAll
Show Select All buttons for selectable tables.
groupBy($groups, $format=null, $field="", $expandedFn=null, $fixedFn=null)
Add groups based on the supplied list of DataItems.
setID($id=null)
Sets the client-side ID for this DataListView.
selector($format="")
Adds a selector column to the DataListView.
$summary
Human-readable summary of the information contained in the table (for usability/accessiblity purposes...
$hideExcelIcon
Suppress the automatic Excel icon if you want an external link.
groupFooterText($text="", $style="", $colspan=1)
Adds a text column to the table footer.
$mode
The display mode for the table: fixed or tree.
$groupClass
The CSS class to apply to the group subheading rows.
writeScript()
Write the Javascript to implement the table output.
autoPopulate($item)
Automatically build the table columns from the given DataItem.
$groups
The group definitions.
$menu
Context menu for selection-based operations.
$commandMenuIcon
Icon to use for the command menu dropdown.
drawViewToString()
Render the table view and return the generated HTML as a string.
column($title, $format, $sortable=false, $style=null, $typeHint=null, $onExport=null, $sortFormat=null)
Adds a column definition to the DataListView.
$cssClass
Optional CSS class to apply to the table.
drawView()
Writes the HTML for the data table to the output buffer.
$excelFile
Name of the Excel file for automatic export to excel.
$tagRowCallbacks
Array of callbacks for adding extra attributes to each row.
footerTotal($field, $style="text-align: right", $colspan=1)
Sums the total for a column.
group($title, $key, $expanded=false, $fixed=false)
Adds a group to the view.
$groupAsWorksheets
Format groups as separate worksheets when exporting to Excel (requires PHPExcel)
$columns
The column definitions for the table.
footerValue($callback, $style="", $colspan=1)
Adds a value column to the table footer.
GroupedDataListView($items, $id, $cssClass="list", $groupClass="subheading")
Creates a new GroupedDataListView object.
getExcelLink()
Get the Excel Link.
exportColumn($title, $format, $sortable=false, $style=null, $typeHint=null, $onExport=null, $sortFormat=null)
Adds a column definition to the DataListView that is only output when exporting the table to Excel.
format($group, $template)
Outputs the text for the groupedBy title bar using the given group dataitem object.
footerText($text="", $style="", $colspan=1)
Adds a text column to the table footer.
$selectable
Whether the table provides a selection mechanism.
commandMenu()
Adds a ContextMenu to the table that can contain a list of operations that can be performed on subsel...
groupFooterTotal($field, $style="text-align: right", $colspan=1)
writeExcelFile($file)
Writes the data table out as an Excel File.
$id
ID of the table in the output script.
$emptyMessage
Message to display when no items are present in the data set.
stripHTMLTags($text)
Removes all HTML tags from the specified string.
Definition: functions.inc:1491
stripHTML($text)
Definition: functions.inc:847
baseURI($uri=null)
Returns the base URI for the current script, with the query string removed.
Definition: functions.inc:1582
getFullQueryString($includePOST=true)
Generates a query string containing the values passed to this page.
Definition: functions.inc:1634
appendToQueryString($qs, $params)
Appends the specified parameters to the supplied query string.
Definition: functions.inc:1594
makeRandomString($len)
Creates a randomized string of characters.
Definition: functions.inc:1735