CWIS Developer Documentation
StdLib.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: StdLib.php
4 #
5 # Part of the ScoutLib application support library
6 # Copyright 2016 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu
8 #
9 
14 class StdLib
15 {
16 
17  # ---- PUBLIC INTERFACE --------------------------------------------------
18 
26  public static function GetCallerInfo($Element = NULL)
27  {
28  $Trace = version_compare(PHP_VERSION, "5.4.0", ">=")
29  ? debug_backtrace(FALSE, 2) : debug_backtrace(FALSE);
30  $FullFileName = $Trace[1]["file"];
31  $Info = [
32  "FileName" => basename($FullFileName),
33  "RelativeFileName" => str_replace(getcwd()."/", "", $FullFileName),
34  "FullFileName" => $FullFileName,
35  "LineNumber" => $Trace[1]["line"],
36  ];
37  return ($Element === NULL) ? $Info : $Info[$Element];
38  }
39 
44  public static function GetMyCaller()
45  {
46  $Trace = version_compare(PHP_VERSION, "5.4.0", ">=")
47  ? debug_backtrace(FALSE, 2) : debug_backtrace(FALSE);
48  $FileName = $Trace[1]["file"];
49  $Caller = basename($FileName).":".$Trace[1]["line"];
50  return $Caller;
51  }
52 
69  public static function CheckMyCaller(
70  $DesiredCaller,
71  $ExceptionMsg = NULL)
72  {
73  # retrieve caller info
74  $Trace = version_compare(PHP_VERSION, "5.4.0", ">=")
75  ? debug_backtrace(FALSE, 3) : debug_backtrace(FALSE);
76  $FullFile = $Trace[1]["file"];
77  $File = basename($FullFile);
78  $Line = $Trace[1]["line"];
79  $Class = isset($Trace[2]["class"]) ? $Trace[2]["class"] : "";
80  $Function = isset($Trace[2]["function"]) ? $Trace[2]["function"] : "";
81 
82  # if caller does not match desired caller
83  if (($DesiredCaller != $Class)
84  && ($DesiredCaller != $Class."::".$Function)
85  && ($DesiredCaller != $Class.$Function)
86  && ($DesiredCaller != $File)
87  && ($DesiredCaller != $File.":".$Line))
88  {
89  # if exception message supplied
90  if ($ExceptionMsg !== NULL)
91  {
92  # make any needed substitutions in exception message
93  $Msg = str_replace(
94  array(
95  "%FILE%",
96  "%LINE%",
97  "%FULLFILE%",
98  "%CLASS%",
99  "%FUNCTION%",
100  "%METHOD%"),
101  array(
102  $File,
103  $Line,
104  $FullFile,
105  $Class,
106  $Function,
107  $Class."::".$Function),
108  $ExceptionMsg);
109 
110  # throw exception
111  throw new Exception($Msg);
112  }
113  else
114  {
115  # report to our caller that their caller was not the desired one
116  return FALSE;
117  }
118  }
119 
120  # report to our caller that their caller was not the desired one
121  return TRUE;
122  }
123 
130  public static function GetBacktraceAsString($IncludeArgs = TRUE)
131  {
132  # get backtrace text
133  ob_start();
134  $TraceOpts = $IncludeArgs ? 0 : DEBUG_BACKTRACE_IGNORE_ARGS;
135  debug_print_backtrace($TraceOpts);
136  $TraceString = ob_get_contents();
137  ob_end_clean();
138 
139  # remove this function from backtrace entries
140  $TraceString = preg_replace(
141  "/^#0\s+".__METHOD__."[^\n]*\n/", "", $TraceString, 1);
142 
143  # renumber backtrace entries
144  $TraceString = preg_replace_callback("/^#(\d+)/m", function($Matches)
145  {
146  return "#".($Matches[1] - 1);
147  },
148  $TraceString);
149 
150  # strip leading path off files names
151  $HomeDir = dirname($_SERVER["SCRIPT_FILENAME"]);
152  $TraceString = preg_replace("%".preg_quote($HomeDir, "%")."/%",
153  "", $TraceString);
154 
155  # return backtrace string to caller
156  return $TraceString;
157  }
158 
164  public static function Pluralize($Word)
165  {
166  # return word unchanged if singular and plural are the same
167  if (in_array(strtolower($Word), self::$UncountableWords))
168  {
169  return $Word;
170  }
171 
172  # check for irregular singular forms
173  foreach (self::$IrregularWords as $Pattern => $Result)
174  {
175  $Pattern = '/'.$Pattern.'$/i';
176  if (preg_match($Pattern, $Word))
177  {
178  return preg_replace($Pattern, $Result, $Word);
179  }
180  }
181 
182  # check for matches using regular expressions
183  foreach (self::$PluralizePatterns as $Pattern => $Result)
184  {
185  if (preg_match($Pattern, $Word))
186  {
187  return preg_replace($Pattern, $Result, $Word);
188  }
189  }
190 
191  # return word unchanged if we could not process it
192  return $Word;
193  }
194 
200  public static function Singularize($Word)
201  {
202  # return word unchanged if singular and plural are the same
203  if (in_array(strtolower($Word), self::$UncountableWords))
204  {
205  return $Word;
206  }
207 
208  # check for irregular plural forms
209  foreach (self::$IrregularWords as $Result => $Pattern)
210  {
211  $Pattern = '/'.$Pattern.'$/i';
212  if (preg_match($Pattern, $Word))
213  {
214  return preg_replace($Pattern, $Result, $Word);
215  }
216  }
217 
218  # check for matches using regular expressions
219  foreach (self::$SingularizePatterns as $Pattern => $Result)
220  {
221  if (preg_match($Pattern, $Word))
222  {
223  return preg_replace($Pattern, $Result, $Word);
224  }
225  }
226 
227  # return word unchanged if we could not process it
228  return $Word;
229  }
230 
239  public static function NeatlyTruncateString(
240  $String, $MaxLength, $BreakAnywhere = FALSE)
241  {
242  $TagStrippedString = strip_tags(html_entity_decode($String));
243 
244  # if the string contained no HTML tags, we can just treat it as text
245  if ($String == $TagStrippedString)
246  {
247  $Length = self::strlen($String);
248 
249  # if string was short enough, we need not do anything
250  if ($Length <= $MaxLength)
251  {
252  return $String;
253  }
254 
255  # if BreakAnywhere is set, just chop at the max length
256  if ($BreakAnywhere)
257  {
258  $BreakPos = $MaxLength;
259  }
260  # otherwise look for an acceptable breakpoint
261  else
262  {
263  $BreakPos = self::strrpos($String, " ", -($Length - $MaxLength));
264 
265  # if we couldn't find a breakpoint, just chop at max length
266  if ($BreakPos === FALSE)
267  {
268  $BreakPos = $MaxLength;
269  }
270  }
271 
272  $Result = self::substr($String, 0, $BreakPos);
273 
274  # tack on the ellipsis
275  $Result .= "...";
276  }
277  # otherwise, we're in an HTML string and we have to account for
278  # how many characters are actually displayed when the string will be
279  # rendered
280  else
281  {
282  # if there aren't enough non-whitespace displayed characters to
283  # exceed the max length, bail because we don't need to do
284  # anything
285  if (self::strlen(trim($TagStrippedString)) <= $MaxLength)
286  {
287  return $String;
288  }
289 
290  # okay, the hard way -- we have to do some limited parsing
291  # of html and attempt to count the number of printing characters
292  # as we're doing that. to accomplish this, we'll build a
293  # little state machine and iterate over the characters one at a
294  # time
295 
296  # split the string into characters (annoyingly, split()/mb_split()
297  # cannot do this, so we have to use preg_split() in unicode mode)
298  $Tokens = preg_split('%%u', $String, -1, PREG_SPLIT_NO_EMPTY);
299 
300  # define our states
301  $S_Text = 0;
302  $S_MaybeTag = 1;
303  $S_MaybeEntity = 2;
304  $S_Tag = 3;
305  $S_Quote = 4;
306 
307  # max length of an HTML Entity
308  $MaxEntityLength = 8;
309 
310  # track how much we have displayed
311  $DisplayedLength = 0;
312 
313  $Buffer = ""; # for characters we're not sure about
314  $BufLen = 0; # count of buffered characters
315  $Result = ""; # for characters we've included
316  $QuoteChar =""; # quote character in use
317 
318  # start in the 'text state'
319  $State = $S_Text;
320 
321  # iterate over all our tokens
322  foreach ($Tokens as $Token)
323  {
324  switch ($State)
325  {
326  # text state handles words that will be displayed
327  case $S_Text:
328  switch($Token)
329  {
330  # look for characters that can end a word
331  case "<":
332  case "&":
333  case " ":
334  # if we've buffered up a word
335  if ($BufLen > 0)
336  {
337  # and if displaying that word exceeds
338  # our length, then we're done
339  if ($DisplayedLength + $BufLen > $MaxLength)
340  {
341  break 3;
342  }
343 
344  # otherwise, add the buffered word to our display
345  $Result .= $Buffer;
346  $DisplayedLength += $BufLen;
347  }
348 
349  # buffer this character
350  $Buffer = $Token;
351  $BufLen = 1;
352 
353  # if it could have been the start of a tag or an entity,
354  # change state appropriately
355  if ($Token != " ")
356  {
357  $State = ($Token == "<") ? $S_MaybeTag :
358  $S_MaybeEntity;
359  }
360  break;
361 
362  # for characters that can't break a word, just buffer them
363  default:
364  $Buffer .= $Token;
365  $BufLen++;
366  break;
367  }
368  break;
369 
370  # MaybeTag state checks if a < began a tag or not
371  case $S_MaybeTag:
372  # tags start with alpha characters (like <b>)
373  # or a slash (like </b>)
374  if (ctype_alpha($Token) || $Token == "/")
375  {
376  # if this was a tag, output it, output it,
377  # clear our buffer, and move to the Tag state
378  $Result .= $Buffer.$Token;
379  $Buffer = "";
380  $BufLen = 0;
381  $State = $S_Tag;
382  }
383  else
384  {
385  # otherwise, check if displaying this character would
386  # exceed our length. bail if so
387  if ($DisplayedLength + 1 > $MaxLength)
388  {
389  break 2;
390  }
391  # if not, output the characters, clear our buffer,
392  # move to the Text state
393  $Result .= $Buffer.$Token;
394  $DisplayedLength++;
395  $Buffer = "";
396  $BufLen = 0;
397  $State = $S_Text;
398  }
399  break;
400 
401  # Tag state processes the contents of a tag
402  case $S_Tag:
403  # always output tag contents
404  $Result .= $Token;
405 
406  # check if this is the beginning of a quoted string,
407  # changing state appropriately if so
408  if ($Token == "\"" || $Token == "'")
409  {
410  $QuoteChar = $Token;
411  $State = $S_Quote;
412  }
413  # if this is the end of the tag, go back to Text state
414  elseif ($Token == ">")
415  {
416  $State = $S_Text;
417  }
418  break;
419 
420  # Quote state processes quoted attributes
421  case $S_Quote:
422  # always output quote contents
423  $Result .= $Token;
424 
425  # if we've found the matching quote character,
426  # return to the Tag state
427  if ($Token == $QuoteChar)
428  {
429  $State = $S_Tag;
430  }
431  break;
432 
433  # MaybeEntity decides if we're enjoying an HTML entity
434  # or just an ampersand
435  case $S_MaybeEntity:
436  # buffer this token
437  $Buffer.= $Token;
438  $BufLen++;
439 
440  # if it was a space, then we're not in an entity
441  # as they cannot contain spaces
442  if ($Token == " ")
443  {
444  # check if we should be fone
445  if ($DisplayedLength + $BufLen > $MaxLength)
446  {
447  break 2;
448  }
449  # if not, output the buffer, clear it, and return to Text
450  $Result .= $Buffer;
451  $DisplayedLength += $BufLen;
452  $Buffer = "";
453  $BufLen = 0;
454  $State = $S_Text;
455  }
456  # if we have &xxxx; then count that as a single character entity,
457  # output it, clear the buffer, and return to Text
458  elseif ($Token == ";")
459  {
460  $Result .= $Buffer;
461  $DisplayedLength++;
462  $Buffer = "";
463  $BufLen = 0;
464  $State = $S_Text;
465  }
466  # if this has been too many characters without a ;
467  # for it to be an entity, return to text
468  elseif ($BufLen > 8)
469  {
470  $State = $S_Text;
471  }
472 
473  break;
474  }
475  }
476 
477  # tack on the ellipsis
478  $Result .= "...";
479 
480  # if our string contained HTML tags that we may need to close
481  if (preg_match_all("%<(/?[a-z]+)[^>]*>%", $Result, $Matches))
482  {
483  # pull out matches for the names of tags
484  $Matches = $Matches[1];
485 
486  # build up an array of open tags
487  $Tags = array();
488  while ( ($Tag = array_shift($Matches)) !== NULL )
489  {
490  # if this was not a close tag, prepend it to our array
491  if (self::substr($Tag, 0, 1) != "/")
492  {
493  array_unshift($Tags, $Tag);
494  }
495  # if this tag is not self-closing, append it to our list of open tags
496  elseif (self::substr($Tag, -1) != "/")
497  {
498  # if this was a close tag, look to see if this tag was open
499  $Tgt = array_search(self::substr($Tag, 1), $Tags);
500  if ($Tgt !== FALSE)
501  {
502  # if so, remove this tag from our list
503  unset($Tags[$Tgt]);
504  }
505  }
506  }
507 
508  # iterate over open tags, closing them as we go
509  while ( ($Tag = array_shift($Tags)) !== NULL)
510  {
511  $Result .= "</".$Tag.">";
512  }
513  }
514  }
515 
516  return $Result;
517  }
518 
523  public static function substr()
524  {
525  return self::CallMbStringFuncIfAvailable(__FUNCTION__, func_get_args(), 3);
526  }
527 
532  public static function strpos()
533  {
534  return self::CallMbStringFuncIfAvailable(__FUNCTION__, func_get_args(), 3);
535  }
536 
541  public static function strrpos()
542  {
543  return self::CallMbStringFuncIfAvailable(__FUNCTION__, func_get_args(), 3);
544  }
545 
550  public static function strlen()
551  {
552  return self::CallMbStringFuncIfAvailable(__FUNCTION__, func_get_args(), 1);
553  }
554 
563  public static function EncodeStringForCdata($String)
564  {
565  return "<![CDATA[".str_replace("]]>", "]]]]><![CDATA[>", $String)."]]>";
566  }
567 
575  public static function SortCompare($A, $B)
576  {
577  if ($A == $B)
578  {
579  return 0;
580  }
581  else
582  {
583  return ($A < $B) ? -1 : 1;
584  }
585  }
586 
599  public static function GetLatLngForZipCode($Zip)
600  {
601  static $ZipCache = array();
602 
603  # if we don't have a cached value for this zip, look one up
604  if (!isset($ZipCache[$Zip]))
605  {
606  # try to open our zip code database
607  $FHandle = fopen(dirname(__FILE__)."/StdLib--ZipCodeCoords.txt", "r");
608 
609  # if we couldn't open the file, we can't look up a value
610  if ($FHandle === FALSE)
611  {
612  throw new Exception("Unable to open zip code coordinates file");
613  }
614 
615  # iterate over our database until we find the desired zip
616  # or run out of database
617  while (($Line = fgetcsv($FHandle, 0, "\t")) !== FALSE)
618  {
619  if ($Line[0] == $Zip)
620  {
621  $ZipCache[$Zip] = array(
622  "Lat" => $Line[1], "Lng" => $Line[2]);
623  break;
624  }
625  }
626 
627  # if we've scanned the entire file and have no coords for
628  # this zip, cache a failure
629  if (!isset($ZipCache[$Zip]))
630  {
631  $ZipCache[$Zip] = FALSE;
632  }
633  }
634 
635  # hand back cached value
636  return $ZipCache[$Zip];
637  }
638 
646  public static function ZipCodeDistance($ZipA, $ZipB)
647  {
648 
649  $FirstPoint = self::GetLatLngForZipCode($ZipA);
650  $SecondPoint = self::GetLatLngForZipCode($ZipB);
651 
652  # if we scanned the whole file and lack data for either of our
653  # points, return NULL
654  if ($FirstPoint === FALSE || $SecondPoint === FALSE)
655  {
656  return FALSE;
657  }
658 
659  return self::ComputeGreatCircleDistance(
660  $FirstPoint["Lat"], $FirstPoint["Lng"],
661  $SecondPoint["Lat"], $SecondPoint["Lng"]);
662  }
663 
673  public static function ComputeGreatCircleDistance($LatSrc, $LonSrc,
674  $LatDst, $LonDst)
675  {
676  # See http://en.wikipedia.org/wiki/Great-circle_distance
677 
678  # Convert it all to Radians
679  $Ps = deg2rad($LatSrc);
680  $Ls = deg2rad($LonSrc);
681  $Pf = deg2rad($LatDst);
682  $Lf = deg2rad($LonDst);
683 
684  # Compute the central angle
685  return 3958.756 * atan2(
686  sqrt( pow(cos($Pf)*sin($Lf-$Ls), 2) +
687  pow(cos($Ps)*sin($Pf) -
688  sin($Ps)*cos($Pf)*cos($Lf-$Ls), 2)),
689  sin($Ps)*sin($Pf)+cos($Ps)*cos($Pf)*cos($Lf-$Ls));
690 
691  }
692 
702  public static function ComputeBearing($LatSrc, $LonSrc,
703  $LatDst, $LonDst)
704  {
705  # See http://mathforum.org/library/drmath/view/55417.html
706 
707  # Convert angles to radians
708  $Ps = deg2rad($LatSrc);
709  $Ls = deg2rad($LonSrc);
710  $Pf = deg2rad($LatDst);
711  $Lf = deg2rad($LonDst);
712 
713  return rad2deg(atan2(sin($Lf-$Ls)*cos($Pf),
714  cos($Ps)*sin($Pf)-sin($Ps)*cos($Pf)*cos($Lf-$Ls)));
715  }
716 
724  public static function ArrayPermutations($Items, $Perms = array())
725  {
726  if (empty($Items))
727  {
728  $Result = array($Perms);
729  }
730  else
731  {
732  $Result = array();
733  for ($Index = count($Items) - 1; $Index >= 0; --$Index)
734  {
735  $NewItems = $Items;
736  $NewPerms = $Perms;
737  list($Segment) = array_splice($NewItems, $Index, 1);
738  array_unshift($NewPerms, $Segment);
739  $Result = array_merge($Result,
740  self::ArrayPermutations($NewItems, $NewPerms));
741  }
742  }
743  return $Result;
744  }
745 
752  public static function GetUsStatesList()
753  {
754  return array(
755  "AL" => "Alabama",
756  "AK" => "Alaska",
757  "AZ" => "Arizona",
758  "AR" => "Arkansas",
759  "CA" => "California",
760  "CO" => "Colorado",
761  "CT" => "Connecticut",
762  "DE" => "Delaware",
763  "DC" => "District of Columbia",
764  "FL" => "Florida",
765  "GA" => "Georgia",
766  "HI" => "Hawaii",
767  "ID" => "Idaho",
768  "IL" => "Illinois",
769  "IN" => "Indiana",
770  "IA" => "Iowa",
771  "KS" => "Kansas",
772  "KY" => "Kentucky",
773  "LA" => "Louisiana",
774  "ME" => "Maine",
775  "MD" => "Maryland",
776  "MA" => "Massachusetts",
777  "MI" => "Michigan",
778  "MN" => "Minnesota",
779  "MS" => "Mississippi",
780  "MO" => "Missouri",
781  "MT" => "Montana",
782  "NE" => "Nebraska",
783  "NV" => "Nevada",
784  "NH" => "New Hampshire",
785  "NJ" => "New Jersey",
786  "NM" => "New Mexico",
787  "NY" => "New York",
788  "NC" => "North Carolina",
789  "ND" => "North Dakota",
790  "OH" => "Ohio",
791  "OK" => "Oklahoma",
792  "OR" => "Oregon",
793  "PA" => "Pennsylvania",
794  "RI" => "Rhode Island",
795  "SC" => "South Carolina",
796  "SD" => "South Dakota",
797  "TN" => "Tennessee",
798  "TX" => "Texas",
799  "UT" => "Utah",
800  "VT" => "Vermont",
801  "VA" => "Virginia",
802  "WA" => "Washington",
803  "WV" => "West Virginia",
804  "WI" => "Wisconsin",
805  "WY" => "Wyoming",
806  );
807  }
808 
817  public static function AdjustHexColor($Color, $LAdjust, $SAdjust = 0)
818  {
819  # split into RGB components
820  $Color = str_replace("#", "", $Color);
821  $Pieces = str_split($Color, 2);
822  $R = hexdec($Pieces[0]);
823  $G = hexdec($Pieces[1]);
824  $B = hexdec($Pieces[2]);
825 
826  # convert RGB to HSL
827  list($H, $S, $L) = array_values(self::RgbToHsl($R, $G, $B));
828 
829  # adjust luminance and saturation
830  $L = $L + ($L * ($LAdjust / 100));
831  $S = $S + ($S * ($SAdjust / 100));
832 
833  # convert HSL to RGB
834  extract(self::HslToRgb($H, $S, $L));
835 
836  # assemble RGB components back into hex color string
837  $FormFunc = function ($Comp) {
838  return str_pad(strtoupper(dechex($Comp)), 2, "0", STR_PAD_LEFT);
839  };
840  $NewColor = "#".$FormFunc($R).$FormFunc($G).$FormFunc($B);
841 
842  # return new color to caller
843  return $NewColor;
844  }
845 
856  public static function GetConstantName($ClassName, $Value, $Prefix = NULL)
857  {
858  # retrieve all constants for class
859  if (is_object($ClassName))
860  {
861  $ClassName = get_class($ClassName);
862  }
863  $Reflect = new ReflectionClass($ClassName);
864  $Constants = $Reflect->getConstants();
865 
866  # for each constant
867  foreach ($Constants as $CName => $CValue)
868  {
869  # if value matches and prefix (if supplied) matches
870  if (($CValue == $Value)
871  && (($Prefix === NULL) || (strpos($CName, $Prefix) === 0)))
872  {
873  # return name to caller
874  return $CName;
875  }
876  }
877 
878  # report to caller that no matching constant was found
879  return NULL;
880  }
881 
888  public static function HexToRgba($Hex, $Opacity = 1)
889  {
890  $Hex = preg_replace('/[^a-fA-F0-9]/', "", $Hex);
891 
892  if (strlen($Hex) == 6)
893  {
894  $Color["R"] = hexdec($Hex{0}.$Hex{1});
895  $Color["G"] = hexdec($Hex{2}.$Hex{3});
896  $Color["B"] = hexdec($Hex{4}.$Hex{5});
897  }
898  else if (strlen($Hex) == 3)
899  {
900  $Color["R"] = hexdec($Hex{0});
901  $Color["G"] = hexdec($Hex{1});
902  $Color["B"] = hexdec($Hex{2});
903  }
904 
905  return "rgba("
906  .$Color["R"].","
907  .$Color["G"].","
908  .$Color["B"].","
909  .$Opacity.")";
910  }
911 
913  const SQL_DATE_FORMAT = "Y-m-d H:i:s";
914 
915 
916  # ---- PRIVATE INTERFACE -------------------------------------------------
917 
918  private static $PluralizePatterns = array(
919  '/(quiz)$/i' => "$1zes",
920  '/^(ox)$/i' => "$1en",
921  '/([m|l])ouse$/i' => "$1ice",
922  '/(matr|vert|ind)ix|ex$/i' => "$1ices",
923  '/(x|ch|ss|sh)$/i' => "$1es",
924  '/([^aeiouy]|qu)y$/i' => "$1ies",
925  '/(hive)$/i' => "$1s",
926  '/(?:([^f])fe|([lr])f)$/i' => "$1$2ves",
927  '/(shea|lea|loa|thie)f$/i' => "$1ves",
928  '/sis$/i' => "ses",
929  '/([ti])um$/i' => "$1a",
930  '/(tomat|potat|ech|her|vet)o$/i'=> "$1oes",
931  '/(bu)s$/i' => "$1ses",
932  '/(alias)$/i' => "$1es",
933  '/(octop)us$/i' => "$1i",
934  '/(ax|test)is$/i' => "$1es",
935  '/(us)$/i' => "$1es",
936  '/s$/i' => "s",
937  '/$/' => "s"
938  );
939  private static $SingularizePatterns = array(
940  '/(quiz)zes$/i' => "$1",
941  '/(matr)ices$/i' => "$1ix",
942  '/(vert|ind)ices$/i' => "$1ex",
943  '/^(ox)en$/i' => "$1",
944  '/(alias)es$/i' => "$1",
945  '/(octop|vir)i$/i' => "$1us",
946  '/(cris|ax|test)es$/i' => "$1is",
947  '/(shoe)s$/i' => "$1",
948  '/(o)es$/i' => "$1",
949  '/(bus)es$/i' => "$1",
950  '/([m|l])ice$/i' => "$1ouse",
951  '/(x|ch|ss|sh)es$/i' => "$1",
952  '/(m)ovies$/i' => "$1ovie",
953  '/(s)eries$/i' => "$1eries",
954  '/([^aeiouy]|qu)ies$/i' => "$1y",
955  '/([lr])ves$/i' => "$1f",
956  '/(tive)s$/i' => "$1",
957  '/(hive)s$/i' => "$1",
958  '/(li|wi|kni)ves$/i' => "$1fe",
959  '/(shea|loa|lea|thie)ves$/i'=> "$1f",
960  '/(^analy)ses$/i' => "$1sis",
961  '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => "$1$2sis",
962  '/([ti])a$/i' => "$1um",
963  '/(n)ews$/i' => "$1ews",
964  '/(h|bl)ouses$/i' => "$1ouse",
965  '/(corpse)s$/i' => "$1",
966  '/(us)es$/i' => "$1",
967  '/s$/i' => ""
968  );
969  private static $IrregularWords = array(
970  'move' => 'moves',
971  'foot' => 'feet',
972  'goose' => 'geese',
973  'sex' => 'sexes',
974  'child' => 'children',
975  'man' => 'men',
976  'tooth' => 'teeth',
977  'person' => 'people'
978  );
979  private static $UncountableWords = array(
980  'sheep',
981  'fish',
982  'deer',
983  'series',
984  'species',
985  'money',
986  'rice',
987  'information',
988  'equipment'
989  );
990 
998  private static function CallMbStringFuncIfAvailable(
999  $Func,
1000  $Args,
1001  $NumPlainArgs)
1002  {
1003  if (function_exists("mb_".$Func))
1004  {
1005  $FuncToCall = "mb_".$Func;
1006  }
1007  else
1008  {
1009  if (count($Args) > $NumPlainArgs)
1010  {
1011  throw new Exception(
1012  "Function mb_".$Func."() required but not available.");
1013  }
1014  $FuncToCall = $Func;
1015  }
1016  return call_user_func_array($FuncToCall, $Args);
1017  }
1018 
1030  private static function RgbToHsl($R, $G, $B)
1031  {
1032  # ensure incoming values are within range
1033  $R = max(min($R, 255), 0);
1034  $G = max(min($G, 255), 0);
1035  $B = max(min($B, 255), 0);
1036 
1037  # scale RGB values to range of 0-1
1038  $R /= 255;
1039  $G /= 255;
1040  $B /= 255;
1041 
1042  # determine RGB range
1043  $MinVal = min($R, $G, $B);
1044  $MaxVal = max($R, $G, $B);
1045  $MaxDif = $MaxVal - $MinVal;
1046 
1047  # calculate luminance
1048  $L = ($MinVal + $MaxVal) / 2.0;
1049  $L *= 100;
1050 
1051  # if RGB are all equal
1052  if ($MinVal == $MaxVal)
1053  {
1054  # no hue or saturation
1055  $S = 0;
1056  $H = 0;
1057  }
1058  else
1059  {
1060  # calculate saturation
1061  if ($L < 50)
1062  {
1063  $S = $MaxDif / ($MinVal + $MaxVal);
1064  }
1065  else
1066  {
1067  $S = $MaxDif / ((2.0 - $MaxVal) - $MinVal);
1068  }
1069  $S *= 100;
1070 
1071  # calculate hue
1072  switch ($MaxVal)
1073  {
1074  case $R:
1075  $H = ($G - $B) / $MaxDif;
1076  break;
1077 
1078  case $G:
1079  $H = ($B - $R) / $MaxDif;
1080  $H += 2.0;
1081  break;
1082 
1083  case $B:
1084  $H = ($R - $G) / $MaxDif;
1085  $H += 4.0;
1086  break;
1087  }
1088  $H *= 60;
1089  if ($H < 0)
1090  {
1091  $H += 360;
1092  }
1093  }
1094 
1095  # return calculated values to caller
1096  return [ "H" => $H, "S" => $S, "L" => $L ];
1097  }
1098 
1109  private static function HslToRgb($H, $S, $L)
1110  {
1111  # ensure incoming values are within range
1112  $H = max(min($H, 360), 0);
1113  $S = max(min($S, 100), 0);
1114  $L = max(min($L, 100), 0);
1115 
1116  # scale HSL to range of 0-1
1117  $H /= 360;
1118  $S /= 100;
1119  $L /= 100;
1120 
1121  # if no saturation
1122  if ($S == 0)
1123  {
1124  # result is greyscale, with equal RGB values based on luminance
1125  $R = $L * 255;
1126  $G = $R;
1127  $B = $R;
1128  }
1129  else
1130  {
1131  # calculate RGB
1132  $R = self::HslToRgbComponent(($H + (1 / 3)), $S, $L) * 255;
1133  $G = self::HslToRgbComponent($H, $S, $L) * 255;
1134  $B = self::HslToRgbComponent(($H - (1 / 3)), $S, $L) * 255;
1135  }
1136 
1137  # return calculated values to caller
1138  return [ "R" => $R, "G" => $G, "B" => $B ];
1139  }
1140 
1151  private static function HslToRgbComponent($H, $S, $L)
1152  {
1153  # ensure hue is in the range 0-1
1154  if ($H < 0) { $H += 1; }
1155  elseif ($H > 1) { $H -= 1; }
1156 
1157  # calculate saturation/luminance adjustments
1158  if ($L < 0.5)
1159  {
1160  $Adj1 = (1 + $S) * $L;
1161  }
1162  else
1163  {
1164  $Adj1 = ($S + $L) - ($S * $L);
1165  }
1166  $Adj2 = ($L * 2) - $Adj1;
1167 
1168  # calculate RGB component and return it to caller
1169  if (($H * 6) < 1)
1170  {
1171  return (($Adj1 - $Adj2) * $H * 6) + $Adj2;
1172  }
1173  elseif (($H * 2) < 1)
1174  {
1175  return $Adj1;
1176  }
1177  elseif (($H * 3) < 2)
1178  {
1179  return (($Adj1 - $Adj2) * ((2 / 3) - $H) * 6) + $Adj2;
1180  }
1181  else
1182  {
1183  return $Adj2;
1184  }
1185  }
1186 }
static AdjustHexColor($Color, $LAdjust, $SAdjust=0)
Adjust hexadecimal RGB color by specified amount.
Definition: StdLib.php:817
static strrpos()
Multibyte-aware (if supported in PHP) version of strrpos().
Definition: StdLib.php:541
static CheckMyCaller($DesiredCaller, $ExceptionMsg=NULL)
Check the caller of the current function.
Definition: StdLib.php:69
static SortCompare($A, $B)
Perform compare and return value appropriate for sort function callbacks.
Definition: StdLib.php:575
static ZipCodeDistance($ZipA, $ZipB)
Compute the distance between two US ZIP codes.
Definition: StdLib.php:646
static EncodeStringForCdata($String)
Encode string to be written out in XML as CDATA.
Definition: StdLib.php:563
static strpos()
Multibyte-aware (if supported in PHP) version of strpos().
Definition: StdLib.php:532
static GetMyCaller()
Get string with file and line number for call to current function.
Definition: StdLib.php:44
static Pluralize($Word)
Pluralize an English word.
Definition: StdLib.php:164
static GetLatLngForZipCode($Zip)
Look up the GPS coordinates for a US ZIP code.
Definition: StdLib.php:599
Standard utility library.
Definition: StdLib.php:14
static ArrayPermutations($Items, $Perms=array())
Return all possible permutations of a given array.
Definition: StdLib.php:724
static substr()
Multibyte-aware (if supported in PHP) version of substr().
Definition: StdLib.php:523
static GetBacktraceAsString($IncludeArgs=TRUE)
Get backtrace as a string.
Definition: StdLib.php:130
static GetConstantName($ClassName, $Value, $Prefix=NULL)
Get name (string) for constant.
Definition: StdLib.php:856
static ComputeGreatCircleDistance($LatSrc, $LonSrc, $LatDst, $LonDst)
Computes the distance in kilometers between two points, assuming a spherical earth.
Definition: StdLib.php:673
static ComputeBearing($LatSrc, $LonSrc, $LatDst, $LonDst)
Computes the initial angle on a course connecting two points, assuming a spherical earth...
Definition: StdLib.php:702
const SQL_DATE_FORMAT
Format to feed to date() to get SQL-compatible date/time string.
Definition: StdLib.php:913
static NeatlyTruncateString($String, $MaxLength, $BreakAnywhere=FALSE)
Attempt to truncate a string as neatly as possible with respect to word breaks, punctuation, and HTML tags.
Definition: StdLib.php:239
static HexToRgba($Hex, $Opacity=1)
Convert a hex color string (e.g., #FF00FF") to a css &#39;rgba(&#39; format color.
Definition: StdLib.php:888
static strlen()
Multibyte-aware (if supported in PHP) version of strlen().
Definition: StdLib.php:550
static Singularize($Word)
Singularize an English word.
Definition: StdLib.php:200
static GetUsStatesList()
Get an array of US state names with their two-letter abbreviations as the index.
Definition: StdLib.php:752
static GetCallerInfo($Element=NULL)
Get info about call to current function.
Definition: StdLib.php:26