-module(ephp_lib_info). -author('manuel@altenwald.com'). -behaviour(ephp_lib). -include_lib("stdlib/include/zip.hrl"). -include("ephp.hrl"). -export([init_func/0, init_config/0, init_const/0, phpinfo/3, phpversion/2, php_sapi_name/2, php_logo_guid/2, ini_get/3, ini_set/4, set_include_path/3, get_include_path/2, version_compare/5, extension_loaded/3, memory_get_usage/3, memory_get_peak_usage/3]). -define(INFO_GENERAL, 2#0000001). -define(INFO_CREDITS, 2#0000010). -define(INFO_CONFIGURATION, 2#0000100). -define(INFO_MODULES, 2#0001000). -define(INFO_ENVIRONMENT, 2#0010000). -define(INFO_VARIABLES, 2#0100000). -define(INFO_LICENSE, 2#1000000). -define(INFO_ALL, 2#1111111). -spec init_func() -> ephp_lib:php_function_results(). init_func() -> [{phpinfo, [{args, {0, 1, undefined, [{integer, ?INFO_ALL}]}}]}, php_sapi_name, php_logo_guid, phpversion, ini_get, ini_set, set_include_path, get_include_path, {version_compare, [{args, {2, 2, undefined, [string, string, {string, undefined}]}}]}, {extension_loaded, [{args, {1, 1, undefined, [string]}}]}, {memory_get_usage, [{args, {0, 1, undefined, [{boolean, false}]}}]}, {memory_get_peak_usage, [{args, {0, 1, undefined, [{boolean, false}]}}]}]. -spec init_config() -> ephp_lib:php_config_results(). init_config() -> []. -spec init_const() -> ephp_lib:php_const_results(). init_const() -> [{<<"PHP_VERSION">>, ?PHP_VERSION}, {<<"PHP_MAJOR_VERSION">>, <>}, {<<"PHP_MINOR_VERSION">>, <>}, {<<"PHP_RELEASE_VERSION">>, <>}, {<<"PHP_VERSION_ID">>, ?PHP_VERSION_ID}, {<<"PHP_EXTRA_VERSION">>, ?PHP_EXTRA_VERSION}, % Zend Thread Safety {<<"PHP_ZTS">>, 0}, {<<"PHP_DEBUG">>, 0}, % TODO: move to ephp_lib_file? {<<"PHP_MAXPATHLEN">>, 1024}, {<<"PHP_OS">>, get_os()}, {<<"PHP_OS_FAMILY">>, get_os_family()}, {<<"PHP_SAPI">>, ephp_config:get(sapi_type, <<"cli">>)}, {<<"PHP_EOL">>, get_os_eol()}, % fake: {<<"PHP_SHLIB_SUFFIX">>, <<"so">>}, % assert options: {<<"INI_USER">>, 1}, {<<"INI_PERDIR">>, 2}, {<<"INI_SYSTEM">>, 4}, {<<"INI_ALL">>, 7}, % phpinfo constants: {<<"INFO_GENERAL">>, ?INFO_GENERAL}, {<<"INFO_CREDITS">>, ?INFO_CREDITS}, {<<"INFO_CONFIGURATION">>, ?INFO_CONFIGURATION}, {<<"INFO_MODULES">>, ?INFO_MODULES}, {<<"INFO_ENVIRONMENT">>, ?INFO_ENVIRONMENT}, {<<"INFO_VARIABLES">>, ?INFO_VARIABLES}, {<<"INFO_LICENSE">>, ?INFO_LICENSE}, {<<"INFO_ALL">>, ?INFO_ALL}]. -spec phpinfo(ephp:context_id(), line(), var_value()) -> undefined. phpinfo(Context, Line, {_, Flags}) -> case php_sapi_name(Context, Line) of <<"cli", _/binary>> -> process_phpinfo("text", Context, Flags); _ -> process_phpinfo("html", Context, Flags) end. -spec phpversion(ephp:context_id(), line()) -> binary(). phpversion(_Context, _Line) -> ?PHP_VERSION. -spec php_sapi_name(ephp:context_id(), line()) -> binary(). php_sapi_name(_Context, _Line) -> ephp_config:get(sapi_type, <<"cli">>). -spec php_logo_guid(ephp:context_id(), line()) -> binary(). php_logo_guid(_Context, _Line) -> Content = get_file("bragful_logo.png"), Base64Content = base64:encode(Content), <<"data:image/png;base64,", Base64Content/binary>>. -spec ini_get(ephp:context_id(), line(), var_value()) -> mixed(). ini_get(_Context, _Line, {_, Key}) -> ephp_config:get(Key). -spec ini_set(ephp:context_id(), line(), var_value(), var_value()) -> binary(). ini_set(_Context, _Line, {_, Key}, {_, Value}) -> PrevVal = ephp_config:get(Key), ephp_config:set(Key, Value), PrevVal. -spec set_include_path(ephp:context_id(), line(), var_value()) -> binary(). set_include_path(_Context, _Line, {_, NewPath}) -> ephp_config:set(<<"include_path">>, NewPath), NewPath. -spec get_include_path(ephp:context_id(), line()) -> binary(). get_include_path(_Context, _Line) -> ephp_config:get(<<"include_path">>). -spec version_compare(ephp:context_id(), line(), Vsn1 :: var_value(), Vsn2 :: var_value(), Op :: var_value()) -> boolean() | integer(). version_compare(_Context, _Line, {_, Vsn1}, {_, Vsn2}, {_, undefined}) -> ephp_string:vsn_cmp(Vsn1, Vsn2); version_compare(_Context, _Line, {_, Vsn1}, {_, Vsn2}, {_, Op}) -> case {Op, ephp_string:vsn_cmp(Vsn1, Vsn2)} of {<<"<">>, -1} -> true; {<<"lt">>, -1} -> true; {<<"<=">>, I} when I =< 0 -> true; {<<"le">>, I} when I =< 0 -> true; {<<">">>, 1} -> true; {<<"gt">>, 1} -> true; {<<">=">>, I} when I >= 0 -> true; {<<"ge">>, I} when I >= 0 -> true; {<<"==">>, 0} -> true; {<<"=">>, 0} -> true; {<<"eq">>, 0} -> true; {<<"!=">>, I} when I =/= 0 -> true; {<<"<>">>, I} when I =/= 0 -> true; {<<"ne">>, I} when I =/= 0 -> true; _ -> false end. -spec extension_loaded(ephp:context_id(), line(), Name :: var_value()) -> boolean(). extension_loaded(_Context, _Line, {_, ModuleName}) -> Modules = lists:map(fun(M) -> case atom_to_binary(M, utf8) of <<"ephp_lib_", Module/binary>> -> Module; Module -> Module end end, ephp_config:get(modules, [])), lists:member(ModuleName, Modules). -spec memory_get_usage(ephp:context_id(), line(), RealUsage :: var_value()) -> pos_integer(). memory_get_usage(_Context, _Line, {_, false}) -> recon_alloc:memory(used, current); memory_get_usage(_Context, _Line, {_, true}) -> recon_alloc:memory(allocated, current). -spec memory_get_peak_usage(ephp:context_id(), line(), RealUsage :: var_value()) -> pos_integer(). memory_get_peak_usage(_Context, _Line, {_, false}) -> recon_alloc:memory(used, max); memory_get_peak_usage(_Context, _Line, {_, true}) -> recon_alloc:memory(allocated, max). %% ---------------------------------------------------------------------------- %% Internal functions %% ---------------------------------------------------------------------------- process_directives(Values) -> D = lists:foldl(fun process_directives/2, orddict:new(), Values), ephp_array:from_list([{K, ephp_array:from_list(V)} || {K, V} <- D]). process_directives({K, V}, Dict) -> case binary:split(K, <<".">>) of [K] -> safe_append(<<"core">>, {K, V}, Dict); [Section, _] -> safe_append(Section, {K, V}, Dict) end. safe_append(K, V, Dict) -> case orddict:is_key(K, Dict) of true -> orddict:append(K, V, Dict); false -> orddict:store(K, [V], Dict) end. phpinfo_metadata() -> OtpRelease = list_to_binary(erlang:system_info(otp_release)), IniFile = filename:dirname(?PHP_INI_FILE), Directives = ephp_config:get_all(), ArrDirectives = process_directives(Directives), Streams = ephp_array:from_list( ephp_stream:list_streams()), ephp_array:from_list([{<<"version">>, ?PHP_VERSION}, {<<"vsn">>, get_vsn()}, {<<"build_date">>, get_build_date()}, {<<"release">>, OtpRelease}, {<<"path_php_ini">>, IniFile}, {<<"directives">>, ArrDirectives}, {<<"streams">>, Streams}, {<<"tz_version">>, ephp_datetime:get_tz_version()}]). process_phpinfo(Format, Context, Flags) -> Metadata = phpinfo_metadata(), PHPInfo = get_file("phpinfo." ++ Format ++ ".php"), ephp:register_var(Context, <<"_METADATA">>, Metadata), ephp:register_var(Context, <<"_FLAGS">>, Flags), ephp:register_var(Context, <<"LICENSE">>, get_file("license")), {ok, false} = ephp:eval(Context, PHPInfo), undefined. get_file(Name, Filename) when Name =:= "rebar3" orelse Name =:= undefined -> FullFilename = filename:join( code:priv_dir(ephp), Filename), {ok, Content} = file:read_file(FullFilename), Content; get_file(Name, Filename) -> {ok, Sections} = escript:extract(Name, []), Zip = proplists:get_value(archive, Sections), Filter = fun (#zip_file{name = "ephp/priv/" ++ _}) -> true; (_) -> false end, {ok, Files} = zip:extract(Zip, [{file_filter, Filter}, memory]), NewFilename = "ephp/priv/" ++ Filename, proplists:get_value(NewFilename, Files). get_file(Filename) -> try Name = filename:basename( escript:script_name()), get_file(Name, Filename) catch error:{badmatch, []} -> get_file(undefined, Filename) end. -spec get_vsn() -> binary(). get_vsn() -> {ok, Vsn} = application:get_key(ephp, vsn), list_to_binary(Vsn). -spec get_build_date() -> binary(). -ifndef(BUILD_DATE). %% keep it only for some editors (like VSCode) -define(BUILD_DATE, "Aug 26 2019 13:57:10"). -endif. get_build_date() -> list_to_binary(?BUILD_DATE). -spec get_os() -> binary(). get_os() -> %% TODO complete the list of OS case os:type() of {unix, darwin} -> <<"Darwin">>; {unix, linux} -> <<"Linux">>; {unix, freebsd} -> <<"FreeBSD">>; {win32, nt} -> <<"WINNT">>; {win32, _} -> <<"Windows">> end. -spec get_os_family() -> binary(). get_os_family() -> %% TODO complete the list of OS families case os:type() of {unix, darwin} -> <<"OSX">>; {unix, linux} -> <<"LINUX">>; %% TODO Add more BSD {unix, freebsd} -> <<"BSD">>; %% TODO Add Solaris {win32, _} -> <<"WIN">>; {_, _} -> <<"unknown">> end. -spec get_os_eol() -> binary(). get_os_eol() -> %% TODO change this for something more coherent :-P <<"\n">>.