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