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"];
69 public static function CheckMyCaller($DesiredCaller, $ExceptionMsg = NULL)
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"] :
"";
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))
87 # if exception message supplied 88 if ($ExceptionMsg !== NULL)
90 # make any needed substitutions in exception message 105 $Class.
"::".$Function),
109 throw new Exception($Msg);
113 # report to our caller that their caller was not the desired one 118 # report to our caller that their caller was not the desired one 132 $TraceOpts = $IncludeArgs ? 0 : DEBUG_BACKTRACE_IGNORE_ARGS;
133 debug_print_backtrace($TraceOpts);
134 $TraceString = ob_get_contents();
137 # remove this function from backtrace entries 138 $TraceString = preg_replace(
139 "/^#0\s+".__METHOD__.
"[^\n]*\n/",
"", $TraceString, 1);
141 # renumber backtrace entries 142 $TraceString = preg_replace_callback(
"/^#(\d+)/m",
function($Matches)
144 return "#".($Matches[1] - 1);
148 # strip leading path off files names 149 $HomeDir = dirname($_SERVER[
"SCRIPT_FILENAME"]);
150 $TraceString = preg_replace(
"%".preg_quote($HomeDir,
"%").
"/%",
153 # return backtrace string to caller 164 # return word unchanged if singular and plural are the same 165 if (in_array(strtolower($Word), self::$UncountableWords))
170 # check for irregular singular forms 171 foreach (self::$IrregularWords as $Pattern => $Result)
173 $Pattern =
'/'.$Pattern.
'$/i';
174 if (preg_match($Pattern, $Word))
176 return preg_replace($Pattern, $Result, $Word);
180 # check for matches using regular expressions 181 foreach (self::$PluralizePatterns as $Pattern => $Result)
183 if (preg_match($Pattern, $Word))
185 return preg_replace($Pattern, $Result, $Word);
189 # return word unchanged if we could not process it 200 # return word unchanged if singular and plural are the same 201 if (in_array(strtolower($Word), self::$UncountableWords))
206 # check for irregular plural forms 207 foreach (self::$IrregularWords as $Result => $Pattern)
209 $Pattern =
'/'.$Pattern.
'$/i';
210 if (preg_match($Pattern, $Word))
212 return preg_replace($Pattern, $Result, $Word);
216 # check for matches using regular expressions 217 foreach (self::$SingularizePatterns as $Pattern => $Result)
219 if (preg_match($Pattern, $Word))
221 return preg_replace($Pattern, $Result, $Word);
225 # return word unchanged if we could not process it 239 $TagStrippedString = strip_tags(html_entity_decode($String));
241 # if the string contained no HTML tags, we can just treat it as text 242 if ($String == $TagStrippedString)
244 $Length = self::strlen($String);
246 # if string was short enough, we need not do anything 247 if ($Length <= $MaxLength)
252 # if BreakAnywhere is set, just chop at the max length 255 $BreakPos = $MaxLength;
257 # otherwise look for an acceptable breakpoint 260 $BreakPos = self::strrpos($String,
" ", -($Length - $MaxLength));
262 # if we couldn't find a breakpoint, just chop at max length 263 if ($BreakPos === FALSE)
265 $BreakPos = $MaxLength;
269 $Result = self::substr($String, 0, $BreakPos);
271 # tack on the ellipsis 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 279 # if there aren't enough non-whitespace displayed characters to 280 # exceed the max length, bail because we don't need to do 282 if (self::strlen(trim($TagStrippedString)) <= $MaxLength)
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 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);
304 # max length of an HTML Entity 305 $MaxEntityLength = 8;
307 # track how much we have displayed 308 $DisplayedLength = 0;
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
315 # start in the 'text state' 318 # iterate over all our tokens 319 foreach ($Tokens as $Token)
323 # text state handles words that will be displayed 327 # look for characters that can end a word 331 # if we've buffered up a word 334 # and if displaying that word exceeds 335 # our length, then we're done 336 if ($DisplayedLength + $BufLen > $MaxLength)
341 # otherwise, add the buffered word to our display 343 $DisplayedLength += $BufLen;
346 # buffer this character 350 # if it could have been the start of a tag or an entity, 351 # change state appropriately 354 $State = ($Token ==
"<") ? $S_MaybeTag :
359 # for characters that can't break a word, just buffer them 367 # MaybeTag state checks if a < began a tag or not 369 # tags start with alpha characters (like <b>) 370 # or a slash (like </b>) 371 if (ctype_alpha($Token) || $Token ==
"/")
373 # if this was a tag, output it, output it, 374 # clear our buffer, and move to the Tag state 375 $Result .= $Buffer.$Token;
382 # otherwise, check if displaying this character would 383 # exceed our length. bail if so 384 if ($DisplayedLength + 1 > $MaxLength)
388 # if not, output the characters, clear our buffer, 389 # move to the Text state 390 $Result .= $Buffer.$Token;
398 # Tag state processes the contents of a tag 400 # always output tag contents 403 # check if this is the beginning of a quoted string, 404 # changing state appropriately if so 405 if ($Token ==
"\"" || $Token ==
"'")
410 # if this is the end of the tag, go back to Text state 411 elseif ($Token ==
">")
417 # Quote state processes quoted attributes 419 # always output quote contents 422 # if we've found the matching quote character, 423 # return to the Tag state 424 if ($Token == $QuoteChar)
430 # MaybeEntity decides if we're enjoying an HTML entity 431 # or just an ampersand 437 # if it was a space, then we're not in an entity 438 # as they cannot contain spaces 441 # check if we should be fone 442 if ($DisplayedLength + $BufLen > $MaxLength)
446 # if not, output the buffer, clear it, and return to Text 448 $DisplayedLength += $BufLen;
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 ==
";")
463 # if this has been too many characters without a ; 464 # for it to be an entity, return to text 474 # tack on the ellipsis 477 # if our string contained HTML tags that we may need to close 478 if (preg_match_all(
"%<(/?[a-z]+)[^>]*>%", $Result, $Matches))
480 # pull out matches for the names of tags 481 $Matches = $Matches[1];
483 # build up an array of open tags 485 while ( ($Tag = array_shift($Matches)) !== NULL )
487 # if this was not a close tag, prepend it to our array 488 if (self::substr($Tag, 0, 1) !=
"/")
490 array_unshift($Tags, $Tag);
492 # if this tag is not self-closing, append it to our list of open tags 493 elseif (self::substr($Tag, -1) !=
"/")
495 # if this was a close tag, look to see if this tag was open 496 $Tgt = array_search(self::substr($Tag, 1), $Tags);
499 # if so, remove this tag from our list 505 # iterate over open tags, closing them as we go 506 while ( ($Tag = array_shift($Tags)) !== NULL)
508 $Result .=
"</".$Tag.
">";
522 return self::CallMbStringFuncIfAvailable(__FUNCTION__, func_get_args(), 3);
531 return self::CallMbStringFuncIfAvailable(__FUNCTION__, func_get_args(), 3);
540 return self::CallMbStringFuncIfAvailable(__FUNCTION__, func_get_args(), 3);
549 return self::CallMbStringFuncIfAvailable(__FUNCTION__, func_get_args(), 1);
562 return "<![CDATA[".str_replace(
"]]>",
"]]]]><![CDATA[>", $String).
"]]>";
580 return ($A < $B) ? -1 : 1;
598 static $ZipCache = array();
600 # if we don't have a cached value for this zip, look one up 601 if (!isset($ZipCache[$Zip]))
603 # try to open our zip code database 604 $FHandle = fopen(dirname(__FILE__).
"/StdLib--ZipCodeCoords.txt",
"r");
606 # if we couldn't open the file, we can't look up a value 607 if ($FHandle === FALSE)
609 throw new Exception(
"Unable to open zip code coordinates file");
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)
616 if ($Line[0] == $Zip)
618 $ZipCache[$Zip] = array(
619 "Lat" => $Line[1],
"Lng" => $Line[2]);
624 # if we've scanned the entire file and have no coords for 625 # this zip, cache a failure 626 if (!isset($ZipCache[$Zip]))
628 $ZipCache[$Zip] = FALSE;
632 # hand back cached value 633 return $ZipCache[$Zip];
646 $FirstPoint = self::GetLatLngForZipCode($ZipA);
647 $SecondPoint = self::GetLatLngForZipCode($ZipB);
649 # if we scanned the whole file and lack data for either of our 650 # points, return NULL 651 if ($FirstPoint === FALSE || $SecondPoint === FALSE)
656 return self::ComputeGreatCircleDistance(
657 $FirstPoint[
"Lat"], $FirstPoint[
"Lng"],
658 $SecondPoint[
"Lat"], $SecondPoint[
"Lng"]);
673 # See http://en.wikipedia.org/wiki/Great-circle_distance 675 # Convert it all to Radians 676 $Ps = deg2rad($LatSrc);
677 $Ls = deg2rad($LonSrc);
678 $Pf = deg2rad($LatDst);
679 $Lf = deg2rad($LonDst);
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));
702 # See http://mathforum.org/library/drmath/view/55417.html 704 # Convert angles to radians 705 $Ps = deg2rad($LatSrc);
706 $Ls = deg2rad($LonSrc);
707 $Pf = deg2rad($LatDst);
708 $Lf = deg2rad($LonDst);
710 return rad2deg(atan2(sin($Lf-$Ls)*cos($Pf),
711 cos($Ps)*sin($Pf)-sin($Ps)*cos($Pf)*cos($Lf-$Ls)));
725 $Result = array($Perms);
730 for ($Index = count(
$Items) - 1; $Index >= 0; --$Index)
734 list($Segment) = array_splice($NewItems, $Index, 1);
735 array_unshift($NewPerms, $Segment);
736 $Result = array_merge($Result,
737 self::ArrayPermutations($NewItems, $NewPerms));
756 "CA" =>
"California",
758 "CT" =>
"Connecticut",
760 "DC" =>
"District of Columbia",
773 "MA" =>
"Massachusetts",
776 "MS" =>
"Mississippi",
781 "NH" =>
"New Hampshire",
782 "NJ" =>
"New Jersey",
783 "NM" =>
"New Mexico",
785 "NC" =>
"North Carolina",
786 "ND" =>
"North Dakota",
790 "PA" =>
"Pennsylvania",
791 "RI" =>
"Rhode Island",
792 "SC" =>
"South Carolina",
793 "SD" =>
"South Dakota",
799 "WA" =>
"Washington",
800 "WV" =>
"West Virginia",
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]);
824 extract(self::RgbToHsl($R, $G, $B));
826 # adjust luminance and saturation 827 $L = $L + ($L * ($LAdjust / 100));
828 $S = $S + ($S * ($SAdjust / 100));
831 extract(self::HslToRgb($H, $S, $L));
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);
837 $NewColor =
"#".$FormFunc($R).$FormFunc($G).$FormFunc($B);
839 # return new color to caller 855 # retrieve all constants for class 856 if (is_object($ClassName))
858 $ClassName = get_class($ClassName);
860 $Reflect =
new ReflectionClass($ClassName);
861 $Constants = $Reflect->getConstants();
864 foreach ($Constants as $CName => $CValue)
866 # if value matches and prefix (if supplied) matches 867 if (($CValue == $Value)
868 && (($Prefix === NULL) || (
strpos($CName, $Prefix) === 0)))
870 # return name to caller 875 # report to caller that no matching constant was found 883 # ---- PRIVATE INTERFACE ------------------------------------------------- 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",
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",
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",
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",
936 private static $IrregularWords = array(
941 'child' =>
'children',
946 private static $UncountableWords = array(
965 private static function CallMbStringFuncIfAvailable($Func, $Args, $NumPlainArgs)
967 if (function_exists(
"mb_".$Func))
969 $FuncToCall =
"mb_".$Func;
973 if (count($Args) > $NumPlainArgs)
976 "Function mb_".$Func.
"() required but not available.");
980 return call_user_func_array($FuncToCall, $Args);
994 private static function RgbToHsl($R, $G, $B)
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);
1001 # scale RGB values to range of 0-1 1006 # determine RGB range 1007 $MinVal = min($R, $G, $B);
1008 $MaxVal = max($R, $G, $B);
1009 $MaxDif = $MaxVal - $MinVal;
1011 # calculate luminance 1012 $L = ($MinVal + $MaxVal) / 2.0;
1015 # if RGB are all equal 1016 if ($MinVal == $MaxVal)
1018 # no hue or saturation 1024 # calculate saturation 1027 $S = $MaxDif / ($MinVal + $MaxVal);
1031 $S = $MaxDif / ((2.0 - $MaxVal) - $MinVal);
1039 $H = ($G - $B) / $MaxDif;
1043 $H = ($B - $R) / $MaxDif;
1048 $H = ($R - $G) / $MaxDif;
1059 # return calculated values to caller 1060 return [
"H" => $H,
"S" => $S,
"L" => $L ];
1073 private static function HslToRgb($H, $S, $L)
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);
1080 # scale HSL to range of 0-1 1088 # result is greyscale, with equal RGB values based on luminance 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;
1101 # return calculated values to caller 1102 return [
"R" => $R,
"G" => $G,
"B" => $B ];
1115 private static function HslToRgbComponent($H, $S, $L)
1117 # ensure hue is in the range 0-1 1118 if ($H < 0) { $H += 1; }
1119 elseif ($H > 1) { $H -= 1; }
1121 # calculate saturation/luminance adjustments 1124 $Adj1 = (1 + $S) * $L;
1128 $Adj1 = ($S + $L) - ($S * $L);
1130 $Adj2 = ($L * 2) - $Adj1;
1132 # calculate RGB component and return it to caller 1135 return (($Adj1 - $Adj2) * $H * 6) + $Adj2;
1137 elseif (($H * 2) < 1)
1141 elseif (($H * 3) < 2)
1143 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 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.