From 7ad01a51ba2c1e11483df49e8479eba5acef4838 Mon Sep 17 00:00:00 2001 From: asus Date: Sun, 31 Mar 2024 17:34:10 +0200 Subject: [PATCH] more improves in optin gestion in class, automatic handling for basic cases --- plugins/cipf_plugin/html/menu/cipf_menu.html | 25 - .../php/admin_menu/menu_content.php | 2 +- plugins/xtxpatch/html/admin_menu.html | 50 + .../xtxpatch/php/classes/plgntls_class.php | 124 +- .../xtxpatch/php/classes/xtxpatch_class.php | 15 +- plugins/xtxpatch/php/menu/admin_menu.php | 46 +- ç | 1327 +++++++++++++++++ 7 files changed, 1531 insertions(+), 58 deletions(-) create mode 100644 plugins/xtxpatch/html/admin_menu.html create mode 100644 ç diff --git a/plugins/cipf_plugin/html/menu/cipf_menu.html b/plugins/cipf_plugin/html/menu/cipf_menu.html index 7113318..982570d 100644 --- a/plugins/cipf_plugin/html/menu/cipf_menu.html +++ b/plugins/cipf_plugin/html/menu/cipf_menu.html @@ -55,31 +55,6 @@ - -

email a la creation de compte

-
- - - -
- - -
- -
- /> - -
- -
- /> - -
- - -
- diff --git a/plugins/cipf_plugin/php/admin_menu/menu_content.php b/plugins/cipf_plugin/php/admin_menu/menu_content.php index 0b5f6bb..ae29d9e 100644 --- a/plugins/cipf_plugin/php/admin_menu/menu_content.php +++ b/plugins/cipf_plugin/php/admin_menu/menu_content.php @@ -32,10 +32,10 @@ function add_plugin_content_CIPF() { /* * registration email * - */ $admin_post_payment_messages = Plgntls::ADMIN_POST_PAYMENT_MESSAGES; $nonce_payment_messages = Plgntls::ADMIN_MENU_NONCE_PAYMENT_MESSAGES; $payment_messages = get_payment_messages_option_CIPF(); + */ ob_start(); include(Plgntls::root_path() . '/html/menu/cipf_menu.html'); diff --git a/plugins/xtxpatch/html/admin_menu.html b/plugins/xtxpatch/html/admin_menu.html new file mode 100644 index 0000000..a8f295c --- /dev/null +++ b/plugins/xtxpatch/html/admin_menu.html @@ -0,0 +1,50 @@ + + + +

email a la creation de compte

+ +
+ + +
+ + + + + + diff --git a/plugins/xtxpatch/php/classes/plgntls_class.php b/plugins/xtxpatch/php/classes/plgntls_class.php index 5a5b1ef..65f6a4f 100644 --- a/plugins/xtxpatch/php/classes/plgntls_class.php +++ b/plugins/xtxpatch/php/classes/plgntls_class.php @@ -487,9 +487,7 @@ class Plgntls_xtx { if ($file->ext === 'html') include($file->path); } - $html = ob_get_clean(); - - return $html; + return ob_get_clean(); } /* @@ -883,11 +881,8 @@ class Plgntls_xtx { * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - const SLUG_TOOGLE_ADMIN_MENU = "toggle_admin_menu_url_xtxpatch"; - const ACTION_TOOGLE_ADMIN_MENU = "toggle_admin_menu_url_xtxpatch"; const OPTION_TOGGLE_MENU = [ '_name'=>'toggle_admin_menu_option_xtxpatch', - '_callback'=>__CLASS__.'::toggle_plugin_menu', '_default'=>'hide', 'show'=>'show', 'hide'=>'hide', @@ -919,7 +914,7 @@ class Plgntls_xtx { $menu_options = array('callback'=>$menu_options); } - self::_create_menu($options); + self::_create_menu($menu_options); } /* @@ -1004,18 +999,13 @@ class Plgntls_xtx { * handle the toggle menu when url is reached * */ - public static function toggle_plugin_menu($request, $option_name) { - error_log("inside toggle_plugin_menu"); - + public static function toggle_plugin_menu($request, $option_name, $option_value) { if ($request[$option_name] === 'show') { update_option($option_name, 'show'); } else if ($request[$option_name] === 'hide') { update_option($option_name, 'hide'); } - - self::redirect_menu_referer(); - exit; } @@ -1077,12 +1067,38 @@ class Plgntls_xtx { public static function get_option_safe($option) { /* * first init option, in case it was not already + * it will returns the option name or false + * it also works if the option is the name directly * */ - if (false === self::_init_option($option)) { + $option_name = self::_init_option($option); + if (false === $option_name) { return; } - return get_option($option['_name']); + + $option_data = get_option($option_name); + if (!is_string($option_data)) { + $option_data = serialize($option_data); + } + return $option_data; + } + + public static function update_option_safe($option, $option_data) { + /* + * first init option, in case it was not already + * it will returns the option name or false + * it also works if the option is the name directly + * + */ + $option_name = self::_init_option($option); + if (false === $option_name) { + return; + } + + if (!is_string($option_data)) { + $option_data = serialize($option_data); + } + update_option($option_name, $option_data); } public static function get_option_link_href($option_name) { @@ -1109,11 +1125,12 @@ class Plgntls_xtx { if (false === $option_data) { return null; } - $form = '
'; - $form .= ''; - $form .= ''; - $form .= wp_nonce_field($option_data["_nonce_action"], $option_data["_nonce_name"], true, true); - return $fields; + ob_start(); + echo ''; + echo ''; + echo ''; + wp_nonce_field($option_data["_nonce_action"], $option_data["_nonce_name"], true, true); + return ob_get_clean(); } @@ -1136,8 +1153,19 @@ class Plgntls_xtx { * if the option was retrieve in the database, * there is no need to check it again : special field _db = null * + * returns the option name if ok, or false + * */ private static function _init_option($option) { + if (is_string($option)) { + if (self::_is_in_list_option($option)) { + return $option; + } + else { + return false; + } + } + if (!isset($option['_name'])) { return false; } @@ -1145,7 +1173,7 @@ class Plgntls_xtx { return false; } if (isset($option['_db'])) { - return true; + return $option['_name']; } /* @@ -1164,14 +1192,14 @@ class Plgntls_xtx { * if you use another action, it will not trigger the class handler function * - _nonce_action * - _nonce_name - * - _callback + * - _callback, default * */ $default_option = array( '_action'=>self::$_options_action, '_nonce_action'=>'nonce_action_'.$name, '_nonce_name'=>'nonce_name_'.$name, - '_callback'=>'', + '_callback'=>__CLASS__.'::default_handle_admin_post_option', ); foreach ($default_option as $key => $value) { if (!isset($option[$key])) { @@ -1194,7 +1222,13 @@ class Plgntls_xtx { $options_unserialized[$name] = $option; update_option(self::$_options_list, serialize($options_unserialized), '', 'no'); - return true; + return $name; + } + + private static function _is_in_list_option($option_name) { + $options_serialized = get_option(self::$_options_list); + $options_unserialized = unserialize($options_serialized); + return isset($options_unserialized[$option_name]); } @@ -1223,6 +1257,7 @@ class Plgntls_xtx { $option_data['_nonce_action'], $option_data['_callback']) ) { + error_log("there has been a problem, the option data don't contains the necessary informations (request: " . json_encode($_REQUEST) . ")(option data: " . json_encode($option_data)); self::redirect_menu_referer(); exit; } @@ -1240,12 +1275,49 @@ class Plgntls_xtx { /* * if nonce passed, call the callback * - with the remaining of the request - * - and the option name + * - the option name + * - and the option value * */ - $nonce_callback($request, $option_name); + $option_value = self::get_option_safe($option_name); + if (false === $option_value) { + self::redirect_menu_referer(); + exit; + } + $nonce_callback($request, $option_name, $option_value); + + /* + * then redirects + * + */ + self::redirect_menu_referer(); + exit; } + public static function default_handle_admin_post_option($request, $option_name, $option_data) { + /* + * in case the option only contains one value, just update it + * + */ + if (is_string($option_data)) { + self::update_option_safe($option_name, $request[$option_name]); + return; + } + + /* + * else, assuming it's a 1 dimension array + * update each values + * + */ + foreach ($option_data as $key) { + if (isset($request[$key])) { + $option_data[$key] = $request[$key]; + } + } + self::update_option_safe($option_name, $option_data); + } + + public static function redirect_menu_referer() { if (wp_get_referer()) { wp_safe_redirect(wp_get_referer()); diff --git a/plugins/xtxpatch/php/classes/xtxpatch_class.php b/plugins/xtxpatch/php/classes/xtxpatch_class.php index 72aa58c..e997430 100644 --- a/plugins/xtxpatch/php/classes/xtxpatch_class.php +++ b/plugins/xtxpatch/php/classes/xtxpatch_class.php @@ -16,9 +16,18 @@ if (!defined('ABSPATH')) { * */ class Xtxpatch { - const SLUG_TOOGLE_ADMIN_MENU = ['_name'=>'toogle_admin_menu_url_xtxpatch', 'toggle'=>'toggle', 'show'=>'show', 'hide'=>'hide']; - const OPTION_TOGGLE_MENU = ['_name'=>'toggle_admin_menu_option_xtxpatch', 'show'=>'show', 'hide'=>'hide']; - + const OPTION_REGISTER_EMAIL = [ + '_name'=>'define_email_at_register_xtxpatch', + '_callback'=>__NAMESPACE__.'\define_register_email', + '_default'=>[ + 'email'=> +'Bonjour, +Vous venez de créer un compte sur le site carteprof.org avec l’identifiant : $$user_login$$ +La FIPF', + 'is_email_prof'=>true, + 'is_email_partner'=>true, + ], + ]; } ?> diff --git a/plugins/xtxpatch/php/menu/admin_menu.php b/plugins/xtxpatch/php/menu/admin_menu.php index d2f5943..6937538 100644 --- a/plugins/xtxpatch/php/menu/admin_menu.php +++ b/plugins/xtxpatch/php/menu/admin_menu.php @@ -33,13 +33,53 @@ add_action('admin_menu', __NAMESPACE__.'\add_plugin_menu'); * */ function menu_content() { - echo '

hello

'; + $option_register_email = Xtxpatch::OPTION_REGISTER_EMAIL; + + $option_register_email_name = $option_register_email['_name']; + $option_register_email_value = \Plgntls_xtx::get_option_safe($option_register_email); + + ob_start(); + include(\Plgntls_xtx::root_path() . 'html/admin_menu.html'); + echo ob_get_clean(); +} + + + + +function define_register_email($request, $option_name, $option_value) { + /* + * email + * + */ + $email = ''; + if (isset($request['email'])) { + $option_value['email'] = $request['email']; + } + + /* + * is email ? + * + $is_email_prof = false; + if (isset($post['is_email_prof']) && $post['is_email_prof'] === 'on') { + $is_email_prof = true; + } + $is_email_partner = false; + if (isset($post['is_email_partner']) && $post['is_email_partner'] === 'on') { + $is_email_partner = true; + } + */ + + + /* + * update the option with new values + * + */ +// Plgntls_xtx::update_option_safe($option_name, $email); + } - - ?> diff --git a/ç b/ç new file mode 100644 index 0000000..5a52b6d --- /dev/null +++ b/ç @@ -0,0 +1,1327 @@ + 'path/to/style2.css', // -> depends on style1.css +* 'http://my_url1.com', // +* 'script1_js' => 'path/to/script2.js', // -> depends on script1.js +* 'path/to/file1.html', // | will be returned +* 'path/to/file2.html', // -> | as expanded html +* 'path/to/file3.html', // | in the order included +* 'handle_name' => 'path/to/script3.js', // -> depends on the script with handle 'handle_name' +* array( // +* 'path/to/script4.js', // | add a script with attributes +* 'attribute_1' => 'value_1', // -> | first element of array must be +* 'attribute_2' => 'value_2', // | the script, without explicit key +* ), // +* array( 'js' => 'var_js' ), // -> add inline js, only first element will be used +* array( 'css' => 'var_css' ), // -> add inline css, only first element will be used +* ), +* compact( // these variables are added to html and js files +* 'var_1', // - in html files you can access them directly +* 'var_2', // - in js files they are properties of object PLGNTLS_data +* 'var_3', // like PLGNTLS_data.var_1; +* ) +* ); +* +*/ + +class Plgntls_xtx { + /* + * 0 : dont output + * 1 : output only level 1 - not always on top of function, output default, only prevent if specifically level 2 + * typical for early hooks like 'init' or 'wp', where there is a first check to see if you should 'enter' in this function, level 1 will be present after thoses checks + * 2 : output everything + * + */ + private static $_DEBUG_INFOS = 0; + + private static $_instance_count = 0; + private static $_adding_count = 0; + private static $_init_count = 0; + + private $_first_script = null; + private $_first_style = null; + private $_js_dependencies = array(); + private $_css_dependencies = array(); + private $_scripts_attributes = array(); + + private static $_prefix = null; + private static $_options_action = null; + private static $_options_list = null; + + /* + */ + public function __construct() { + ++self::$_instance_count; + self::init_class(); + } + + + + + + + + + public static function init_class() { + if (self::$_init_count > 0) { + return; + } + ++self::$_init_count; + + self::$_prefix = strtoupper(__CLASS__); + + /* + * 'set paths and urls' init + * + */ + self::_init_root_dir(); + + + /* + * 'debug logs' init + * + */ + + + /* + * 'add to front' init + * + */ + + + /* + * 'add menu' init + * + */ + add_filter("plugin_action_links_".self::$_plugin_dir."/".self::$_main_file, function($links) { + return self::_add_link_to_plugin($links); + }); + + + /* + * 'handle options' init + * + */ + self::$_options_action = 'action_for_admin_post_options_'.self::$_prefix; + self::$_options_list = 'list_of_options_for_this_plugin_'.self::$_prefix; + if (false === get_option(self::$_options_list)) { + add_option(self::$_options_list, '', '', 'no'); + } + add_action('admin_post_'.self::$_options_action, function() { + self::_handle_admin_post_option(); + }); + } + + + + + + + + + +/* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* ( +* ACTIONS FUNCTIONS +* +* 1. set paths and urls +* 2. debug logs +* 3. add to front +* 4. add menu +* 5. handle options +* +* END{ 5{ 4{ 3{ 2{ 1{ +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +*/ + + + /* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * 1 * + * * * * * * * + *})( set paths and urls * + * * * * * * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + + private static $_plugin_dir_path; + private static $_plugin_dir; + private static $_file_dir_path; + private static $_file_name; + private static $_main_file; + private static $_root_path; + private static $_root_url; + + + /* + * ex: + * /home/www-data/cipf_plugin/php/test/test2/test3/test.php + * _plugin_dir_path /home/www-data + * _plugin_dir cipf_plugin + * _file_dir_path php/test/test2/test3 + * _file_name test.php + * _root_path /home/www-data/cipf_plugin/ + * _main_file main_file,php + * + * /home/www-data/cipf_plugin/test.php + * _plugin_dir_path /home/www-data + * _plugin_dir cipf_plugin + * _file_dir_path '' + * _file_name test.php + * _root_path /home/www-data/cipf_plugin/ + * _main_file main_file,php + * + */ + private static function _init_root_dir() { + if (isset(self::$_plugin_dir, self::$_file_name, self::$_file_dir_path, self::$_plugin_dir_path)) + return ; + /* + * it uses exploded_path_path by removing data from the array + * so order is important ! + * plugin_dir / path / to / file.php + * exploded [plugin_dir, path, to, file.php] + * plugin_dir plugin_dir [path, to, file.php] + * file_name [path, to] file.php + * file_dir_name path / to + */ + $exploded_plugin_path = explode('/', plugin_basename( __FILE__ )); + + self::$_plugin_dir = array_shift($exploded_plugin_path); + self::$_file_name = array_pop($exploded_plugin_path); + self::$_file_dir_path = implode('/', $exploded_plugin_path); + self::$_plugin_dir_path = str_replace('/'.plugin_basename(__DIR__).'/', '', plugin_dir_path(__FILE__)); + + // https://wordpress.stackexchange.com/questions/19900/how-to-get-main-plugin-theme-file + $plugin_data = get_plugins("/".self::$_plugin_dir); + self::$_main_file = array_keys($plugin_data)[0]; + + self::$_root_path = self::$_plugin_dir_path.'/'.self::$_plugin_dir.'/'; + self::$_root_url = plugins_url(self::$_plugin_dir.'/'); + } + public static function root_path() { + self::init_class(); + return(self::$_root_path); + } + public static function root_url() { + self::init_class(); + return(self::$_root_url); + } + + + + + + + + + + + + + /* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * 2 * + * * * * * * * + *})( debugs logs * + * * * * * * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + + public static function debug_infos($level = null) { + if (self::$_DEBUG_INFOS === 0) { + return; + } + else if (self::$_DEBUG_INFOS === 1 && $level === 2) { + return; + } + $trace = debug_backtrace(); + $function = $trace[1]['function']; + $file = $trace[0]['file']; + $line = $trace[0]['line']; + error_log("-debug: function '".$function."' (in ".$file.", line ".$line .')'); + } + + + + + + + + + + + + + + /* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * 3 * + * * * * * * * + *})( add to front * + * * * * * * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + + + + public static function add_to_front($srcs_arr = array(), $vars = array()) { + $instance = new self(); + return $instance->_add_to_front($srcs_arr, $vars); + } + + private function _add_to_front($srcs_arr, $vars) { + /* + * even if the function is called with no srcs + * add fetch, because it can be used just for that + */ + if (self::$_adding_count === 0) { + $this->_add_fetch($srcs_arr, $vars); + } + + $srcs = array(); + foreach($srcs_arr as $src_key => $src_value) { + $init = $this->_init_src($src_key, $src_value); + if ($init !== null) + $srcs[] = $init; + } + if (!is_null($srcs_arr)) { + $this->_add_srcs_to_front($srcs); + } + + if (!is_null($vars)) { + $this->_add_vars_to_front($vars); + } + $add_html = $this->_create_html($srcs, $vars); + self::$_adding_count += 1; + return $add_html; + } + + + /* + * for fetch, we add the script that contains the fetch function + * it's an inline script, but is made globally available by adding it to window + * + */ + private function _add_fetch(&$srcs_arr, &$vars) { + // add fetch script at end of scripts list + // it will try to be added at the first script anyway, + // but for that, the scripts must be already enqueued, + // hence adding it at the end of the array + $srcs_arr[] = array('js'=>$this->_fetch_script()); + // for the custom endpoints in rest api to work + // they need to have a nonce named 'wp_rest' + // see : https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/ + $fetch_nonce = array("fetch_nonce" => wp_create_nonce('wp_rest')); + $fetch_url = array("fetch_url" => get_site_url() . "/wp-json"); + $vars = array_merge($vars, $fetch_nonce); + $vars = array_merge($vars, $fetch_url); + } + private function _fetch_script() { + return " + if (typeof window !== 'undefined') { + window.PLGNTLS_fetch = function PLGNTLS_fetch(url, options = {}) { + url = PLGNTLS_data.fetch_url + url; + + options.headers = options.headers || {}; + options.headers['X-WP-Nonce'] = PLGNTLS_data.fetch_nonce; + + return fetch(url, options); + } + } + "; + } + + + /* + * @param two arguments : + * 1. html files to include in front + * - can be a string of 1 filename + * - or an array of strings of filenames + * ( https://stackoverflow.com/q/4747876/9497573 ) + * - it's probably better to only add 1 file, and let it include other files + * 2. list of variables to make available to this files + * - in the form of key => val + * - recommanded to do it with compact() + * ex: _create_html( "file.html", compact("var1","var2",) ); + * ex: _create_html( array("file1.html", "file2.html"), array("var1"=>"value") ); + * @return a string of html code + * + */ + private function _create_html($files = null, $vars = null) { + if (is_null($files)) + return null; + $plgn_dir = $this->root_path(); + if (!is_null($vars)) + extract($vars); + + // using ob_start() and ob_get_clean() + // allows to have php expansion inside the html loaded + // in opposition to the method file_get_contents() + // https://stackoverflow.com/a/4402045/9497573 + ob_start(); + foreach($files as $file) { + if ($file->ext === 'html') + include($file->path); + } + return ob_get_clean(); + } + + /* + * pass variables to js front as global variables + * @param array : list of key => value + * with the key being name of the variable, like this : + * 'my_var' => 'value', + * simpler way to do it is to use compact when calling the function : + * add_var_to_front(compact("var1", "var2", "var3")); + * @param string (optionnal) : name of first embended script that need these variables + * (it will be available to this script and all followings) + * this name is the filename + "_" + extension : + * init.js -> init_js + */ + private function _add_vars_to_front($vars_arr) { + if (is_null($vars_arr)) + return ; + + $object_name = $this->_prefix . "_data"; + $vars_json = json_encode($vars_arr); + // note : we need to use 'var' instead of 'let' or 'const', + // because their scope is restricted to the if statement + $front = " + if (typeof $object_name === 'undefined') { + var $object_name = $vars_json; + } + else { + Object.assign($object_name, $vars_json); + } + "; + + $handle = $this->_check_inline_handles('js'); + if (!is_null($handle)) { + wp_add_inline_script($handle, $front, 'before'); + } + else { + // in last ressort, but bad + echo ''; + } + } + + /* + * @param array : list of files : + * - with their path from root of their type of file (ex: from js/ to .js files) + * - and with their extension + * @param boolean + * - to add ajax script and variables + * - default to true + */ + private function _add_srcs_to_front($srcs_arr) { + //wp_enqueue_script(, /url/to/script, [depends on], version, defer? ); + //wp_enqueue_style( , /url/to/script, [depends on], version, media ); + + $previous_css_basename = ''; + $previous_js_basename = ''; + foreach ($srcs_arr as $src) { + if ($src->inline === "js") { + $this->_add_inline_script($src); + } + else if ($src->inline === "css") { + $this->_add_inline_style($src); + } + else if (in_array($src->ext, array("js", "url"))) { + $this->_add_script($src, $previous_js_basename); + $previous_js_basename = $src->handle; + } + else if ($src->ext === "css") { + $this->_add_style($src, $previous_css_basename); + $previous_css_basename = $src->handle; + } + } + + // https://developer.wordpress.org/reference/hooks/wp_script_attributes/ + // https://wordpress.stackexchange.com/questions/66843/attach-a-private-function-at-a-hook + add_filter( 'wp_script_attributes', fn($attr)=>$this->_add_attributes_to_script($attr), 10, 1 ); + + //uncomment to print all enqueued files, can be usefull + /* + global $wp_scripts; + error_log("wp_scripts->queue:"); + error_log(json_encode($wp_scripts->queue)); + global $wp_styles; + error_log("wp_styles->queue:"); + error_log(json_encode($wp_styles->queue)); + */ + } + private function _add_script($script, $previous_js_basename) { + if (is_null($this->_first_script)) { + $this->_first_script = $script->handle; + } + $depends_on = $this->_check_dependencies($script, $previous_js_basename); + if ($depends_on !== null) { + wp_enqueue_script( $script->handle, $script->url, $depends_on, $script->version, true); + } + } + private function _add_style($style, $previous_css_basename) { + if (is_null($this->_first_style)) { + $this->_first_style = $style->handle; + } + $depends_on = $this->_check_dependencies($style, $previous_css_basename); + if ($depends_on !== null) { + wp_enqueue_style( $style->handle, $style->url, $depends_on, $style->version, ''); + } + } + private function _add_inline_script($src) { + $handle = $this->_check_inline_handles('js', $src); + if (!is_null($handle)) { + wp_add_inline_script($handle, $src->src, 'before'); + } + else { + // in last ressort, only add the script inline, it's not ideal, + // but the only situation where it should not work is if another script is loaded before + // and it should not be the case otherwise the handle would not have returned true + echo ''; + } + } + private function _add_inline_style($src) { + $handle = $this->_check_inline_handles('css', $src); + if (!is_null($handle)) { + wp_add_inline_style($handle, $src->src); + } + else { + // in last ressort, cf script notes above + echo ''; + } + } + + private function _add_attributes_to_script($attr) { + if (empty($attr['id'])) { + return $attr; + } + $handle = $attr['id']; + if (isset($this->_scripts_attributes[$handle])) { + $script = $this->_scripts_attributes[$handle]; + } + else { + return $attr; + } + + foreach($script as $key => $value){ + $attr[$key] = $value; + } + unset($this->_scripts_attributes[$handle]); + + return $attr; + } + + private function _check_dependencies(&$src, $previous_basename) { + if (in_array($src->ext, array("js", "url"))) { + global $wp_scripts; + $already_enqueued = array_search($src->handle, $wp_scripts->queue); + if ($already_enqueued !== false) { + return null; + } + } + else if ($src->ext === "css") { + global $wp_styles; + $already_enqueued = array_search($src->handle, $wp_styles->queue); + if ($already_enqueued !== false) { + return null; + } + } + $depends_on = array(); + if (isset($src->depends) && $src->depends !== '') { + $depends_on[] = $src->depends; + } + if (isset($previous_basename) && $previous_basename !== '') { + $depends_on[] = $previous_basename; + } + + return $depends_on; + } + + private function _check_inline_handles($type = null, $src = null) { + /* + * first, try to simply get the explicit dependency + * + */ + $handle = null;; + if (!is_null($src)){ + $handle = $src->depends; + } + if (!empty($handle) && !is_null($handle)) { + return $handle; + } + + /* + * second, try to get the first enqueued src of this call to 'add_to_front()' + * and make it the dependency + * default to js + * + */ + if ($type === "js" || is_null($type)) { + $handle = $this->_first_script; + } + else if ($type === "css") { + $handle = $this->_first_style; + } + if (!empty($handle) && !is_null($handle)) { + return $handle; + } + + /* + * third, try to get the last enqueued src of the page + * https://www.php.net/manual/en/function.array-key-last.php + * + */ + if ($type === "js" || is_null($type)) { + global $wp_scripts; + $array = $wp_scripts->queue; + } + else if ($type === "css") { + global $wp_styles; + $array = $wp_styles->queue; + } + $last_key = array_key_last($array); + if (!is_null($last_key)) { + return $array[$last_key]; + } + + /* + * didn't find any handle to add the src to it + * + */ + return null; + } + + /* + * @param string : name of the file, with its path from its extension directory + * - from js/ root for .js files + * - from css/ root for .css files + * @return object / null : + * - null if file is not valid + * - or an object with all the necessary infos : + * 0. src : array("name.js", ...) -> "name.js" + * "name.js" -> "name.js" + * 1. ext : name.js -> "js" + * 2. basename : name.js -> "name" + * 3. handle : name.js -> "name_js" + * 4. url : url to file in wordpress + * 5. path : path to file in server + * 6. version : used to avoid browser caching + * 7. depends : string if depends on a handle, or '' + * 8. attr : associative array of html attribute like 'type'=>'module' + * 9. inline : array("js" => "name.js") -> js + * array("css" => "name.css") -> css + */ + private function _init_src($key, $value) { + if (empty($value)) + return null; + $src = (object)[ + 'src' => null, + 'ext' => null, + 'basename' => null, + 'handle' => null, + 'url' => null, + 'path' => null, + 'version' => null, + 'depends' => null, + 'attr' => null, + 'inline' => null, + ]; + + // 7. depends + if (is_string($key)) + $src->depends = $key; + + // 0. src + // 8. attr + // 9. inline + // first element of array is used, so must not be empty + // value => ['path/to/file', 'key1'=>'value1', 'key2'=>'value2'] + // src => 'path/to/file' + // attr => ['key1'=>'value1', 'key2'=>'value2'] + if (is_array($value)){ + $first_key = array_keys($value)[0]; + if (empty($value[$first_key])) + return null; + if ($first_key === 0) { // is a script file or url with attributes + $src->src = array_shift($value); + $src->attr = $value; + } + else if ($first_key === "js" || $first_key === "css") { // is an inline code + $src->src = $value[$first_key]; + $src->inline = $first_key; + return $src; // inline only needs 'depends', 'src' and 'inline' + } + } + else { + $src->src = $value; + $src->attr = null; + } + + // 1. ext + if(filter_var($src->src, FILTER_VALIDATE_URL)) + $src->ext = "url"; + else + $src->ext = pathinfo($src->src, PATHINFO_EXTENSION); + if (! in_array($src->ext, array("js", "css", "html", "url"))) + return null; + + // 2. basename + // 3. handle + // basename handle + // https://www.url.com/route -> www.url.com/route -> www_url_com_route + // path/to/script.js -> script.js -> script_js + if ($src->ext === "url") { + $url = parse_url($src->src); + $src->basename = $url['host'] . $url['path']; + } + else + $src->basename = pathinfo($src->src, PATHINFO_BASENAME); + $src->handle = "PLGNTLS_" . str_replace(array('.', '/'), "_", $src->basename); + + // 4. url + // 5. path + // 6. version + if ($src->ext === "url") { + $src->url = $src->src; + $src->path = null; + $src->version = null; + } + else { + $src->url = $this->root_url().$src->src; + $src->path = $this->root_path().$src->src; + $src->version = date("ymd-Gis", filemtime($src->path)); + } + + // if ext is 'js' or 'url' and attr is not empty + // also add to global variable to access in 'wp_script_attributes' filter + if ($src->ext === 'js' || $src->ext === 'url') { + if ($src->attr !== null) { + $this->_scripts_attributes[$src->handle.'-js'] = $src->attr; + } + } + + return $src; + } + + + + + + + + + + + + + + /* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * 4 * + * * * * * * * + *})( add menu * + * * * * * * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + + const OPTION_TOGGLE_MENU = [ + '_name'=>'toggle_admin_menu_option_xtxpatch', + '_callback'=>__CLASS__.'::toggle_plugin_menu', + '_default'=>'hide', + 'show'=>'show', + 'hide'=>'hide', + ]; + + + + /* + * options is the options needed to set a new menu + * see _create_menu below for more informations + * + */ + public static function add_menu($menu_options) { + /* + * will init the filter for the toggle + * + */ + self::init_class(); + + if (empty($menu_options)) { + return; + } + + /* + * if menu_options is only a string, we assume it is the callback + * + */ + if (is_string($menu_options)) { + $menu_options = array('callback'=>$menu_options); + } + + self::_create_menu($menu_options); + } + + /* + * triggered by filter "admin_menu" + * + * page_title -> (optional, default 'name') + * name -> (optional, default _plugin_dir) + * capability -> (optional, default 'manage_options') + * slug -> (optional, default 'name') + * callback -> required + * toggle -> (optional, default true) + * + */ + private static function _create_menu($menu_options) { + if (!isset($menu_options['name'])) { + $menu_options['name'] = self::$_plugin_dir; + } + $default = array( + 'page_title'=> $menu_options['name'], + 'name' => $menu_options['name'], + 'capability'=> 'manage_options', + 'slug' => $menu_options['name'], + 'callback' => $menu_options['callback'], + 'toggle' => true, + ); + foreach ($default as $key => $value) { + if (!isset($menu_options[$key])) { + $menu_options[$key] = $value; + } + } + + if (false === $menu_options['toggle']) { + add_menu_page($menu_options['page_title'], $menu_options['name'], $menu_options['capability'], $menu_options['slug'], $menu_options['callback']); + } + else { + self::_toggle_menu($menu_options['page_title'], $menu_options['name'], $menu_options['capability'], $menu_options['slug'], $menu_options['callback']); + } + } + private static function _toggle_menu($menu_page_title, $menu_title, $menu_capability, $menu_slug, $menu_callback) { + $toggle_menu = self::OPTION_TOGGLE_MENU; + + $toggle = self::get_option_safe($toggle_menu); + + if ($toggle === $toggle_menu['hide']) { + remove_menu_page($menu_slug); + } + else if ($toggle === $toggle_menu['show']) { + add_menu_page($menu_page_title, $menu_title, $menu_capability, $menu_slug, $menu_callback); + } + } + + /* + * add link under the plugin in the plugins admin page + * triggered by filter “plugin_action_links_{$plugin_file}” + * + */ + private static function _add_link_to_plugin($links) { + $option_toggle = self::OPTION_TOGGLE_MENU; + + $toggle = self::get_option_safe($option_toggle); + if (false === $toggle) { + return $links; + } + if (!in_array($toggle, ['hide', 'show'])) { + return $links; + } + $state = $toggle === 'show' ? 'hide' : 'show'; + + $link = "".$state." menu"; + $links[] = $link; + + return $links; + } + + + + + /* + * callback of the option toggle_menu + * handle the toggle menu when url is reached + * + */ + public static function toggle_plugin_menu($request, $option_name, $option_value) { + if ($request[$option_name] === 'show') { + update_option($option_name, 'show'); + } + else if ($request[$option_name] === 'hide') { + update_option($option_name, 'hide'); + } + } + + + + + + + + + + + + + + /* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * 5 * + * * * * * * * + *})( handle options * + * * * * * * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + + + + public static function get_option_safe($option) { + /* + * first init option, in case it was not already + * it will returns the option name or false + * it also works if the option is the name directly + * + */ + $option_name = self::_init_option($option); + if (false === $option_name) { + return; + } + + $option_value = get_option($option_name); + if (!is_string($option_value)) { + $option_value = serialize($option_value); + } + return $option_value; + } + + public static function get_option_link_href($option_name) { + $option_data = self::_get_option_data($option_name); + if (false === $option_data) { + return null; + } + $href = admin_url('admin-post.php'); + $href .= "?action=" . $option_data['_action']; + $href .= "&" . $option_data['_nonce_name'] . "=" . wp_create_nonce($option_data['_nonce_action']); + $href .= "&option_name=" . $option_data['_name']; + + return $href; + } + + /* + * this replaces the form opening tag + * it creates this tag with the appropriate action, + * and add 3 hidden inputs fields for form action and nonce + * + */ + public static function open_form_option($option_name, $method = "post") { + $option_data = self::_get_option_data($option_name); + if (false === $option_data) { + return null; + } + ob_start(); + echo ''; + echo ''; + echo ''; + wp_nonce_field($option_data["_nonce_action"], $option_data["_nonce_name"], true, true); + return ob_get_clean(); + } + + + private static function _get_option_data($option_name) { + if (!is_string($option_name)) { + return false; + } + + $options_serialized = get_option(self::$_options_list); + $options_unserialized = unserialize($options_serialized); + if (!isset($options_unserialized[$option_name])) { + return false; + } + + return $options_unserialized[$option_name]; + } + + /* + * a valid option_data must contains '_name' and '_default' at least + * if the option was retrieve in the database, + * there is no need to check it again : special field _db = null + * + * returns the option name if ok, or false + * + */ + private static function _init_option($option) { + if (is_string($option)) { + if (self::_is_in_list_option($option)) { + return $option; + } + else { + return false; + } + } + + if (!isset($option['_name'])) { + return false; + } + if (!isset($option['_default'])) { + return false; + } + if (isset($option['_db'])) { + return $option['_name']; + } + + /* + * if wp option does not already exists, just add it + * otherwise, get it and check the values + * + */ + $name = $option['_name']; + if (false === get_option($name)) { + add_option($name, $option['_default'], '', 'no'); + } + + /* + * checks all the default fields : + * - _action, default 'action_for_admin_post_options_'.self::_prefix + * if you use another action, it will not trigger the class handler function + * - _nonce_action + * - _nonce_name + * - _callback, default + * + */ + $default_option = array( + '_action'=>self::$_options_action, + '_nonce_action'=>'nonce_action_'.$name, + '_nonce_name'=>'nonce_name_'.$name, + '_callback'=>__CLASS__.'::default_handle_admin_post_option', + ); + foreach ($default_option as $key => $value) { + if (!isset($option[$key])) { + $option[$key] = $value; + } + } + + /* + * override saved option with new one + * or we could compare each fields and update them if needed + * - but is it not more heavy in computing actions ? + * also add the field '_db' equal to anything (null by default) + * to mark it as coming from this database + * and differentiate it from new options + * + */ + $options_serialized = get_option(self::$_options_list); + $options_unserialized = unserialize($options_serialized); + $option['_db'] = null; + $options_unserialized[$name] = $option; + update_option(self::$_options_list, serialize($options_unserialized), '', 'no'); + + return $name; + } + + private static function _is_in_list_option($option_name) { + $options_serialized = get_option(self::$_options_list); + $options_unserialized = unserialize($options_serialized); + return isset($options_unserialized[$option_name]); + } + + + private static function _handle_admin_post_option() { + if (!isset($_REQUEST)) { + return; + } + $request = $_REQUEST; + + /* + * get values from the request, and unset them : + * - name of the option -> to get the option_data + * then get the option data : + * - nonce infos -> to check the nonce (nonce_name and nonce_action) + * - callback + * with nonce name, get the nonce from the request, and check it + * + */ + unset($request['action']); + $option_name = $request['option_name']; + unset($request['option_name']); + // option data : + $option_data = self::_get_option_data($option_name); + if (!isset( + $option_data['_nonce_name'], + $option_data['_nonce_action'], + $option_data['_callback']) + ) { + error_log("there has been a problem, the option data don't contains the necessary informations (request: " . json_encode($_REQUEST) . ")(option data: " . json_encode($option_data)); + self::redirect_menu_referer(); + exit; + } + $nonce_name = $option_data['_nonce_name']; + $nonce_action = $option_data['_nonce_action']; + $nonce_callback = $option_data['_callback']; + // check the nonce + $nonce = $request[$nonce_name]; + unset($request[$nonce_name]); + if (!wp_verify_nonce($nonce, $nonce_action)) { + self::redirect_menu_referer(); + exit; + } + + /* + * if nonce passed, call the callback + * - with the remaining of the request + * - the option name + * - and the option value + * + */ + $option_value = self::get_option_safe($option_name); + if (false === $option_value) { + self::redirect_menu_referer(); + exit; + } + $nonce_callback($request, $option_name, $option_value); + + /* + * then redirects + * + */ + self::redirect_menu_referer(); + exit; + } + + public static function default_handle_admin_post_option($request, $option_name, $option_value) { + /* + * in case the option only contains one value, just update it + * + */ + if (is_string($option_value)) { + self::update_option_safe($option_name, $request[$option_name]); + return; + } + + /* + * else, assuming it's a 1 dimension array + * update each values + * + */ + foreach ($option_value as $key => $value) { + + } + } + + + public static function redirect_menu_referer() { + if (wp_get_referer()) { + wp_safe_redirect(wp_get_referer()); + exit; + } + else { + wp_safe_redirect(admin_url()); + exit; + } + } + + + + + + + + + /* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + *}) end + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ +} + + +?>