5 # Part of the ScoutLib application support library 6 # Copyright 2016 Edward Almasy and Internet Scout Research Group 7 # http://scout.wisc.edu 17 # ---- PUBLIC INTERFACE -------------------------------------------------- 28 $Trace = version_compare(PHP_VERSION,
"5.4.0",
">=")
29 ? debug_backtrace(FALSE, 2) : debug_backtrace(FALSE);
30 $FullFileName = $Trace[1][
"file"];
32 "FileName" => basename($FullFileName),
33 "RelativeFileName" => str_replace(getcwd().
"/",
"", $FullFileName),
34 "FullFileName" => $FullFileName,
35 "LineNumber" => $Trace[1][
"line"],
37 return ($Element === NULL) ? $Info : $Info[$Element];
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"];
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"] :
"";
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))
89 # if exception message supplied 90 if ($ExceptionMsg !== NULL)
92 # make any needed substitutions in exception message 107 $Class.
"::".$Function),
111 throw new Exception($Msg);
115 # report to our caller that their caller was not the desired one 120 # report to our caller that their caller was not the desired one 134 $TraceOpts = $IncludeArgs ? 0 : DEBUG_BACKTRACE_IGNORE_ARGS;
135 debug_print_backtrace($TraceOpts);
136 $TraceString = ob_get_contents();
139 # remove this function from backtrace entries 140 $TraceString = preg_replace(
141 "/^#0\s+".__METHOD__.
"[^\n]*\n/",
"", $TraceString, 1);
143 # renumber backtrace entries 144 $TraceString = preg_replace_callback(
"/^#(\d+)/m",
function($Matches)
146 return "#".($Matches[1] - 1);
150 # strip leading path off files names 151 $HomeDir = dirname($_SERVER[
"SCRIPT_FILENAME"]);
152 $TraceString = preg_replace(
"%".preg_quote($HomeDir,
"%").
"/%",
155 # return backtrace string to caller 166 # return word unchanged if singular and plural are the same 167 if (in_array(strtolower($Word), self::$UncountableWords))
172 # check for irregular singular forms 173 foreach (self::$IrregularWords as $Pattern => $Result)
175 $Pattern =
'/'.$Pattern.
'$/i';
176 if (preg_match($Pattern, $Word))
178 return preg_replace($Pattern, $Result, $Word);
182 # check for matches using regular expressions 183 foreach (self::$PluralizePatterns as $Pattern => $Result)
185 if (preg_match($Pattern, $Word))
187 return preg_replace($Pattern, $Result, $Word);
191 # return word unchanged if we could not process it 202 # return word unchanged if singular and plural are the same 203 if (in_array(strtolower($Word), self::$UncountableWords))
208 # check for irregular plural forms 209 foreach (self::$IrregularWords as $Result => $Pattern)
211 $Pattern =
'/'.$Pattern.
'$/i';
212 if (preg_match($Pattern, $Word))
214 return preg_replace($Pattern, $Result, $Word);
218 # check for matches using regular expressions 219 foreach (self::$SingularizePatterns as $Pattern => $Result)
221 if (preg_match($Pattern, $Word))
223 return preg_replace($Pattern, $Result, $Word);
227 # return word unchanged if we could not process it 240 $String, $MaxLength, $BreakAnywhere = FALSE)
242 $TagStrippedString = strip_tags(html_entity_decode($String));
244 # if the string contained no HTML tags, we can just treat it as text 245 if ($String == $TagStrippedString)
247 $Length = self::strlen($String);
249 # if string was short enough, we need not do anything 250 if ($Length <= $MaxLength)
255 # if BreakAnywhere is set, just chop at the max length 258 $BreakPos = $MaxLength;
260 # otherwise look for an acceptable breakpoint 263 $BreakPos = self::strrpos($String,
" ", -($Length - $MaxLength));
265 # if we couldn't find a breakpoint, just chop at max length 266 if ($BreakPos === FALSE)
268 $BreakPos = $MaxLength;
272 $Result = self::substr($String, 0, $BreakPos);
274 # tack on the ellipsis 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 282 # if there aren't enough non-whitespace displayed characters to 283 # exceed the max length, bail because we don't need to do 285 if (self::strlen(trim($TagStrippedString)) <= $MaxLength)
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 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);
307 # max length of an HTML Entity 308 $MaxEntityLength = 8;
310 # track how much we have displayed 311 $DisplayedLength = 0;
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
318 # start in the 'text state' 321 # iterate over all our tokens 322 foreach ($Tokens as $Token)
326 # text state handles words that will be displayed 330 # look for characters that can end a word 334 # if we've buffered up a word 337 # and if displaying that word exceeds 338 # our length, then we're done 339 if ($DisplayedLength + $BufLen > $MaxLength)
344 # otherwise, add the buffered word to our display 346 $DisplayedLength += $BufLen;
349 # buffer this character 353 # if it could have been the start of a tag or an entity, 354 # change state appropriately 357 $State = ($Token ==
"<") ? $S_MaybeTag :
362 # for characters that can't break a word, just buffer them 370 # MaybeTag state checks if a < began a tag or not 372 # tags start with alpha characters (like <b>) 373 # or a slash (like </b>) 374 if (ctype_alpha($Token) || $Token ==
"/")
376 # if this was a tag, output it, output it, 377 # clear our buffer, and move to the Tag state 378 $Result .= $Buffer.$Token;
385 # otherwise, check if displaying this character would 386 # exceed our length. bail if so 387 if ($DisplayedLength + 1 > $MaxLength)
391 # if not, output the characters, clear our buffer, 392 # move to the Text state 393 $Result .= $Buffer.$Token;
401 # Tag state processes the contents of a tag 403 # always output tag contents 406 # check if this is the beginning of a quoted string, 407 # changing state appropriately if so 408 if ($Token ==
"\"" || $Token ==
"'")
413 # if this is the end of the tag, go back to Text state 414 elseif ($Token ==
">")
420 # Quote state processes quoted attributes 422 # always output quote contents 425 # if we've found the matching quote character, 426 # return to the Tag state 427 if ($Token == $QuoteChar)
433 # MaybeEntity decides if we're enjoying an HTML entity 434 # or just an ampersand 440 # if it was a space, then we're not in an entity 441 # as they cannot contain spaces 444 # check if we should be fone 445 if ($DisplayedLength + $BufLen > $MaxLength)
449 # if not, output the buffer, clear it, and return to Text 451 $DisplayedLength += $BufLen;
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 ==
";")
466 # if this has been too many characters without a ; 467 # for it to be an entity, return to text 477 # tack on the ellipsis 480 # if our string contained HTML tags that we may need to close 481 if (preg_match_all(
"%<(/?[a-z]+)[^>]*>%", $Result, $Matches))
483 # pull out matches for the names of tags 484 $Matches = $Matches[1];
486 # build up an array of open tags 488 while ( ($Tag = array_shift($Matches)) !== NULL )
490 # if this was not a close tag, prepend it to our array 491 if (self::substr($Tag, 0, 1) !=
"/")
493 array_unshift($Tags, $Tag);
495 # if this tag is not self-closing, append it to our list of open tags 496 elseif (self::substr($Tag, -1) !=
"/")
498 # if this was a close tag, look to see if this tag was open 499 $Tgt = array_search(self::substr($Tag, 1), $Tags);
502 # if so, remove this tag from our list 508 # iterate over open tags, closing them as we go 509 while ( ($Tag = array_shift($Tags)) !== NULL)
511 $Result .=
"</".$Tag.
">";
525 return self::CallMbStringFuncIfAvailable(__FUNCTION__, func_get_args(), 3);
534 return self::CallMbStringFuncIfAvailable(__FUNCTION__, func_get_args(), 3);
543 return self::CallMbStringFuncIfAvailable(__FUNCTION__, func_get_args(), 3);
552 return self::CallMbStringFuncIfAvailable(__FUNCTION__, func_get_args(), 1);
565 return "<![CDATA[".str_replace(
"]]>",
"]]]]><![CDATA[>", $String).
"]]>";
583 return ($A < $B) ? -1 : 1;
601 static $ZipCache = array();
603 # if we don't have a cached value for this zip, look one up 604 if (!isset($ZipCache[$Zip]))
606 # try to open our zip code database 607 $FHandle = fopen(dirname(__FILE__).
"/StdLib--ZipCodeCoords.txt",
"r");
609 # if we couldn't open the file, we can't look up a value 610 if ($FHandle === FALSE)
612 throw new Exception(
"Unable to open zip code coordinates file");
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)
619 if ($Line[0] == $Zip)
621 $ZipCache[$Zip] = array(
622 "Lat" => $Line[1],
"Lng" => $Line[2]);
627 # if we've scanned the entire file and have no coords for 628 # this zip, cache a failure 629 if (!isset($ZipCache[$Zip]))
631 $ZipCache[$Zip] = FALSE;
635 # hand back cached value 636 return $ZipCache[$Zip];
649 $FirstPoint = self::GetLatLngForZipCode($ZipA);
650 $SecondPoint = self::GetLatLngForZipCode($ZipB);
652 # if we scanned the whole file and lack data for either of our 653 # points, return NULL 654 if ($FirstPoint === FALSE || $SecondPoint === FALSE)
659 return self::ComputeGreatCircleDistance(
660 $FirstPoint[
"Lat"], $FirstPoint[
"Lng"],
661 $SecondPoint[
"Lat"], $SecondPoint[
"Lng"]);
676 # See http://en.wikipedia.org/wiki/Great-circle_distance 678 # Convert it all to Radians 679 $Ps = deg2rad($LatSrc);
680 $Ls = deg2rad($LonSrc);
681 $Pf = deg2rad($LatDst);
682 $Lf = deg2rad($LonDst);
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));
705 # See http://mathforum.org/library/drmath/view/55417.html 707 # Convert angles to radians 708 $Ps = deg2rad($LatSrc);
709 $Ls = deg2rad($LonSrc);
710 $Pf = deg2rad($LatDst);
711 $Lf = deg2rad($LonDst);
713 return rad2deg(atan2(sin($Lf-$Ls)*cos($Pf),
714 cos($Ps)*sin($Pf)-sin($Ps)*cos($Pf)*cos($Lf-$Ls)));
728 $Result = array($Perms);
733 for ($Index = count(
$Items) - 1; $Index >= 0; --$Index)
737 list($Segment) = array_splice($NewItems, $Index, 1);
738 array_unshift($NewPerms, $Segment);
739 $Result = array_merge($Result,
740 self::ArrayPermutations($NewItems, $NewPerms));
759 "CA" =>
"California",
761 "CT" =>
"Connecticut",
763 "DC" =>
"District of Columbia",
776 "MA" =>
"Massachusetts",
779 "MS" =>
"Mississippi",
784 "NH" =>
"New Hampshire",
785 "NJ" =>
"New Jersey",
786 "NM" =>
"New Mexico",
788 "NC" =>
"North Carolina",
789 "ND" =>
"North Dakota",
793 "PA" =>
"Pennsylvania",
794 "RI" =>
"Rhode Island",
795 "SC" =>
"South Carolina",
796 "SD" =>
"South Dakota",
802 "WA" =>
"Washington",
803 "WV" =>
"West Virginia",
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]);
827 list($H, $S, $L) = array_values(self::RgbToHsl($R, $G, $B));
829 # adjust luminance and saturation 830 $L = $L + ($L * ($LAdjust / 100));
831 $S = $S + ($S * ($SAdjust / 100));
834 extract(self::HslToRgb($H, $S, $L));
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);
840 $NewColor =
"#".$FormFunc($R).$FormFunc($G).$FormFunc($B);
842 # return new color to caller 858 # retrieve all constants for class 859 if (is_object($ClassName))
861 $ClassName = get_class($ClassName);
863 $Reflect =
new ReflectionClass($ClassName);
864 $Constants = $Reflect->getConstants();
867 foreach ($Constants as $CName => $CValue)
869 # if value matches and prefix (if supplied) matches 870 if (($CValue == $Value)
871 && (($Prefix === NULL) || (
strpos($CName, $Prefix) === 0)))
873 # return name to caller 878 # report to caller that no matching constant was found 890 $Hex = preg_replace(
'/[^a-fA-F0-9]/',
"", $Hex);
894 $Color[
"R"] = hexdec($Hex{0}.$Hex{1});
895 $Color[
"G"] = hexdec($Hex{2}.$Hex{3});
896 $Color[
"B"] = hexdec($Hex{4}.$Hex{5});
898 else if (
strlen($Hex) == 3)
900 $Color[
"R"] = hexdec($Hex{0});
901 $Color[
"G"] = hexdec($Hex{1});
902 $Color[
"B"] = hexdec($Hex{2});
916 # ---- PRIVATE INTERFACE ------------------------------------------------- 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",
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",
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",
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",
969 private static $IrregularWords = array(
974 'child' =>
'children',
979 private static $UncountableWords = array(
998 private static function CallMbStringFuncIfAvailable(
1003 if (function_exists(
"mb_".$Func))
1005 $FuncToCall =
"mb_".$Func;
1009 if (count($Args) > $NumPlainArgs)
1011 throw new Exception(
1012 "Function mb_".$Func.
"() required but not available.");
1014 $FuncToCall = $Func;
1016 return call_user_func_array($FuncToCall, $Args);
1030 private static function RgbToHsl($R, $G, $B)
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);
1037 # scale RGB values to range of 0-1 1042 # determine RGB range 1043 $MinVal = min($R, $G, $B);
1044 $MaxVal = max($R, $G, $B);
1045 $MaxDif = $MaxVal - $MinVal;
1047 # calculate luminance 1048 $L = ($MinVal + $MaxVal) / 2.0;
1051 # if RGB are all equal 1052 if ($MinVal == $MaxVal)
1054 # no hue or saturation 1060 # calculate saturation 1063 $S = $MaxDif / ($MinVal + $MaxVal);
1067 $S = $MaxDif / ((2.0 - $MaxVal) - $MinVal);
1075 $H = ($G - $B) / $MaxDif;
1079 $H = ($B - $R) / $MaxDif;
1084 $H = ($R - $G) / $MaxDif;
1095 # return calculated values to caller 1096 return [
"H" => $H,
"S" => $S,
"L" => $L ];
1109 private static function HslToRgb($H, $S, $L)
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);
1116 # scale HSL to range of 0-1 1124 # result is greyscale, with equal RGB values based on luminance 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;
1137 # return calculated values to caller 1138 return [
"R" => $R,
"G" => $G,
"B" => $B ];
1151 private static function HslToRgbComponent($H, $S, $L)
1153 # ensure hue is in the range 0-1 1154 if ($H < 0) { $H += 1; }
1155 elseif ($H > 1) { $H -= 1; }
1157 # calculate saturation/luminance adjustments 1160 $Adj1 = (1 + $S) * $L;
1164 $Adj1 = ($S + $L) - ($S * $L);
1166 $Adj2 = ($L * 2) - $Adj1;
1168 # calculate RGB component and return it to caller 1171 return (($Adj1 - $Adj2) * $H * 6) + $Adj2;
1173 elseif (($H * 2) < 1)
1177 elseif (($H * 3) < 2)
1179 return (($Adj1 - $Adj2) * ((2 / 3) - $H) * 6) + $Adj2;
static AdjustHexColor($Color, $LAdjust, $SAdjust=0)
Adjust hexadecimal RGB color by specified amount.
static strrpos()
Multibyte-aware (if supported in PHP) version of strrpos().
static CheckMyCaller($DesiredCaller, $ExceptionMsg=NULL)
Check the caller of the current function.
static SortCompare($A, $B)
Perform compare and return value appropriate for sort function callbacks.
static ZipCodeDistance($ZipA, $ZipB)
Compute the distance between two US ZIP codes.
static EncodeStringForCdata($String)
Encode string to be written out in XML as CDATA.
static strpos()
Multibyte-aware (if supported in PHP) version of strpos().
static GetMyCaller()
Get string with file and line number for call to current function.
static Pluralize($Word)
Pluralize an English word.
static GetLatLngForZipCode($Zip)
Look up the GPS coordinates for a US ZIP code.
Standard utility library.
static ArrayPermutations($Items, $Perms=array())
Return all possible permutations of a given array.
static substr()
Multibyte-aware (if supported in PHP) version of substr().
static GetBacktraceAsString($IncludeArgs=TRUE)
Get backtrace as a string.
static GetConstantName($ClassName, $Value, $Prefix=NULL)
Get name (string) for constant.
static ComputeGreatCircleDistance($LatSrc, $LonSrc, $LatDst, $LonDst)
Computes the distance in kilometers between two points, assuming a spherical earth.
static ComputeBearing($LatSrc, $LonSrc, $LatDst, $LonDst)
Computes the initial angle on a course connecting two points, assuming a spherical earth...
const SQL_DATE_FORMAT
Format to feed to date() to get SQL-compatible date/time string.
static NeatlyTruncateString($String, $MaxLength, $BreakAnywhere=FALSE)
Attempt to truncate a string as neatly as possible with respect to word breaks, punctuation, and HTML tags.
static HexToRgba($Hex, $Opacity=1)
Convert a hex color string (e.g., #FF00FF") to a css 'rgba(' format color.
static strlen()
Multibyte-aware (if supported in PHP) version of strlen().
static Singularize($Word)
Singularize an English word.
static GetUsStatesList()
Get an array of US state names with their two-letter abbreviations as the index.
static GetCallerInfo($Element=NULL)
Get info about call to current function.