$wpdb->sanitize_key, $wpdb->sanitize_text_field y $wpdb->sanitize_textarea_field no existen!
Backend Frontend Template Pro: the WordPress Plugin Template
125,84€
The WordPress plugin template you need to create advanced plugins quickly, simple and professional. Menus, settings, automated listings, shortcodes, AJAX, and much more.
Save time and focus on developing your idea.
➜ 35% for Cyber Week!, Don't wait for the offer to run out!
Description
Get started now, it's not necessary to know all the WordPress plugin developments documentation because you won't need to know WordPress functions like add_menu_page, add_submenu_page, add_settings_section, add_settings_field, register_setting among others. Nor will it be necessary to reinvent the wheel by modifying the class WP_List_Table to be able to have a listing in WordPress because WP does not provide a direct way to have a listing in a plugin. It will also not be necessary to investigate how to make an AJAX form on the client side, how to do translations, how to use the WP settings system,… This WordPress plugin template makes it easy and explains these points step by step, unlike a simple WordPress boilerplate that only generates doubts and will make you waste too many hours
With this template/library you can develop your plugin right now just by following the installation, making the plugin yours, checking the content, and seeing the examples only of what you need
Guide
- Part 1: installing the plugin/template/library, create your administration pages and your own tags/shortcodes
- Part 2: create custom functions for your administration pages and find out how to check your data or errors in the WordPress log
- Part 3: save data without creating tables in the database, just using the WordPress settings system
Gallery
Download conditions
- No limit on the number of downloads
- Lifetime License
- From your download section you will be able to download the latest current version of the product
Only logged in customers who have purchased this product may leave a review.
Free version
BFT | BFT Pro | |
---|---|---|
Menu system | Yes | Yes |
Log manipulation | Yes | Yes |
Error handling | Yes | Yes |
Internalization | Yes | Yes |
Frontend system by shortcode | Yes | Yes |
Menu with nestable children | No | Yes |
System of settings/data stored by WP | No | Yes |
Ipunts system, including images and files processed by WP | No | Yes |
Sample database | No | Yes |
Menu of installation and desistalación of the database of the plugin | No | Yes |
Automated data manipulation of the plugin database | No | Yes |
Automated listings and forms through the plugin database | No | Yes |
Paginated listing by MySQL/Maria DB query | No | Yes |
Direct listing by array | No | Yes |
Manual form | No | Yes |
Download system for private files | No | Yes |
Iframe system | No | Yes |
PDF by iframe system | No | Yes |
Shortcode frontend system with AJAX | No | Yes |
Frontend form system by shortcode with AJAX response | No | Yes |
Documentation
Installation
Download the code and copy the folder “backend-frontend-template-pro” in your WordPress instalation/wp-content/plugins
Make the plugin yours
- Uninstall the sample database in Database / Installation/(De)install database explanation / Install/(De)install menu. You can reinstall the sample database later
- Change the folder and files of wp-content/plugins/backend-frontend-template-pro with the id of your plugin (like ‘your-plugin’)
- Delete the files or change their extension:
- wp-content/plugins/backend-frontend-template-pro/backend-frontend-template-pro.php
- wp-content/plugins/backend-frontend-template-pro/includes/class-bft.php
- Change the extension of the files to PHP:
- wp-content/plugins/backend-frontend-template-pro/your-plugin.txt
- wp-content/plugins/backend-frontend-template-pro/includes/class-your-plugin.txt
- Uncomment: “//$this->admin_pages_main_name = $this->plugin_title; on wp-content/plugins/backend-frontend-template-pro/admin/class-your-plugin-admin.php
- Find and replace all the file names with ‘your-plugin’ like ‘class-your-plugin.php’ ' to your plugin id, example: ‘class-a-plugin-name.php’.
- On wp-content/plugins/backend-frontend-template-pro/languages replace the file names with 'bft-internationalization’ to 'your-plugin', example: 'a-plugin-name-es_ES.mo’
- Go to search and replace of your editor, active 'match case’ and replace these strings:
- 'bft-internationalization’ to your-plugin
- '$plugin_slug = “bft_pro”‘ to $plugin_slug = “your_plugin”
- 'bft_shortcode’ to your_plugin_shortcode
- 'bft-shortcode’ to your-plugin-shortcode
- Go to search and replace of your editor, active “match case” and replace the words with the names and IDs of your plugin:
- Description of Your Plugin
- https://yourfuturewebsite/your-plugin/
- https://yourfuturewebsite
- Plugin Author
- pluginauthor@email
- Your Plugin
- your_plugin
- Your_Plugin
- your-plugin
- YOUR_PLUGIN
- Replace the icon for you own on wp-content/plugins/backend-frontend-template-pro/admin/img/icon-16px.png
Content
-
BFT Pro folders
- /admin -> administration folder
- /includes -> global folder, for administration and public files
- /languages -> translation files, these binaries are created with programs such as Poedit or EazyPo
- /private -> for sensitive data that only a download script can send the file to the user
- /admin -> public folder
-
BFT Pro sub Folders
- /css -> CSS files
- /img -> images
- /js -> JavaScript files
- /lib -> library, the internal BFT files are stored here
- /partials -> frontend files
-
Top BFT Pro Files (using the original filenames)
- /includes/class-your-plugin.txt -> main class of the plugin
- /includes/class-your-plugin-activator.php -> controls when the plugin is activated
- /includes/class-your-plugin-deactivator.php -> controls when the plugin is disabled
- /includes/class-your-plugin-cronjobs.php -> control the cronjobs of the plugin
- /includes/class-your-plugin-install-upgrade-deinstall-database.php -> install and remove the plugin database
- /includes/class-your-plugin-functions-admin-public.php -> class with functions for admin and public classes, it's an extension of the BFT admin-public class
- /admin/class-your-plugin-admin.php -> class for the admin section, it's an extension of the BFT admin-public class, your admin-public class and BFT admin class
- /public/class-your-plugin-public.php -> class for the public section, it's an extension of the BFT admin-public class, your admin-public class and the BFT public class
Pro menu
Designing a BFT menu looks like this:
$this->admin_pages = [ "hello_world" => [ "page_title" => $this->__("Hello world page"), "menu_title" => $this->__("Hello world"), "file" => "your-plugin-admin-display-hello-world.php", ], "blank_page" => [ "page_title" => $this->__("Blank page"), "menu_title" => $this->__("Blank page"), "file" => "bft-admin-display-blank-page-with-title.php", ], ];
BFT menu can have child pages, example:
$this->admin_pages = [ "hello_world" => [ "page_title" => $this->__("Hello world page"), "menu_title" => $this->__("Hello world"), "file" => "your-plugin-admin-display-hello-world.php", "children" => [ "blank_page" => [ "page_title" => $this->__("Blank page"), "menu_title" => $this->__("Blank page"), "file" => "bft-admin-display-blank-page-with-title.php", ], ] ], ];
Note: the array data is expanded by the function $this->admin_pages_prepare(), if you make a $this->debug_log_write($this->admin_pages) on a page: you can see the atual status of the array in the WordPress log
Explaining the BFT menu:
-
Automatic parameters added to the array
- id: the array key
- is_child: isset and true if child
- page_parent: isset with the key of the parent if it's a child
- ids_required_get_data: the GET data found within the ids 'ids_required’
- ids_optional_get_data: the GET data found within the ids 'ids_optional’
- ids_aux_required_get_data: the GET data found within the ids 'ids_aux_required’
- ids_aux_optional_get_data: the GET data found within the ids 'ids_aux_optional’
- ids_all: 'ids_required', 'ids_optional', 'ids_aux_required', 'ids_aux_optional’ all combined, this order is the priority for maintain the data
- ids_all_get_data: the GET data found within the ids 'ids_all’, uses the ‘key_final’ as ‘key’
- ids_all_get_data_excluded_zeros: ids_all_get_data sin los ids con get data == ‘0’
- ids_all_get_url: ids_all_get_data transformed on a URL GET string ‘&key=data’
- ids_all_get_data_excluded_zeros: ids_all_get_url sin los ids con get data == ‘0’
- ids_all_inverse: ids_all on inverse order, ex:
$page_data["ids_all"] = [ "key_initial" =>"key_final" ] -> $page_data["ids_all_inverse"] = [ "key_final" =>"key_initial" ]
- ids_required_all: 'ids_required', ‘ids_aux_required’ all combined, this order is the priority for maintain the data
- ids_required_all_get_data: the GET data found within the ids 'ids_required_all', uses the ‘key_final’ as ‘key’
- ids_required_all_get_data_excluded_zeros: ids_required_all_get_data without the ids with get data == ‘0’
- ids_optional_all: 'ids_optional', 'ids_aux_optional’ all combined, this order is the priority for maintain the data
- ids_optional_all_get_data: the GET data found within the ids 'ids_optional_all', uses the ‘key_final’ as ‘key’
- ids_principal_all: 'ids_required', ‘ids_optional’ all combined, this order is the priority for maintain the data
- ids_principal_all_get_data: the GET data found within the ids 'ids_optional_all', uses the ‘key_final’ as ‘key’
- ids_aux_all: 'ids_aux_required', 'ids_aux_optional’ all combined, this order is the priority for maintain the data
- ids_aux_all_get_data: the GET data found within the ids 'ids_aux_all', uses the ‘key_final’ as ‘key’
-
Parametters with default data if missing
- page_title: page title, by default: $this->admin_pages_page_title_default
- menu_title: page tab title, by default: $this->admin_pages_page_title_default
- menu_slug: page slug, by default: page key. The slug menu will be changed to: $this->admin_pages_slug_name_prefix.”_”.menu_slug because it's needed a unique page name among the plugins
- tab_show: if false does not display the page tab, even if the page is selected, by default: true
- tabs_new_or_edit_on_url: true it adds '&action=edit’ o '&action=new’ in the tab link. New if there are an id detected on the GET data, defaults false
- tabs_show_children: if false doesn't show the first line children tabs of a page, default True
- function: the function for when a page is displayed, by default: $this->admin_pages_function_default
- function_load: loads the function before a page is displayed,by default: $this->admin_pages_function_load_default
- file: the admin/partials file that will be displayed, default: $this->admin_pages_file_default (If the file starts with 'bft-‘ the file will be loaded of the folder admin/lib/BFT/partials
- error_throw_what_do, it's used on error_throw, options:
- show_error: shows the error (default option)
- show_error_and_die: show the error and stop the execution
- go_to_parent: go to the parent page and anotes on the GET data the error (only works if $triggered_on_function_load = true, because in a normal WordPress function it will cause the error: 'Cannot modify header information – headers already sent')
- error_throw_file_change: change the file option if error_throw_what_do is triggered, by default: false
- capability, by default: 'manage_options', WordPress capabilities: https://wordpress.org/support/article/roles-and-capabilities/
- IDs: they are data arrays with 'name on the GET data’ => 'id internal to use', default empty array
On the GET data it's needed to put a unique id, example: 'book_id', but on the page/function maybe it's needed like 'id', then use: 'book_id’ => 'id’ Options:- ids_required (throw an error if GET id not found,)
- ids_optional
- ids_aux_required (throw an error if GET id not found,, for pages with secondary data)
- ids_aux_optional (for pages with secondary data)
- Example:
"ids_optional" => [ "key_initial" => "key_final", "'key_get'" => "'key_needed'", "book_id" => "id", ],
- Note: if it's a children page there may be automatic ids: if a parent have an id or id aux that it's not found on the children page, then the children has the id like an id optional on ids_optional
-
Ready-to-use available functions (you can create whatever function you need)
- admin_menu_page_display: displays the selected page in 'File’
- admin_menu_page_table_display_first_menu_child: displays a table with the data of the first child and the form stored in $this->admin_forms[]
- admin_menu_page_table_display: displays a table with the data of the displayed page
- admin_menu_admin_form_page_display: displays a custom automated form stored in $this->admin_forms[]
-
Functions load available out of the box (you can create whatever function you need)
- redirect_to_first_page_child: redirects to the first child page of the current page (executes admin_permission_check_and_ids_required_check_function_load first)
- admin_permission_check_and_ids_required_check_function_load: checks the admin permissions and checks the required and optional ids specified in the URL, principal and aux ids type checked, prepared for 'function_load', 'go_to_parent’ used if needed
- ids_required_check_function_load: checks required and optional ids for the page specified in the URL, principal and aux ids type checked, prepared for 'function_load', 'go_to_parent’ used if needed
- download_file_private: download private file
- custom_form_set_do_by_post: send a form for automated data manipulation
-
Optional parameters
- admin_page_settings_id: for select the key to use in $this->admin_settings[], prepared for use with the function $this->settings_section_form($this->admin_pages_data_get(“admin_page_settings_id”)), that function is used in “file” => “bft-admin-display-settings.php”,
- iframe_url: the full URL of the inside iframe, for use with “file” => “bft-admin-display-iframe.php”
- iframe_admin_page_slug: the slug of the inside iframe, the iframe slug will be changed to: $this->admin_pages_slug_name_prefix.”_”.menu_slug, for use with “file” => “bft-admin-display-iframe.php”
- file_aux_folder_includes: for use a second full path with the variable $file_aux_url (parameter 'file’ generates the variable $file_url )
- new_text: the text of the new entry button, it can be added on the parent or on the child, the priority is on the child data, for use on the function admin_menu_page_table_display_first_menu_child() () and file bft-admin-display-list-table.php
it creates the variable $button_add and it will be finally used in $this->html_button_action
if new_text is boolean true it will be used the text: $this->__(“New”) - admin_forms_aux_id: for page with automatic data, used in the function: admin_pages_data_title_from_admin_forms_aux()
Displays the automatic name of the form (column_title_name data) before the data like this: Automatic name | Title
For the auto name is used the ids_required and ids_optional (only the first ID found), but only is used the key_initial (the key_get) - children: array with the children pages. A children page can has descent, the maximum levels are 5 counting the parent page. There is a limit because it is not recursive, that kills the performance of the server
- page_copy_of: copy the data of a page. Only copy the data not found on the page, neither copy id, is_child, page_parent, menu_slug and children”)?>
-
Your own parameters
- You can create your own parameters, later on you can access to the info on a function or on a page with:
$variable_name = $this->admin_pages_data_get(“parametter_name”);And if you want you can retrieve the data of a certain page with $page_name, and retrieve all the array data with $key = false
$variable_name = $this->admin_pages_data_get($key = false, $page_name = NULL) - You can set your own parameters by code with:
$this->admin_pages_data_set($key, $data, $page_name = NULL)
- You can create your own parameters, later on you can access to the info on a function or on a page with:
-
How to start
- Use the examples of the beginning, the majority of the options are not needed initially
- Except all the 'id_required'/'id_optional’ part, that's important, but only for child pages. You can see detailed examples on other pages where it is needed, because the idea is to have a page listed with all the entries, and then see a single item on the children, example:
$this->admin_pages = [ "books" => [ "page_title" => $this->__("List with all the books"), "menu_title" => $this->__("Books"), "function" => "admin_menu_page_table_display_first_menu_child", "file" => "bft-admin-display-list-table.php", "children" => [ "book" => [ "ids_required" => [ "book_id" => "id", ], "admin_forms_id" => "books", "page_title" => $this->__("Book selected"), "menu_title" => $this->__("Book"), "function" => "admin_menu_admin_form_page_display", "file" => "bft-admin-display-form.php", "function_load" => "custom_form_set_do_by_post", ], ] ], ];
In this quick example there is a page that shows all the books: admin.php?page=your_plugin_books , and each item/book links to admin.php?page=your_plugin_book&book_id=[id of the book]
And because 'book’ has 'ids_required', the page automatically throws error if a required GET data is not found, book_id in this case
A complex example of nested tabs:
his is a part of the menu used on the BFT default menu, more info on database -> Automated data manipulation
$this->admin_pages = [ "database" => [ "menu_title" => $this->__("Database"), "page_title" => $this->__("Database"), "file" => "bft-admin-display-menu-system.php", "function_load" => "redirect_to_first_page_child", "children" => [ "install_deinstall_upgrade_database_explication" => [...], "automated_data_manipulation_explication" => [ "menu_title" => $this->__("Automated data manipulation"), "page_title" => $this->__("Data managed by BFT"), "file" => "bft-admin-display-automated-data-manipulation-system.php", "children" => [ "automated_data_manipulation_courses" => [...], "automated_data_manipulation_students" => [...], "automated_data_manipulation_teachers" => [ "menu_title" => $this->__("Teachers"), "page_title" => $this->__("List of teachers"), "function" => "admin_menu_page_table_display_first_menu_child", "file" => "bft-admin-display-list-table.php", "children" => [ "automated_data_manipulation_teacher" => [ "ids_required" => [ "teacher_id" => "id", ], "admin_forms_id" => "teachers", "tabs_new_or_edit_on_url" => true, "menu_title" => $this->__("Teacher"), "page_title" => $this->__("Teacher form"), "function" => "admin_menu_admin_form_page_display", "file" => "bft-admin-display-form.php", "function_load" => "custom_form_set_do_by_post", "new_text" => $this->__("New teacher"), ], "automated_data_manipulation_teacher_notes" => [ "ids_required" => [ "teacher_id" => "teacher_id", ], "ids_aux_required" => [ "teacher_id" => "id", ], "admin_forms_aux_id" => "teachers", "menu_title" => $this->__("Teacher notes"), "page_title" => $this->__("List of notes"), "function" => "admin_menu_page_table_display_first_menu_child", "file" => "bft-admin-display-list-table.php", "children" => [ "automated_data_manipulation_teacher_note" => [ "ids_required" => [ "note_id" => "id", "teacher_id" => "teacher_id", ], "admin_forms_id" => "teachers_notes", "ids_aux_required" => [ "teacher_id" => "id", ], "admin_forms_aux_id" => "teachers", "tabs_new_or_edit_on_url" => true, "menu_title" => $this->__("Teacher note"), "page_title" => $this->__("Teacher note form"), "function" => "admin_menu_admin_form_page_display", "file" => "bft-admin-display-form.php", "function_load" => "custom_form_set_do_by_post", "new_text" => $this->__("New note"), ], ], ], ], ], ], ], ], ], ], ];
That generates this menu:
GET data: admin.php?page=bft_pro_automated_data_manipulation_explication
GET data: admin.php?page=bft_pro_automated_data_manipulation_teachersn
GET data: admin.php?page=bft_pro_automated_data_manipulation_teacher&action=new&teacher_id=0
GET data: admin.php?page=bft_pro_automated_data_manipulation_teacher&action=edit&teacher_id=1
GET data: admin.php?page=bft_pro_automated_data_manipulation_teacher_notes&teacher_id=1
GET data: admin.php?page=bft_pro_automated_data_manipulation_teacher_note&action=edit¬e_id=1&teacher_id=1
Settings/data system stored via WP
Edit your WordPress settings pages on the variable $this->admin_settings of the file admin/class-your-plugin-admin.php
On each page we describe the fields, and several pages can repeat the same fields
Designing BFT Settings looks like this:
$this->admin_settings = [ "general" => [ "title" => $this->__("Test settings"), "fields" => [ "text_test" => [ "title" => $this->__("Text input"), ], "number_test" => [ "title" => $this->__("Number input"), "args" => [ "type" => "number", ], ], ], ], ];
Explaining the BFT menu:
-
Settings pages
-
Automatic parameters added to the array
- id: the array key
- id_wordpress: $this->admin_pages_settings_prefix.”_”.array key
- page: id_wordpress.$this->admin_pages_settings_page_suffix
-
Parametters with default data if missing
- id: the array key
- function, by default: $this->settings_section_function_default
- fields, by default: settings_section_function_default
- title, by default: $this->admin_pages_settings_page_title_default
-
-
Page field
-
Automatic parameters added on the field array part
- id: the array key
- id_wordpress: $this->plugin_slug.”_”.the array key
-
Parametters with default data if missing
- title, by default: array key
- function, by default: settings_section_form_field()
- args, array where we define the data type, if it's multiple, the options for the select, etc. Default: “type” => “text”
"args" => [ "type" => "text" ],
-
Input types
- text: input text
"text_test" => [ "title" => $this->__("Text input"), ],
- textarea: input textarea, rows and cols are optionals
"textarea_test" => [ "title" => $this->__("Textarea input"), "args" => [ "type" => "textarea", "rows" => 10, "cols" => 50, ], ],
- number: input number, optional args: min, max and step. By defecation for step: 1, alternative: 'decimal’ or 'decimals', this option calculates the necessary steps
"number_test" => [ "title" => $this->__("Number input"), "args" => [ "type" => "number", "min" => 1, "max" => 99999, "step" => 1, ], ],
- select: input select, args must have 'options', it stores string wiht checked options, ex: 'en,es’ , optional args: search (defaults false), multiple (defaults false)
"select_test" => [ "title" => $this->__("Select input"), "args" => [ "type" => "select", "search" => true, "options" => [ "zh" => $this->__("Chinesse"), "en" => $this->__("English"), "es" => $this->__("Spanish"), ], ], ], "select_multiple_test" => [ "title" => $this->__("Select multiple input"), "args" => [ "type" => "select", "multiple" => true, "search" => true, "options" => [ "zh" => $this->__("Chinesse"), "en" => $this->__("English"), "es" => $this->__("Spanish"), ], ], ],
- date: input text with a calendar, the date will be MySQL style '2023-01-01’
"date_test" => [ "title" => $this->__("Date input"), "args" => [ "type" => "date", ], ],
- checkbox, single and multiple, in multiple is needed 'options’ on args
"checkbox_test" => [ "title" => $this->__("Checkbox input"), "args" => [ "type" => "checkbox", "multiple" => false, ], ], "checkbox_multiple_test" => [ "title" => $this->__("Checkbox multiple input"), "args" => [ "type" => "checkbox", "multiple" => true, "options" => [ "zh" => $this->__("Chinesse"), "en" => $this->__("English"), "es" => $this->__("Spanish"), ], ], ],
- radio: radio buttons, stores string wiht checked options, ex: 'en,es’, it requires 'options’
"radio_test" => [ "title" => $this->__("Radio input"), "args" => [ "type" => "radio", "multiple" => true, "options" => [ "zh" => $this->__("Chinesse"), "en" => $this->__("English"), "es" => $this->__("Spanish"), ], ], ],
- image: store a image on the WordPress Media Library, the data saved on settings is the WordPress ID of the file. Once selected shows the image
"image_test" => [ "title" => $this->__("Image input"), "args" => [ "type" => "image", ], ],
- file: store a file on the WordPress Media Library, the data saved on settings is the WordPress ID of the file. Once selected shows a download button
"file_test" => [ "title" => $this->__("File input"), "args" => [ "type" => "file", ], ],
- empty: a commented input, it prints ”
"empty_test" => [ "title" => $this->__("Input commented"), "args" => [ "type" => "empty", ], ],
- text: input text
-
-
How the fields are stored
- On the array a field ID is a simple sentence, ex: 'languages', 'currency', etc., but BFT stores it as: $this->plugin_slug.”_”.”field_id”, then it will be: 'your_plugin_languages', 'your_plugin_currency', etc.
- That's because WordPress doesn't isolate the settings by plugins, the plugin itself needs to isolate and secure the settings
- Knowing this, there are two access styles for updating and deleting data by code:
-
BFT Style
- $a_variable = $this->option_field_get(“field_id”)
- $this->update_option_field(“field_id”, “new string value”)
- $this->delete_option_field(“field_id”)
-
WordPress style
- $a_variable = get_option(“plugin_slug”.”_”.”field_id”);
- update_option(“plugin_slug”.”_”.”field_id”, “new string value”)
- delete_option(“plugin_slug”.”_”.”field_id”);
Note: use 'update’ to create a new entry
-
An example with all the input types:
$this->admin_settings = [ "input_types_test" => [ "title" => $this->__("Test settings"), "fields" => [ "text_test" => [ "title" => $this->__("Text input"), ], "textarea_test" => [ "title" => $this->__("Textarea input"), "args" => [ "type" => "textarea", "rows" => 10, "cols" => 50, ], ], "number_test" => [ "title" => $this->__("Number input"), "args" => [ "type" => "number", "min" => 1, "max" => 99999, "step" => 1, ], ], "select_test" => [ "title" => $this->__("Select input"), "args" => [ "type" => "select", "search" => true, "options" => [ "zh" => $this->__("Chinesse"), "en" => $this->__("English"), "es" => $this->__("Spanish"), ], ], ], "select_multiple_test" => [ "title" => $this->__("Select multiple input"), "args" => [ "type" => "select", "multiple" => true, "search" => true, "options" => [ "zh" => $this->__("Chinesse"), "en" => $this->__("English"), "es" => $this->__("Spanish"), ], ], ], "date_test" => [ "title" => $this->__("Date input"), "args" => [ "type" => "date", ], ], "checkbox_test" => [ "title" => $this->__("Checkbox input"), "args" => [ "type" => "checkbox", "multiple" => false, ], ], "checkbox_multiple_test" => [ "title" => $this->__("Checkbox multiple input"), "args" => [ "type" => "checkbox", "multiple" => true, "options" => [ "zh" => $this->__("Chinesse"), "en" => $this->__("English"), "es" => $this->__("Spanish"), ], ], ], "radio_test" => [ "title" => $this->__("Radio input"), "args" => [ "type" => "radio", "multiple" => true, "options" => [ "zh" => $this->__("Chinesse"), "en" => $this->__("English"), "es" => $this->__("Spanish"), ], ], ], "image_test" => [ "title" => $this->__("Image input"), "args" => [ "type" => "image", ], ], "file_test" => [ "title" => $this->__("File input"), "args" => [ "type" => "file", ], ], "empty_test" => [ "title" => $this->__("Input commented"), "args" => [ "type" => "empty", ], ], ], ], ];
Sample database
Notes about the database:
- Courses has an internalization table
- Courses and students have a many-to-many relationship
- Courses have a soft foreign key, teacher_id can be NULL, a course can exist without a teacher
- Teachers notes has a hard foreig key, teacher_id cannot be NULL, a note it can't exist without a teacher
Inside of Backend Frontend Template Pro you will find the queries used to install the database
Automated data manipulation of the plugin database
-
Required fields on the database table
For display and manipulate the data automatically we will need a few dedicated fields in a table
- Status, enum type, field name decided in $this->database_status_column_name, for the enum options use the stored data on $this->database_status_option_active_name and $this->database_status_option_bin_name
- Created time, datetime type, field name decided in $this->database_datetime_created_name
- Modified time, datetime type, field name decided in $this->database_datetime_modified_name
- Removed time (moved to bin), datetime type, field name decided in $this->database_datetime_removed_name
Plus the table can have an internationalization table (i18n), that table will have a foreign_key pointing to the ID of the main table
An example of a table with an internalization table:
CREATE TABLE `wp_bft_pro_courses` ( `id` int(10) NOT NULL AUTO_INCREMENT, `status` enum('active','bin') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'active', `datetime_created` datetime NULL DEFAULT NULL, `datetime_modified` datetime NULL DEFAULT NULL, `datetime_removed` datetime NULL DEFAULT NULL, `hours` int(4) NOT NULL DEFAULT 0, `order` int(10) NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; CREATE TABLE `wp_bft_pro_courses_i18n` ( `id` int(10) NOT NULL AUTO_INCREMENT, `course_id` int(10) NOT NULL COMMENT 'Foreign Key', `language` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `datetime_created` datetime NULL DEFAULT NULL, `datetime_modified` datetime NULL DEFAULT NULL, `name_i18n` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `image` int(10) NOT NULL, PRIMARY KEY (`course_id`, `language`) USING BTREE, UNIQUE INDEX `uk_wp_bft_pro_courses_i18n_id`(`id`) USING BTREE, UNIQUE INDEX `uk_wp_bft_pro_courses_i18n_id_primary_keys`(`course_id`, `language`) USING BTREE, CONSTRAINT `fk_wp_bft_pro_courses_i18n_course_id` FOREIGN KEY (`course_id`) REFERENCES `wp_bft_pro_courses` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Internationalization of course names' ROW_FORMAT = Dynamic;
Note: don't use 'wp_', a WordPress installation maybe use other prefix, use instead $wpdb->prefix, example: CREATE TABLE `”.$wpdb->prefix.”your_plugin_table`
Warning: ‘name’ can't be used for a column a i18n table. WordPress clean the data automatically of all 'name' appearances’ and WordPress doesn't expect an array of data (it's an array because it's the text in each language)
On the many to many relationships is needed the created time and modified time
//Estudiantes no descritos en esta página de ejemplo, la clave foránea no puede funcionar si copias la query CREATE TABLE `wp_bft_pro_students_courses` ( `id` int(10) NOT NULL AUTO_INCREMENT, `student_id` int(10) NOT NULL COMMENT 'Foreign Key', `course_id` int(10) NOT NULL COMMENT 'Foreign Key', `datetime_created` datetime NULL DEFAULT NULL, `datetime_modified` datetime NULL DEFAULT NULL, PRIMARY KEY (`student_id`, `course_id`) USING BTREE, UNIQUE INDEX `uq_wp_bft_pro_students_courses_id`(`id`) USING BTREE, UNIQUE INDEX `uq_wp_bft_pro_students_courses_student_id_course_id`(`student_id`, `course_id`) USING BTREE, INDEX `fk_wp_bft_pro_students_courses_course_id`(`course_id`) USING BTREE, CONSTRAINT `fk_wp_bft_pro_students_courses_course_id` FOREIGN KEY (`course_id`) REFERENCES `wp_bft_pro_courses` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `fk_wp_bft_pro_students_courses_student_id` FOREIGN KEY (`student_id`) REFERENCES `wp_bft_pro_students` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Relationship table' ROW_FORMAT = Dynamic;
-
$this->admin_forms
The forms are described in $this->admin_forms of the file admin/class-your-plugin-admin.php, a form it's the union of a table and its internationalization
Designing a BFT form looks like this:
$this->admin_forms = [ "courses" => [ "table" => $wpdb->prefix.$this->plugin_slug."_"."courses", "column_key" => "id", "column_title_name" => "name_i18n", "i18n_foreign_key" => "course_id", "columns" => [ $this->database_status_column_name => [ "label" => $this->database_status_column_text, "type" => "select", "options" => $this->database_status_options, ], "id" => [ "label" => $this->__("Nº"), "placeholder" => "", "type" => "text", "display_table" => true, "readonly" => true, ], $this->database_datetime_created_name => [ "label" => $this->database_datetime_created_text, "placeholder" => "", "type" => "datetime", "display_table" => false, "readonly" => true, ], $this->database_datetime_modified_name => [ "label" => $this->database_datetime_modified_text, "placeholder" => "", "type" => "datetime", "display_table" => false, "readonly" => true, "only_in_active" => true, ], $this->database_datetime_removed_name => [ "label" => $this->database_datetime_removed_text, "placeholder" => "", "type" => "datetime", "display_table" => true, "readonly" => true, "only_in_removed" => true, ], "hours" => [ "label" => $this->__("Total hours of the course"), "placeholder" => $this->__("Hours"), "type" => "number", "i18n" => false, "readonly" => true, "display_table" => true, ], "name_i18n" => [ "label" => $this->__("Course name"), "placeholder" => $this->__("Name"), "type" => "text", "i18n" => true, "display_table" => true, ], "image" => [ "label" => $this->__("Course logo"), "placeholder" => $this->__("Image"), "type" => "image", "i18n" => true, "display_table" => true, ], "order" => [ "label" => $this->__("Order"), "placeholder" => $this->__("Order, lowest number first"), "type" => "text", "i18n" => false, "display_table" => true, ], "teacher_id" => [ "label" => $this->__("Teacher"), "placeholder" => "", "type" => "select", "foreign_key" => "teachers", "display_table" => true, "value_empty_is_null" => true, "foreign_key_null_text" => $this->__("Teacher not selected"), ], "students_courses" => [ "label" => $this->__("Enrolled students"), "placeholder" => $this->__("Students"), "type" => "many_to_many_relationship", //Display table doesn't work for now with many_to_many_relationship "display_table" => false, "search" => true, ], ], ], ];
Explaining the BFT menu:
-
Form parameters
- key: internal form id
- table: table name
- column_key: key name ('id’ usually)
- column_title_name: field where it's stored the row name (the name of a user, rhe name of a book, etc.)
- i18n_foreign_key: foreign key on the internationalization table
- columns: list of fields
-
Optional field parameters
- label: text to show on the admin. Default: “”
- placeholder: placeholder if the data type can have placeholder
- display_table: displays the field in the list table. Note: display_table doesn't work with fields many_to_many_relationship
- i18n: if the data of the field is stored in a internationalization table
- only_in_active: display the field only if the data is active
- only_in_removed: display the field only if the data is on the bin
- readonly: readonly field, for the key column for example
- value_from_get: catch the data from the GET data on the URL if there aren't data stored of this field. For a dependant data, example: teachers' Notes, a new note needs to know the teacher id of the note
- value_empty_is_null: an empty data will be transformed to NULL (the NULL data always is printed like value=”)
- foreign_key: it stores a foreign key
- foreign_key_null_text: text to display when NULL is saved in a foreign key with optional NULL
- type: the BFT input options plus the 'many_to_many_relationship' option, is a select to link the relationship. 'text’ by default
- many_to_many_relationship: uses the data saved in $this->admin_forms_many_to_many_relationships
"type" => "many_to_many_relationship", "search" => true, //Display table doesn't work for now with many_to_many_relationship "display_table" => false,
- text: input text
"type" => "select",
- textarea: input textarea, rows and cols are optionals
"type" => "textarea", "rows" => 10, "cols" => 50,
- number: input number, optional args: min, max and step. Default of step: 1, alternative: 'decimal’ or 'decimals', this option calculates the required steps
"type" => "number", "min" => 1, "max" => 99999, "step" => 1,
- select: input select, args must have 'options', it stores string wiht checked options, ex: 'en,es’, optional args: search (false by default), multiple (false by default)
"type" => "select", "search" => true, "options" => [ "zh" => $this->__("Chinesse"), "en" => $this->__("English"), "es" => $this->__("Spanish"), ], "type" => "select", "multiple" => true, "search" => true, "options" => [ "zh" => $this->__("Chinesse"), "en" => $this->__("English"), "es" => $this->__("Spanish"), ],
- date: input text with a calendar, the date will be in MySQL style '2023-01-01’
"type" => "date",
- checkbox, single and multiple, in multiple is needed 'options’ on args
"type" => "checkbox", "multiple" => false, "type" => "checkbox", "multiple" => true, "options" => [ "zh" => $this->__("Chinesse"), "en" => $this->__("English"), "es" => $this->__("Spanish"), ],
- radio: radio buttons, it stores string wiht checked options, ex: 'en, es’, it requires 'options’
"type" => "radio", "multiple" => true, "options" => [ "zh" => $this->__("Chinesse"), "en" => $this->__("English"), "es" => $this->__("Spanish"), ],
- image: store is needed and select and image of the WordPress Media Library, the data saved is the ID of the file. Once selected shows the image
"type" => "image",
- file: the same as the image type, but don't try to display an image, only shows a download button
"type" => "file",
- empty: a commented input, it prints '<!– id=”'.$id_setting.'” empty –>’
"type" => "empty",
- many_to_many_relationship: uses the data saved in $this->admin_forms_many_to_many_relationships
-
-
$this->admin_forms_many_to_many_relationships
This form describes a many to many relationship between two tables
-
Form parameters
- key: internal id of the many to many relationship
- table: table name
- forms: array of the two relationship form
-
Parameter of the two related fields
- key: form ID
- value: foreign key used in the table
Example of the array:
$this->admin_forms_many_to_many_relationships = [ "students_courses" => [ "table" => $wpdb->prefix.$this->plugin_slug."_"."students_courses", "forms" => [ "courses" => "course_id", "students" => "student_id", ], ], ];
The system will work with the above data:
$this->admin_forms = [ "courses" => [ [...] "columns" => [ [...] "students_courses" => [ "label" => $this->__("Enrolled students"), "placeholder" => $this->__("Students"), "type" => "many_to_many_relationship", //Display table doesn't work for now with many_to_many_relationship "display_table" => false, "search" => true, ], ], ], ];
-
-
$this->admin_pages
BFT needs to know what function and function_load to use on the page. The system also needs to know how to read the GET data from the URL
There are listing pages and form pages. It's posible to nest listing pages that use the data of the previous form, for example: teacher's notes, first select a teacher (or create a new one), then you go to that teacher's notes.
The explanation of $this->admin_pages is in the 'Menu' tab, there is a complete example of use
-
-
Display a listing
-
For display a list it's needed a child with all the data, plus it's needed:
-
- “function” => “admin_menu_page_table_display_first_menu_child”,
- “file” => “bft-admin-display-list-table.php”,
-
-
Display a form
-
A form is usually a child page of a listing, the form needs:
-
- “ids_required” => [“get_data” => “id_data”],
"ids_required" => [ "teacher_id" => "id", ],
- “admin_forms_id” => “form_key”,
- “function” => “admin_menu_admin_form_page_display”,
- “file” => “bft-admin-display-form.php”,
- “function_load” => “custom_form_set_do_by_post”,
- “tabs_new_or_edit_on_url” => true,
- “new_text” => “New entry text”,
- “ids_required” => [“get_data” => “id_data”],
An example of a listing and form:
$this->admin_pages = [ "automated_data_manipulation_courses" => [ "menu_title" => $this->__("Courses"), "page_title" => $this->__("List of courses"), "function" => "admin_menu_page_table_display_first_menu_child", "file" => "bft-admin-display-list-table.php", "children" => [ "automated_data_manipulation_course" => [ "ids_required" => [ "courses_id" => "id", ], "admin_forms_id" => "courses", "tabs_new_or_edit_on_url" => true, "menu_title" => $this->__("Course"), "page_title" => $this->__("Course form"), "function" => "admin_menu_admin_form_page_display", "file" => "bft-admin-display-form.php", "function_load" => "custom_form_set_do_by_post", "new_text" => $this->__("New course"), ], ], ], ];
-
-
Nest a form
-
To nest a BFT form it's needed always to specify the external GET data, Don't put 'id’ on the foreign id
-
-
-
Display a listing
- “ids_required” => [“get_data” => “id_data”], is the same as the parent form
"ids_required" => [ "teacher_id" => "teacher_id", ],
- “ids_aux_required” => [“get_data” => “id_data”], optional, works with ids_aux_required
- “admin_forms_aux_id” => “form_key”, optional
- “function” => “admin_menu_page_table_display_first_menu_child”,
- “file” => “bft-admin-display-list-table.php”,
- “ids_required” => [“get_data” => “id_data”], is the same as the parent form
-
-
-
Display a form
-
-
The main difference is that it has two ids: the main id, and the foreig id. Do not put 'id’ in the foreign id. Plus it can show column_title_name
-
-
- “ids_required” => [“get_data” => “id_data”],
"ids_required" => [ "note_id" => "id", "teacher_id" => "teacher_id", ],
- “admin_forms_id” => “form_key”,
- “ids_aux_required” => [“get_data” => “id_data”], optional, works with ids_aux_required
- “admin_forms_aux_id” => “form_key”, optional
- “function” => “admin_menu_admin_form_page_display”,
- “file” => “bft-admin-display-form.php”,
- “function_load” => “custom_form_set_do_by_post”,
- “tabs_new_or_edit_on_url” => true,
- “new_text” => “New entry text”,
- “ids_required” => [“get_data” => “id_data”],
-
An example of a nested form:
$this->admin_pages = [ "automated_data_manipulation_teachers" => [ "menu_title" => $this->__("Teachers"), "page_title" => $this->__("List of teachers"), "function" => "admin_menu_page_table_display_first_menu_child", "file" => "bft-admin-display-list-table.php", "children" => [ "automated_data_manipulation_teacher" => [ "ids_required" => [ "teacher_id" => "id", ], "admin_forms_id" => "teachers", "tabs_new_or_edit_on_url" => true, "menu_title" => $this->__("Teacher"), "page_title" => $this->__("Teacher form"), "function" => "admin_menu_admin_form_page_display", "file" => "bft-admin-display-form.php", "function_load" => "custom_form_set_do_by_post", "new_text" => $this->__("New teacher"), ], "automated_data_manipulation_teacher_notes" => [ "ids_required" => [ "teacher_id" => "teacher_id", ], "ids_aux_required" => [ "teacher_id" => "id", ], "admin_forms_aux_id" => "teachers", "menu_title" => $this->__("Teacher notes"), "page_title" => $this->__("List of notes"), "function" => "admin_menu_page_table_display_first_menu_child", "file" => "bft-admin-display-list-table.php", "children" => [ "automated_data_manipulation_teacher_note" => [ "ids_required" => [ "note_id" => "id", "teacher_id" => "teacher_id", ], "admin_forms_id" => "teachers_notes", "ids_aux_required" => [ "teacher_id" => "id", ], "admin_forms_aux_id" => "teachers", "tabs_new_or_edit_on_url" => true, "menu_title" => $this->__("Teacher note"), "page_title" => $this->__("Teacher note form"), "function" => "admin_menu_admin_form_page_display", "file" => "bft-admin-display-form.php", "function_load" => "custom_form_set_do_by_post", "new_text" => $this->__("New note"), ], ], ], ], ], ];
-
In the “Menu Pro” section there is an example of nested forms, if you have not seen that section it is recommended to view it
Manual data manipulation: inside a function
-
WordPress functions
WordPress functions For database manipulation WordPress provide the class $wpdb, call it inside a function with global $wpdb
Some data manipulation functions of $wpdb are:
- $wpdb->prefix: the WordPress database prefix, all tables will have this prefix
- $wpdb->query($query): do a direct query
- $wpdb->insert($table_name, $array_data): insert a new entry (the function sanitizes the inputs)
- $data = $wpdb->insert_id: last inserted ID
- $wpdb->delete($table_name, $array_search_data): delete entries that match the array search data (the function sanitizes the inputs)
- $wpdb->update($table_name, $array_data, $array_search_data): update the data in the entries that match the array data (the function sanitizes the inputs)
- $data = $wpdb->get_results($query, “ARRAY_A” ): returns a two-dimensional array with all the data
All the info of this class can be found here
-
Backend Frontend Template functions
BFT provides additional functions for the programmers that prefer this database access style
- $this->wpdb_get_results_array($query): returns a two-dimensional array with all the data
- $this->wpdb_get_results_with_index($query): Returns a two-dimensional array with all the data, the index of each row will be the data in the first column
- $this->wpdb_get_results_one_data_per_row($query): returns a one-dimensional array, returns only the first column in each row
- $this->wpdb_get_results_index_and_data_per_row($query): returns a one-dimensional array, the first column data will be the index, the second the data
- $this->wpdb_get_result_one_data($query): returns a string, returns only the first column of the first row
- $this->wpdb_insert_update_on_duplicate_key($table, $data, $multi_row = false, $modified_value = NULL, $data_for_update = array()): insert or update multiple data, more explanation below (the function sanitizes the inputs)
- $this->wpdb_insert_update_on_duplicate_key_delete_others($table, $data, $column_where_delete, $value_where_delete): insert or update multiple data, then delete the non updated rows, more information below (the function sanitizes the inputs)
Additional explanations
-
wpdb_insert_update_on_duplicate_key
$this->wpdb_insert_update_on_duplicate_key($table, $data, $multi_row = false, $modified_value = NULL, $data_for_update = array())
This function can insert or update one or more rows, when updating the data can use extra data or only use the data used to insert
- $table: table name
- $data: mono-dimensional array (an insert) or two-dimensional (several inserts), data can have true NULL or string 'NULL’
- $multi_row: true if multiple inserts are required
- $modified_value: modification datetime, it will use the column $this->database_datetime_modified_name, if NULL it will use the current time
- $data_for_update: the data used for the update case, if the array is empty the update will use the $data info
-
wpdb_insert_update_on_duplicate_key_delete_others
$this->wpdb_insert_update_on_duplicate_key_delete_others($table, $data, $column_where_delete, $value_where_delete)
This function uses wpdb_insert_update_on_duplicate_key, then it does a delete of the rows not updated
- $table: table name
- $data: mono-dimensional array (an insert) or two-dimensional (several inserts), data can have true NULL or string 'NULL’
- $column_where_delete: the column to check (id, foreign key, etc.)
- $value_where_delete: the ID to check in column_where_delete
This function is equivalent to deleting all data and inserting the new data, the problem is database abuse, with this the database updates all the data (maybe just update $this->database_datetime_modified_name), and then delete the not updated data. Less UPDATE/INSERTS, less DELETES
NOTE: due to how WordPress works the BFT functions are not accesible from create/update/delete database in class-your-plugin-install-upgrade-deinstall-database.php only use the official WordPress function
-
Data sanitization
For input sanitization WordPress provide several sanitization functions in the class $wpdb
Some sanitation functions of $wpdb are:
- $wpdb->sanitize_key: sanitize an ID
- $wpdb->sanitize_text_field: sanitize a string
- $wpdb->sanitize_textarea_field: sanitize a multiple string
- $wpdb->prepare: sanitize a full query
All sanitation information can be found here
For the use of $wpdb->prepare($query, $args) Check the information
Manual data manipulation: paginated listing by query
It is also necessary to use the file “file” => “bft-admin-display-list-table.php”
$this->display_table_query_custom()
display_table_query_custom( $query_select_inside, $query_from_inside, $query_where_inside, $group_inside, $ids, $columns_tables_dont_search, $search_concat, $column_key, $columns_tables, $columns_labels, $column_action_add, $status_system = false, $write_log_query = false )
-
$query_select_inside
SELECT part of the query, without the SELECT tag
-
$query_from_inside
FROM part of the query, without the FROM tag
-
$query_where_inside
WHERE part of the query, without the WHERE tag
-
$group_inside
GROUP BY part of the query, without the FROM tag
-
$ids
Ids to search for, the indexes will be the column key
It can be an empty array, an array with manual data, or an automatic array with the ids_required_get_data thanks to $this->ids_required_and_optional_check_and_get()
$ids = [ "teacher_id" => "1", ];
$ids = $this->ids_required_and_optional_check_and_get ( $ids_principal_aux_type = "principal", $ids_require_optional_type = "both", $read_all_get_data = false );
-
$columns_tables_dont_search
Don't search the search string in these columns. The indexes will be the column key
$columns_tables_dont_search = [ "column_1" => "table", "column_2" => "table", ];
-
$search_concat
Two-dimensional array, first dimension is the final column, the second dimension is the columns of CONCAT, for search the string on merged columns
Ex:
search_string: "Lorem Ipsum" row_1 column_1: "Lorem" row_1 column_2: "Ipsum"
$search_concat = [ [ "column_1" => "table", "column_2" => "table", ], ];
-
$column_key
Column key of the query
-
$columns_tables
The tables of the columns, with indexes
$columns_tables_dont_search = [ "column_1" => "table", "column_2" => "table", ];
-
$columns_labels
The labels of the columns, with indexes of the columns
$columns_labels = [ "id" => $this->__("Nº"), "column_2" => $this->__("Label of the column"), "column_3" => "Label without internalization", ];
-
$column_action_add
Add or not add a link
- column_action_key: the column key for the link, false if a link is not needed
- column_action_edit_slug: the slug of the page
$column_action_add = [ "column_action_key" => "course_id", "column_action_edit_slug" => $this->admin_pages_slug_name_prefix."_"."slug_of_the_page", ];
$column_action_add = [ "column_action_key" => false, "column_action_edit_slug" => "", ];
-
$status_system
The table uses the column $this->database_status_column_name
-
$write_log_query
It writes to the registry the base query
A complete example of $this->display_table_query_custom() used in a function
public function manual_data_manipulation_listing_by_query_example() { global $wpdb; $this->admin_permission_check(); $language_admin = $this->language_admin_get(); $ids = array(); //Add or not add a "to go element", inside the array: "column_action_key" with the future get data with the id, "column_action_edit_slug" the target slug for the edit action $column_action_add = [ "column_action_key" => "course_id", //The future get data name with the id "column_action_edit_slug" => $this->admin_pages_slug_name_prefix."_automated_data_manipulation_course", //Target slug for the edit action ]; /* SELECT wp_bft_pro_courses.id, wp_bft_pro_courses_i18n.name_i18n AS course FROM wp_bft_pro_courses LEFT JOIN wp_bft_pro_courses_i18n ON wp_bft_pro_courses.id = wp_bft_pro_courses_i18n.course_id AND wp_bft_pro_courses_i18n.language = 'en'; WHERE teacher_id is not null */ //SELECT part of the query, whitout the SELECT tag $query_select_inside = " ".$wpdb->prefix.$this->plugin_slug."_"."courses.id, ".$wpdb->prefix.$this->plugin_slug."_"."courses_i18n.name_i18n AS name"; //FROM part of the query, whitout the FROM tag $query_from_inside = " ".$wpdb->prefix.$this->plugin_slug."_"."courses LEFT JOIN ".$wpdb->prefix.$this->plugin_slug."_"."courses_i18n ON ".$wpdb->prefix.$this->plugin_slug."_"."courses.id = ".$wpdb->prefix.$this->plugin_slug."_"."courses_i18n.course_id AND ".$wpdb->prefix.$this->plugin_slug."_"."courses_i18n.`".$this->database_i18n_column_language."` = '$language_admin'"; //WHERE part of the query, whitout the WHERE tag $query_where_inside = " teacher_id IS NOT NULL"; //GROUP BY part of the query, whitout the FROM tag $group_inside = ""; //Column key of the query $column_key = "id"; //The tables of the columns, with indexes of the tables $columns_tables = [ "id" => $wpdb->prefix.$this->plugin_slug."_"."courses", "name" => $wpdb->prefix.$this->plugin_slug."_"."courses_i18n", ]; //Don't search the search string on this columns. The indexes will be the column key $columns_tables_dont_search = array(); //Two dimensional array, first dimension is the final colum, the second dimension are the colums of the CONCAT ("column" => "table"), for search the string on the final CONCAT data $search_concat = []; //The labels of the columns, with indexes of the columns $columns_labels = [ "id" => $this->__("Nº"), "name" => $this->__("Course"), ]; $status_system = false; $write_log_query = false; $args = $this->display_table_query_custom($query_select_inside, $query_from_inside, $query_where_inside, $group_inside, $ids, $columns_tables_dont_search, $search_concat, $column_key, $columns_tables, $columns_labels, $column_action_add, $status_system, $write_log_query); $args["ids"] = $ids; $this->admin_menu_page_display($args); }
Manual data manipulation: listing by Array
It's also necessary to use “file” => “bft-admin-display-list-table.php”
-
Array data
-
Required data
-
data
Array with each row of data, without id. Within each row, each column of data has the column keys
-
columns
Array with the keys and names of the columns
$display_table_data = [ "data" => [ [ "id" => "1", "name" => "Lorem Ipsum Name", ], [ "id" => "2", "name" => "Dolor Sit Name", ], ], "columns" => [ "id" => "Nº", "name" => "Name", ], ];
-
-
Optional data
-
$column_action_add
Add or not add a link
- column_action_key: the column key for the link, false if a link is not needed
- column_action_edit_slug: the slug of the page
$column_action_add = [ "column_action_key" => "course_id", "column_action_edit_slug" => $this->admin_pages_slug_name_prefix."_"."slug_of_the_page", ];
$column_action_add = [ "column_action_key" => false, "column_action_edit_slug" => "", ];
-
get_extra_all_rows
Array with data to add in the GET data of the links
"get_extra_all_rows" => [ "example_get_data" => "Lorem_Ipsum", ], GET resultant: [...]?page=[...]&example_get_data=Lorem_Ipsum
-
items_per_page
The items for a page, for data not paginated it's not needed this data
-
items_column_key
The key column
-
items_count
Total item count, for data not paginated it's not needed this data
-
items_count_with_search
Total item count with search, for data without search it's not needed this data
-
search_text
The search label, for data without search it's not needed this data
-
page_slug
The actual slug page
-
ids
Ids used on this page
-
-
-
Sending the data
For display a table the function admin_menu_page_display() needs “display_table” => true” and “display_table_data” with the listing data
$args = [ "ids" => $ids, "display_table" => true, "display_table_data" => $display_table_data, ]; $this->admin_menu_page_display($args);
A complete example of a custom listing by array data used in a function
public function manual_data_manipulation_listing_by_array_example() { $this->admin_permission_check(); $ids = $this->ids_required_and_optional_check_and_get($ids_principal_aux_type = "principal", $ids_require_optional_type = "both", $read_all_get_data = false); //$ids = array(); /* $display_table_data = [ "data" => [ [ "id" => "1", "name" => "Lorem Ipsum Name", ], ], "columns" => [ "id" => "Nº", "name" => "Name", ], "column_action_add" => [ "column_action_key" => false, "column_action_edit_slug" => "", ], "get_extra_all_rows" => false, "items_per_page" => 999999, "items_column_key" => "id", "items_count" => "1", "items_count_with_search" => "1", "search_text" => false, "page_slug" => $this->plugin_slug."_manual_data_manipulation_listing_by_array_example", "ids" => [], ]; $display_table_data = [ "data" => [ [ "id" => "1", "name" => "Lorem Ipsum Name", ], ], "columns" => [ "id" => "Nº", "name" => "Name", ], "column_action_add" => [ "column_action_key" => "course_id", "column_action_edit_slug" => $this->admin_pages_slug_name_prefix."_automated_data_manipulation_course", ], "get_extra_all_rows" => [ "example_get_data" => "Lorem Ipsum", ], "items_per_page" => 999999, "items_column_key" => "id", "items_count" => "1", "items_count_with_search" => "1", "search_text" => false, "page_slug" => $this->plugin_slug."_manual_data_manipulation_listing_by_array_example", "ids" => [], ]; */ $display_table_data = [ "data" => [ [ "id" => "1", "name" => "Lorem Ipsum Name", ], ], "columns" => [ "id" => "Nº", "name" => "Name", ], ]; $args = [ "ids" => $ids, "display_table" => true, "display_table_data" => $display_table_data, ]; $this->admin_menu_page_display($args); }
Manual data manipulation: forms
You can use the page bft-admin-display-form.php to display the form or create your own page
-
Custom function
Get the data your own way and send it to the function $this->admin_menu_page_display(), for example:
$args = [ "title" => $this->admin_pages_data_get("page_title"), "id" => $id, "ids" => $ids, "form_data" => [ "page_name" => $this->page_name, "data" => $data, "column_key" => $column_key, ], ]; $this->admin_menu_page_display($args);
If the page uses bft-admin-display-form.php the data will be formed in this way:
$data = [ "column_name_1" => [ "label" => "", "placeholder" => "", "type" => text, "id" => column_name_1, "value" => "", ], "column_name_2" => [ "label" => "", "placeholder" => "", "type" => text, "id" => column_name_2, "value" => "", ], "column_group_name_1" => [ "label" => "", "values" => [ "column_name_3" => [ "label" => "", "placeholder" => "", "type" => text, "id" => column_name_3, "value" => "", ], "column_name_4" => [ "label" => "", "placeholder" => "", "type" => text, "id" => column_name_4, "value" => "", ], ], ], ];
-
Custom function_load
This function is executed first, POST data sent from the browser or not, break the function if post data not sent or some error was found, example:
if ("POST" !== $_SERVER['REQUEST_METHOD']) { return false; }
At the end of the function use redirect if needed. To redirect can be used wp_redirect() or $this->custom_form_redirect()
-
wp_redirect($url)
This function of WordPress needs the full URL, that's why BFT offers better alternatives:
-
$this->admin_page_url_get()
Returns the currently displayed page without additional GET data, only the GET data of the page
https://yourfuturewebsite/wp-admin/admin.php?page=bft_pro_manual_data_manipulation_form_explanation
-
$this->gets_for_admin_page($id)
Returns the GET data needed for the form of the page. Ex: if a page uses the form 'courses', send it the id '1'’ and it will retrieve:
&course_id=1
-
-
$this->custom_form_redirect($id = false)
This BFT function does a redirect to the full URL with the ID needed for the form and adds &action=new or &action=edit depending on whether ID has been provided
-
-
Full example
$this->admin_pages = [ "manual_data_manipulation_form_simple_example_teacher" => [ /*"ids_required" => [ "teacher_id" => "id", ],*/ "admin_forms_id" => "teachers", "columns" => [ "id", "name", "surname", "id_card", ], "menu_title" => $this->__("Example 1"), "page_title" => $this->__("Teacher")." 1, "function" => "manual_data_manipulation_form_example_for_two_pages_get", "file" => "bft-admin-display-form.php", "function_load" => "manual_data_manipulation_form_example_for_two_pages_set", ], ];
public function manual_data_manipulation_form_example_for_two_pages_get() { $this->admin_permission_check(); global $wpdb; $ids = $this->ids_required_and_optional_check_and_get($ids_principal_aux_type = "principal", $ids_require_optional_type = "both", $read_all_get_data = false); //$ids will be empty on the example, adding the id $ids["id"] = "1"; //teachers or students $admin_forms_id = $this->admin_pages_data_get_admin_forms_id(); /* Remember: on $this->admin_pages you can store all the data you want and retrieve it with $this->admin_pages_data_get(); Teachers: "columns" => [ "id", "name", "surname", "id_card", ], Students: "columns" => [ "id", "name", "surname", ], */ $columns_array = $this->admin_pages_data_get( "columns"); //$language_admin = $this->language_admin_get(); if (0 == count($ids) || false == $admin_forms_id || !isset($this->admin_forms[$admin_forms_id]) || false == $columns_array) { $error_message = $this->__("Error message"); $this->error_throw ($error_message, $error_throw_what_do_use_this = "show_error_and_die"); } $id = sanitize_key(array_values($ids)[0]); $query = " SELECT ".implode(", ", $columns_array)." FROM ".$this->admin_forms[$admin_forms_id]["table"]." WHERE id = '$id'"; $data_database = $this->wpdb_get_result_one_row($query); if (0 == count($data_database)) { $error_message = $this->__("Error message"); $this->error_throw ($error_message, $error_throw_what_do_use_this = "show_error_and_die"); } $data = array(); foreach ($data_database AS $data_database_key => $data_database_value) { if (isset($this->admin_forms[$admin_forms_id]["columns"][$data_database_key])) { $data[$data_database_key] = $this->admin_forms[$admin_forms_id]["columns"][$data_database_key]; $data[$data_database_key]["value"] = $data_database_value; } } $args = [ "title" => $this->admin_pages_data_get("page_title"), "id" => $id, "ids" => $ids, "form_data" => [ "page_name" => $this->page_name, "data" => $data, "column_key" => $this->admin_forms[$admin_forms_id]["column_key"], ], ]; $this->admin_menu_page_display($args); }
public function manual_data_manipulation_form_example_for_two_pages_set() { $this->admin_permission_check(); $redirect_page = false; $request_method = "POST"; if ($request_method !== $_SERVER['REQUEST_METHOD']) { //Post data not found return false; } $data_raw = $_POST; if ( !isset($data_raw["_wpnonce"]) || !wp_verify_nonce($data_raw["_wpnonce"], $this->plugin_slug."_form")) { //Nonce not verified return false; } //For security check $this->ids_required_and_optional_check_and_get($ids_principal_aux_type = "principal", $ids_require_optional_type = "both", $read_all_get_data = false); //teachers or students $admin_forms_id = $this->admin_pages_data_get_admin_forms_id(); /* Remember: on $this->admin_pages you can store all the data you want and retrieve it with $this->admin_pages_data_get(); Teachers: "columns" => [ "id", "name", "surname", "id_card", ], Students: "columns" => [ "id", "name", "surname", ], */ $columns_array = $this->admin_pages_data_get( "columns"); if ( false == $admin_forms_id || !isset($this->admin_forms[$admin_forms_id]) || false == $columns_array) { return false; } $data_row = array(); foreach ($columns_array AS $column) { if (!isset($data_raw[$column])) { return false; } $data_row[$column] = $data_raw[$column]; } $this->wpdb_insert_update_on_duplicate_key($this->admin_forms[$admin_forms_id]["table"], $data_row, $multi_row = false); /* Query executed: INSERT INTO wp_bft_pro_teachers ( datetime_modified, id, name, surname, id_card, datetime_created ) VALUES ( '2023-01-01 00:00:01', '1', 'Teacher 1 edited', 'surname 1', '1111', '2023-01-01 00:00:01' ) ON DUPLICATE KEY UPDATE datetime_modified = '2023-01-01 00:00:01', id = '1', name = 'Teacher 1 edited', surname = 'surname 1', id_card = '1111' Obviously the duplicate key will be executed on this case for the "id" column Note the automated columns: $this->database_datetime_created_name (datetime_created) $this->database_datetime_modified_name (datetime_modified) */ if (true === $redirect_page) { //ids not specified in the example page, this is for a complete case $this->custom_form_redirect($data_row["id"]); } else { return true; } }
Internationalization
-
Preparing the text
For specify a text that maybe needs translation, WordPress provides the functions:
- __(‘string’, 'translation domain/plugin id'): for direct translation
- _e(‘string’, 'translation domain/plugin id'): for direct translation and display the translated text
- esc_html_(‘string’, 'translation domain/plugin id') for translation and escape HTML characters
- esc_html_e(‘string’, 'translation domain/plugin id') for translation, escape HTML characters and display the resulting text
For more functions look in the WordPress documentation: link here
With that, WordPress will use a translation file if it exists and has that sentence, or an external plugin will be able to translate your plugin into the visitor's language
BFT also provides the option of use its intermediary functions to avoid having to put the domain in every string prepared for a future translation . With ALL the WordPress translation functions
For that, call the function through ‘$this’ and don't put the translation domain
- $this->__(‘string’): for direct translation
- $this->_e(‘string’): for direct translation and display the translated text
- $this->esc_html_(‘string’) for translation and escape HTML characters
- $this->esc_html_e(‘string’) for translation, escape HTML characters and display the resulting text
NOTE: the functions allow the domain field, if you put a domain then that domain will be use
-
Preparing the translation files
The translation files are allocated in plugin_folder/languages, BFT automatically will set WordPress to search translations on that folder
The language files are:
- .pot: Portable Object Template, the master file with all the strings
- .po: Portable Object, the file with the strings translated to one language
- .mo: Portable Object, Machine Object, the compiled data from the .po file, WordPress will use this file
Steps to translate a plugin:
- Create a new .pot file of your plugin
- Update/merge the original .pot with the new .pot file
- Delete the old .pot files and rename the new merged file if needed
- Prepare the .po language file
- If it's a new translation language: duplicate the .pot file and change the name and extension to the designed language, like bft-pro – copy.pot to bft-pro-es.po or bft-pro-es_ES.po
- If it's a existing translation language: update/merge the file with the new .pot file
- Translate the sentences of the .po file
- Create the .mo file from the .po file
To create and combine the files and translate the sentences you can use programs such as Poedit or EazyPo
The WordPress log with BFT
- define( ‘WP_DEBUG’, true );
- define( ‘WP_DEBUG_LOG’, true );
Now you can check the log in wp-content/debug.log
For printing to the log you can use the WordPress function error_log($string_or_number), but with Backend Frontend Template you can use: $this->debug_log_write($whatever)
$this->debug_log_write() it's a better option because it shows:
- ‘NULL’ if is a NULL variable
- ‘TRUE’ and 'FALSE'’ if it's a boolean
- print_r() whether it is an array or an object
Now you can print in the log the variable you want
In addition, BFT offers an alternative name for debug_log_write: $this->write_log()
Function visibility
-
Private
Don't use private functions, BFT use inheritance on the classes and a private function can't inheritance
-
Protected
Ideal for the internal functions for security reasons, only your classes can use these functions
-
Public
Some functions need to be public due to how WordPress works:
- Functions called via $this->admin_pages -> an_admin_page -> 'function_load’ data
- Functions called via $this->admin_pages -> an_admin_page -> 'function’ data
- Functions called via install, upgrade or uninstall, upgrade or uninstall
- Functions called via shortcodes
- Functions called via AJAX responses
Secure the functions
-
Function load
The 'function_load’ option of the menu is the function that the page executes before sending the HTML headers
By default all pages execute admin_permission_check_and_ids_required_check_function_load(), the executed function can be changed on
class-your-plugin-admin -> $this->admin_pages_function_load_default = “admin_permission_check_and_ids_required_check_function_load”The function admin_permission_check_and_ids_required_check_function_load() checks if the admin capabilities are correct and if the id required data is not missing. In this function it works the 'go_to_parent’ option of the menu
This function can be called at the beginning of a custom function_load to check all before save changes
-
Function
The 'function’ option of the menu is the main function that the page executes
By default all pages execute admin_permission_check_and_ids_required_and_optional_check_page_display(), the executed function can be changed on
class-your-plugin-admin -> $this->admin_pages_function_default = “admin_permission_check_and_ids_required_and_optional_check_page_display”The function admin_permission_check_and_ids_required_and_optional_check_page_display() checks if the admin capabilities are correct and if the id required data is not missing
On a custom function there are functions for checking the access and to retrieve the ids:
- $this->admin_permission_check(): check the admin permissions and throw an error if needed. Recommended for use at the beginning of the function
- $this->ids_required_check(): check the ids required permissions and throw an error if needed
- $this->ids_required_and_optional_check_and_get ($ids_principal_aux_type = “principal”, $ids_require_optional_type = “both”, $read_all_get_data = true, $return_type = “array”, $error_message = NULL, $die_always_if_required_missing = true, $triggered_on_function_load = false): check the ids and return the data
Additional explanations
-
ids_required_and_optional_check_and_get
$this->ids_required_and_optional_check_and_get ($ids_principal_aux_type = “principal”, $ids_require_optional_type = “both”, $read_all_get_data = true, $return_type = “array”, $error_message = NULL, $die_always_if_required_missing = true, $triggered_on_function_load = false)
This is an alternative method to retrieve manually the data with admin_pages_data_get()
$data = $this->admin_pages_data_get("ids_required_get_data"); $data = $this->admin_pages_data_get("ids_principal_all_get_data"); $data = $this->admin_pages_data_get("ids_aux_required_get_data"); $data = $this->admin_pages_data_get("ids_aux_all_get_data"); $data = $this->admin_pages_data_get("ids_required_all_get_data"); $data = $this->admin_pages_data_get("ids_all_get_data");
But this method check if there are ids requiered missing, also can add GET data not define on the ids
Function variables:
- $ids_principal_aux_type: What ids to receive. Options: 'principal', 'aux’ and 'both', default 'principal'. Principal -> “ids_required', aux -> 'ids_aux_required', both -> 'ids_required_all’ (ids_required and ids_aux_required)
- $ids_require_optional_type: Select if only principal ids or add the optionals. Options: 'require’ and 'both', default 'require'. If both: Principal -> 'ids_principal_all_get_data’ (ids_required and ids_optional), aux -> 'ids_aux_all_get_data’ (ids_aux_required and ids_aux_optional), both -> 'ids_all_get_data’ (ids_required, ids_optional, ids_aux_required and ids_aux_optional)
- $read_all_get_data: Read all GET data from the URL and add additional options, default True. 'page’ GET data will not be added, the GET data key needs to be more than 2 characters
- $return_type: 'array’ (default option), 'array_always’ (don't return false, instead it returns an empty array if error found), 'data’ (first data if multiple ids)
- $error_message: default text: Missing required ID’
- $die_always_if_required_missing: If true adds 'show_error_and_die’ in a error throw. Default true
- $triggered_on_function_load: If triggered on a function_load, select false in a main function. Default false
Example of use:
$ids = $this->ids_required_and_optional_check_and_get ($ids_principal_aux_type = 'principal', $ids_require_optional_type = 'required', $read_all_get_data = false); $ids_aux = $this->ids_required_and_optional_check_and_get ($ids_principal_aux_type = 'aux', $ids_require_optional_type = 'required', $read_all_get_data = false);
Manage and display errors
-
Show an error
Backend Frontend Template can easily display errors, and does not repeat the same error on the same load. Also: the title of the plugin will be added to the message
- $this->error_show ($error_message = “”) displays an error message. If $error_message = “” it shows “Error detected”
- Adding error_message in the GET URL, the error message can be triggered with the functions $this->admin_permission_check() or $this->error_throw()
-
Throw an error
BFT can throw errors with
$this->error_throw ($error_message = “”, $error_throw_what_do_use_this = NULL, $error_throw_file_change_use_this = NULL, $triggered_on_function_load = false, $page_id = NULL)- $error_message: error to send to $this->error_show(), but first it will show the 'error_message’ stored in the URL
- $error_throw_what_do_use_this: to use this data instead of $this->admin_pages_data_get(“error_throw_what_do”), options: show_error, show_error_and_die, go_to_parent
- $error_throw_file_change_use_this: NULL by default, it use this data instead of $this->admin_pages_data_get(“error_throw_file_change”), for change the file displayed if error triggered
- $triggered_on_function_load: false by default, 'go_to_parent’ 'go_to_parent' only works if true == $triggered_on_function_load because it's needed do the redirect before sending the headers
- $page_id: the key/page name, if null it's the visualized page
-
Examples
- This page show an error with:
$error_message = $this->__("This is an error test"); $this->error_show ($error_message);
- The children page throws an error and return to this page with:
$this->admin_pages = [ "errors_manage" => [ "menu_title" => $this->__("Errors"), "page_title" => $this->__("Manage and display errors"), "file" => "bft-admin-display-errors-manage-show-pro.php", "children" => [ "throw_error_and_return_to_parent" => [ "menu_title" => $this->__("Throw error and return to parent"), "page_title" => $this->__("Throw error and return to parent"), "ids_required" => [ "nonexistent_id" => "nonexistent_id", ], "error_throw_what_do" => "go_to_parent", ], ] ], ];
Result that gives the test to enter a child page that gives error:
- This page show an error with:
Download private files
Deny from all
Therefore a simple link will not work, but a BFT download works (Examples available inside of Backend Frontend Pro: the WordPress Plugin Template)
For download it's needed a page where to send the data, this page uses the “function_load” => “download_file_private”
Plus note that download_file_private() search the 'file' get data’ on the URL, that's why it's needed to filter the URL calls without 'file' data: “ids_required” => [“file” => “file”]
Remember:
- The menu don't show a tab with GET data missing
- WordPress admin menu can't filter a existing page, don't use it on a parent page:
This example uses this download page:
"download_explanation" => [ [...] "children" => [ "download_file_private" => [ "menu_title" => $this->__("Download private file"), "page_title" => $this->__("Download private file"), "menu_slug" => "download", "function_load" => "download_file_private", "file" => "bft-admin-display-blank-page.php", "ids_required" => [ "file" => "file", ], "error_throw_what_do" => "go_to_parent", ], ] ],
Iframe on an admin page
<?php if (isset($iframe_admin_page_slug) && false != $iframe_admin_page_slug) { ?> <iframe src="" width="100%" height="700"> </iframe> <?php } if (isset($iframe_url) && false != $iframe_url) { ?> <iframe src="" width="100%" height="700"> </iframe> <?php } ?>
There are two types of iframes:
-
Iframe by slug
This method requires two pages: One is the main page with the iframe, the other is the iframe itself
-
Main page
The main page especifies the slug of the iframe page with “iframe_admin_page_slug” => “iframe page” and the display iframe page with “file” => “bft-admin-display-iframe.php”
"iframe_admin_page_slug_test" => [ "menu_title" => $this->__("Iframe by slug"), "page_title" => $this->__("Iframe by slug").': "iframe_admin_page_slug" => "iframe_test"', "file" => "bft-admin-display-iframe.php", "iframe_admin_page_slug" => "iframe_test", ],
-
Iframe page
The iframe page will call a function_load with a die, a function_load runs before displaying the WordPress Admin Menu. With “tab_show” => false we ignore the page on the BFT tab menu
"iframe_test" => [ "tab_show" => false, "file" => "bft-admin-display-iframe-test.php", "function_load" => "function_load_page_display", ],
-
-
Iframe by URL
This method only needs the URL of the iframe: “iframe_url” => admin_url() and the display iframe page “file” => “bft-admin-display-iframe.php”
"iframe_url_test" => [ "menu_title" => $this->__("URL iframe"), "page_title" => $this->__("URL iframe").": ".admin_url(), "file" => "bft-admin-display-iframe.php", "iframe_url" => admin_url()."admin.php?page=".$this->plugin_slug."_iframe_test", ],
Note: admin_url() points to the WordPress Admin Menu, it's used on this example because normally an external iframe will not work
-
Display PDF on ifame
With all this we can show a private PDF via slug iframe with “private_file_dir” => “file on private folder” and “function_load” => “show_pdf_private”
"iframe_admin_page_slug_test_pdf" => [ "menu_title" => $this->__("PDF example"), "page_title" => $this->__("Iframe by slug - PDF example").': "private_file_dir" => "hello_world.pdf" | "function_load" => "show_pdf_private"', "file" => "bft-admin-display-iframe.php", "iframe_admin_page_slug" => "iframe_test_pdf", "private_file_dir" => "hello_world.pdf", ], "iframe_test_pdf" => [ "tab_show" => false, "function_load" => "show_pdf_private", ],
Language functions
-
$this->languages_codes_names_get()
Returns a language list
$languages_codes_names = [ 'ab' => $this->__('Abkhazian'), 'aa' => $this->__('Afar'), 'af' => $this->__('Afrikaans'), 'ak' => $this->__('Akan'), 'sq' => $this->__('Albanian'), 'am' => $this->__('Amharic'), [...]
-
$this->languages_get($country_code)
Returns the data saved in the $this- setting>option_field_get(“languages”)
-
$this->language_admin_get($country_code)
Returns the data saved in the $this- setting>option_field_get(“language_admin”) if exists in $this->option_field_get(“languages”)
- If languages is empty it will set the languages 'en’ and 'es’
- If language_admin is empty or not found in the languages, it will set the first language stored in languages
Country functions
-
$this->countries_codes_names_get()
Returns a country list
$countries_codes_names = [ 'AF'=> $this->__('Afghanistan'), 'AX'=> $this->__('Aland Islands'), 'AL'=> $this->__('Albania'), 'DZ'=> $this->__('Algeria'), 'AS'=> $this->__('American Samoa'), 'AD'=> $this->__('Andorra'), [...]
-
$this->country_code_name_get($country_code)
Returns the country name via country code
Currency functions
-
$this->currencies_array_get()
Returns to a currency list with all data, including the numer of currency on the ISO 4217 standard 4217
$currencies_name_and_symbol = [ 'ARS' => [ 'id' => 'ARS', 'name' => 'Argentina Peso', 'symbol' => '$', 'code' => '032', ], 'AWG' => [ 'id' => 'AWG', 'name' => 'Aruba Guilder', 'symbol' => 'ƒ', 'code' => '533', ], [...]
-
$this->currencies_selector_get()
Returns a currency list
$currencies_name_and_symbol = [ 'ALL' => 'L - Albania Lek', 'AFN' => '؋ Afghanistan Afghani', 'ARS' => '$ Argentina Peso', 'AWG' => 'ƒ Aruba Guilder', [...]
-
$this->currency_symbol_get($currency_id)
Returns the currency symbol through the currency code
-
$this->currency_code_get($currency_id)
Returns a the ISO 4217 number 4217 through the currency id
Shortcode system
-
Defining a shortcode
The shortcodes on BFT are defined on public -> class-your-plugin-admin -> shortcodes_init_plugin()
The structure of a shortcode is:
add_shortcode("shortcode-name", array($this, "shortcode_function_name"));
-
Defining a function
The structure of a shortcode function is:
public function shortcode_function_name ( $atts = [], $content = null, $tag = '' ) { }
The variables of the function are:
- $atts: array with all data specified on the shortcode
- $content: the content within the two tags, if the shortcode uses a closing tag
- $tag: the shortcode tag
-
Shortcodes uses examples
A shortcode without data in $atts and $content
[bft-shortcode-test]
Shortcode with data on $atts and $content
[bft-shortcode-test atts_data_1="Lorem ipsum" atts_data_2="Dolor sit amet"]Datos del contenido[/bft-shortcode-test]
-
Full example
public function shortcodes_init_plugin() { add_shortcode("bft-shortcode-test", array($this, "bft_shortcode_test")); }
public function bft_shortcode_test( $atts = [], $content = null, $tag = '' ) { $html_aux = ""; if (isset($atts["aditional_text"])) { $html_aux .= "<h4>".esc_html($atts["aditional_text"])."</h4>"; } if (!is_null($content)) { $html_aux .= "<p>".esc_html($content)."<p>"; } ob_start(); require plugin_dir_path( dirname( __FILE__ ) ) . "public/partials/your-plugin-shortcode-test.php"; $html = ob_get_clean(); return $html; }
-
Try it for yourself
Create a page, insert a shortcode block and put:
[bft-shortcode-test]
Or:
[bft-shortcode-test aditional_text="Esto es un texto adicional"]El texto dentro de las etiquetas[/bft-shortcode-test]
Shortcode system with AJAX
-
Initial shortcode
-
Explanation
The shortcode will not change,The only change is on the partial page, that page will have the JavaScript AJAX data
You can use your own method, but BFT uses jQuery, the functions provided by $this->bft_ajax_functions() are designed for be printed inside of a jQuery script
$this->bft_ajax_functions() provides JavaScript functions to show error on the designed container and erase the HTML data on the main container:
- bft_pro_error_wordpress_show(response_array, id_container_error, id_container_success = “”)
- bft_pro_error_conection_show(jqXHR, textStatus, errorThrown, id_container_error, id_container_success = “”)
For the WordPress part, it's necessary:
- AJAX URL, it's provided by Backend Frontend Template with the variable bft_pro_ajax_url
- Action data, from a form or created by code:
var form_data = []; var data = {}; data["name"] = 'action'; data["value"] = "bft_shortcode_ajax_test_response"; form_data.push(data);
-
Full example
<div id="bft_shortcode_ajax_test_response_container"></div> <div id="bft_shortcode_ajax_test_response_error_container"></div> <!-- <form method="post" action="#" name="bft_shortcode_ajax_test_response" class="bft_shortcode_ajax_test_response" id="bft_shortcode_ajax_test_response"> <input type="hidden" name="action" value="bft_shortcode_ajax_test_response"> </form> --> <script> (function( $ ) { "use strict"; function bft_shortcode_ajax_test_function () { var form_data = []; var data = {}; data["name"] = "action"; data["value"] = "bft_shortcode_ajax_test_response"; form_data.push(data); //var form_data = jQuery("#bft_shortcode_ajax_test_response").serializeArray(); jQuery.ajax({ url : your_plugin_ajax_url, type : "post", data : form_data, success : function( response ) { console.log("<?=$this->__("AJAX successful")?>") console.log(response); if (typeof response.success !== "undefined" && false === false) { console.log("<?=$this->__("AJAX failed")?>"); <?=$this->plugin_slug?>_error_wordpress_show(response, "bft_shortcode_ajax_test_response_error_container", "bft_shortcode_ajax_test_response_container"); } else { $("#bft_shortcode_ajax_test_response_container").html(response.data.html); } }, fail : function( jqXHR, textStatus, errorThrown) { console.log("<?=$this->__("AJAX failed")?>"); <?=$this->plugin_slug?>_error_conection_show(jqXHR, textStatus, errorThrown, "bft_shortcode_ajax_test_response_error_container", "bft_shortcode_ajax_test_response_container"); } }); } bft_shortcode_ajax_test_function(); <?=$this->bft_ajax_functions()?> })( jQuery ); </script>
-
-
AJAX response
-
Defining a WordPress AJAX Action
The AJAX responses are defined on includes -> class-your-plugin -> define_public_hooks()
The structure of a AJAX response is double, WordPress separates the logged ussers of non logged users:
//AJAX response for logged users $this->loader->add_action( 'wp_ajax_ajax_response_function_name', $plugin_public, 'ajax_response_function_name' ); //AJAX response for non logged users $this->loader->add_action( 'wp_ajax_nopriv_ajax_response_function_name', $plugin_public, 'ajax_response_function_name' );
-
Defining a function
This function doesn't has variables, all pass data will be POST data
For preparing the return data, BFT uses this structure:
$data = array(); $data["html"] = $html; $return = [ "error" => [], "data" => $data, ];
For sending the data, the function will use the WordPress functions wp_send_json() and wp_die()
wp_send_json($return); wp_die();
-
Returning an error
Backend Frontend Template provides the function $this->error_send_json(“Error text”)
$this->error_send_json() will return the JSON and will stop the execution of the code
-
Full example
private function define_public_hooks() { //bft_shortcode_ajax_test_response $this->loader->add_action( 'wp_ajax_bft_shortcode_ajax_test_response', $plugin_public, 'bft_shortcode_ajax_test_response' ); $this->loader->add_action( 'wp_ajax_nopriv_bft_shortcode_ajax_test_response', $plugin_public, 'bft_shortcode_ajax_test_response' ); }
public function bft_shortcode_ajax_test_response( ) { ob_start(); require plugin_dir_path( dirname( __FILE__ ) ) . "public/partials/your-plugin-shortcode-ajax-test-response.php"; $html = ob_get_clean(); $data = array(); $data["html"] = $html; $return = [ "error" => [], "data" => $data, ]; wp_send_json($return); wp_die(); return $html; }
-
-
Try it for yourself
Create a page, insert a shortcode block and put
[bft-shortcode-ajax-test]
Shortcode system with an AJAX response form
-
Initial shortcode
-
Explanation
On the HTML data will be a form with the 'action', data, the data will be read with jQuery(“”).serializeArray()
var form_data = jQuery("#bft_shortcode_form_test_response").serializeArray();
Also, the form data will have a nonce if the user is logged, for additional security
$script_push_nonce = ""; if(is_user_logged_in()) { $script_push_nonce = 'form_data.push( { "name" : "'.$this->plugin_slug.'_security", "value" : '.$this->plugin_slug.'_ajax_nonce } );'; }
-
Full example
public function bft_shortcode_form_test( $atts = [], $content = null, $tag = '' ) { $script_push_nonce = ""; if(is_user_logged_in()) { $script_push_nonce = 'form_data.push( { "name" : "'.$this->plugin_slug.'_security", "value" : '.$this->plugin_slug.'_ajax_nonce } );'; } ob_start(); require plugin_dir_path( dirname( __FILE__ ) ) . "public/partials/bft-shortcode-form-test-static.php"; $html = ob_get_clean(); return $html; }
HTML
<form method="post" action="#" name="bft_shortcode_form_test_response" class="bft_shortcode_form_test_response" id="bft_shortcode_form_test_response"> <input type="hidden" name="action" value="bft_shortcode_form_test_response"> <div> <label><?=$this->__("Name")?></label> <input type="text" name="bft_shortcode_form_test_response_name" id="bft_shortcode_form_test_response_name" value=""> </div> <div> <label><?=$this->__("Surname")?></label> <input type="text" name="bft_shortcode_form_test_response_surname" id="bft_shortcode_form_test_response_surname" value=""> </div> <div> <input type="submit" value="<?=$this->__("Test form")?>" id="bft_shortcode_form_test_response_submit"> </div> </form> <div id="bft_shortcode_form_test_response_container"></div> <div id="bft_shortcode_form_test_response_error_container"></div> <script> (function( $ ) { "use strict"; $(document).on("submit", ".bft_shortcode_form_test_response", function(e) { e.preventDefault(); var form_data = jQuery("#bft_shortcode_form_test_response").serializeArray(); <?=$script_push_nonce?> $("#bft_shortcode_form_test_response_container").html(""); $("#bft_shortcode_form_test_response_error_container").text(""); jQuery.ajax({ url : your_plugin_ajax_url, type : "post", data : form_data, success : function( response ) { console.log("<?=$this->__("AJAX successful")?>") if (typeof response.success !== "undefined" && false === false) { console.log("<?=$this->__("AJAX failed")?>"); <?=$this->plugin_slug?>_error_wordpress_show(response, "bft_shortcode_form_test_response_error_container", "bft_shortcode_form_test_response_container"); } else { $("#bft_shortcode_form_test_response_container").html(response.data.html); } }, fail : function( jqXHR, textStatus, errorThrown) { console.log("<?=$this->__("AJAX failed")?>"); <?=$this->plugin_slug?>_error_conection_show(jqXHR, textStatus, errorThrown, "bft_shortcode_form_test_response_error_container", "bft_shortcode_form_test_response_container"); } }); }); <?=$this->bft_ajax_functions()?> })( jQuery ); </script>
-
-
AJAX response
-
Explanation
An AJAX form will check the nonce for logged users before use the data with check_ajax_referer(), if the nonce fails the execution will stop
if(is_user_logged_in()) { check_ajax_referer($this->plugin_slug."_form_public_nonce", $this->plugin_slug."_security" ); } unset($_POST["action"]); unset($_POST[$this->plugin_slug."_security"]);
-
Full example
private function define_public_hooks() { //bft_shortcode_form_test_response $this->loader->add_action( 'wp_ajax_bft_shortcode_form_test_response', $plugin_public, 'bft_shortcode_form_test_response' ); $this->loader->add_action( 'wp_ajax_nopriv_bft_shortcode_form_test_response', $plugin_public, 'bft_shortcode_form_test_response' ); }
public function bft_shortcode_form_test_response( ) { if(is_user_logged_in()) { check_ajax_referer($this->plugin_slug."_form_public_nonce", $this->plugin_slug."_security" ); } unset($_POST["action"]); unset($_POST[$this->plugin_slug."_security"]); if ( !isset($_POST["bft_shortcode_form_test_response_name"]) || !isset($_POST["bft_shortcode_form_test_response_surname"])) { $this->error_send_json($this->__("An error of validation ocurred, contact the admin of the site")); } ob_start(); require plugin_dir_path( dirname( __FILE__ ) ) . "public/partials/bft-shortcode-form-test-response.php"; $html = ob_get_clean(); $data = array(); $data["html"] = $html; $return = [ "error" => [], "data" => $data, ]; wp_send_json($return); wp_die(); return $html; }
-
-
Try it for yourself
Create a page, insert a shortcode block and put
[bft-shortcode-form-test]
Your first edited page
Changelog
1.1.0 | 2023-10-02
Changed
- Example images are now loaded from moisesbarrachina.online to make the plugin lighter
- Improved explanation and readability of texts
Corrected
- Fixed typos
1.0.0 | 2023-09-14
Added
- Menu system with children
- Settings system
- Ipunts system:text, textarea, number, select, select multiple, date, checkbox, checkbox multiple, radio, image, file and commented input
- Database example
- Install and uninstall the plugin database
- Automated data manipulation
- Manual data manipulation
- Paginated listing by query
- Direct listing by array
- Manual form
- Log manipulation
- Error manipulation
- Internalization
- Download system for private files
- Iframe system
- PDF by iframe system
- Frontend system by shortcode
- AJAX system by frontend
- Internalization into Spanish
Blog sobre Backend Frontend Template Pro
How to create a WordPress plugin step by step, part 4
Create tables in the database on plugin installation and delete all tables on plugin uninstall
Program card payments through RedSys coming soon with BFT Pro
The next update to Backend Frontend Template Pro is now live: the WordPress Plugin Template, In it, you can easily add RedSys payments to your plugin, Schedule your clients your own WordPress booking plugin and add payment to it!.
How to create a WordPress plugin step by step, part 3
Saves data without creating tables in the database, just using the WordPress settings system
How to create a WordPress plugin step by step, part 2
Create custom functions for your admin pages and find out how to check your WordPress log data or errors
How to create a WordPress plugin step by step, part 1
Installing the plugin/template/library, create your administration pages and your own tags/shortcodes
Reviews
There are no reviews yet.