diff --git a/application/components/extensions/.placeholder b/application/components/extensions/.placeholder new file mode 100644 index 000000000..e69de29bb diff --git a/application/helpers/hlp_common_helper.php b/application/helpers/hlp_common_helper.php index 7b937d73d..c7440bfa9 100644 --- a/application/helpers/hlp_common_helper.php +++ b/application/helpers/hlp_common_helper.php @@ -132,9 +132,9 @@ function loadResource($path, $resources = null, $subdir = false) while (($entry = readdir($dirHandler)) !== false) { // If entry is a directory but not the current and subdirectories should be loaded - if ($subdir === true && $entry != '.' && $entry != '..' && is_dir($entry)) + if ($subdir === true && $entry != '.' && $entry != '..' && is_dir($path.$entry)) { - $tmpPaths[] = $entry; + $tmpPaths[] = $path.$entry.'/'; } // If no resources are specified and the current file system entry is a file if ($resources == null && is_file($path.$entry)) @@ -351,3 +351,42 @@ function sanitizeProblemChars($str) return preg_replace($acentos, array_keys($acentos), htmlentities($str, ENT_NOQUOTES | ENT_HTML5, $enc)); } + +/** + * + */ +function findResource($path, $resource, $subdir = false) +{ + // Place a / character at the and of the string if not present + if (strrpos($path, '/') < strlen($path) - 1) $path .= '/'; + + // Loads in $tmpPaths path and eventually the subdirectories + $tmpPaths = array($path); + // NOTE: Used @ to prevent ugly error messages + if (is_dir($path) && ($dirHandler = @opendir($path)) !== false) + { + // Reads all file system entries present in path + while (($entry = readdir($dirHandler)) !== false) + { + // If entry is a directory but not the current and subdirectories should be loaded + if ($subdir === true && $entry != '.' && $entry != '..' && is_dir($path.$entry)) + { + $tmpPaths[] = $path.$entry.'/'; + } + } + closedir($dirHandler); + } + + // Loops through the paths + foreach ($tmpPaths as $tmpPath) + { + $fileName = $tmpPath.$resource.'.php'; // Php extension + if (file_exists($fileName)) + { + return $fileName; + } + } + + return null; +} + diff --git a/application/libraries/FilterCmptLib.php b/application/libraries/FilterCmptLib.php index b8849187f..55c6a4dbc 100644 --- a/application/libraries/FilterCmptLib.php +++ b/application/libraries/FilterCmptLib.php @@ -117,11 +117,25 @@ class FilterCmptLib // if (!$this->_checkJSParameters()) return; - // Gets the filter configuration from the file system - require_once(APPPATH.'components/filters/'.$this->_filterType.'.php'); + // + $filePath = findResource(APPPATH.'components/filters/', $this->_filterType, true); + if (!isEmptyString($filePath)) + { + // Gets the filter configuration from the file system + require_once($filePath); + } + else + { + $filePath = findResource(APPPATH.'components/extensions/', $this->_filterType, true); + if (!isEmptyString($filePath)) require_once($filePath); + } - // Gets the filter configuration from the extensions - // require_once(APPPATH.'components/extensions/'.$this->_filterType.'.php'); + // + if (!isset($filterCmptArray)) + { + $this->_setSession(error('Component definition file '.$this->_filterType.' not found')); + return; + } // if (!$this->_checkPHPParameters($filterCmptArray)) return; diff --git a/application/views/system/logs/logsViewer.php b/application/views/system/logs/logsViewer.php index 4accde521..f382165a6 100644 --- a/application/views/system/logs/logsViewer.php +++ b/application/views/system/logs/logsViewer.php @@ -1,13 +1,13 @@ 'Logs Viewer', - 'jquery3' => true, + 'axios027' => true, 'bootstrap5' => true, 'fontawesome6' => true, + 'jquery3' => true, + 'restclient' => true, 'tablesorter2' => true, 'vue3' => true, - 'ajaxlib' => true, - 'jqueryui1' => true, 'filtercomponent' => true, 'navigationcomponent' => true, 'phrases' => array( @@ -23,7 +23,7 @@
- +
diff --git a/application/views/templates/FHC-Common.php b/application/views/templates/FHC-Common.php index 6c6f58b8f..aa724def8 100644 --- a/application/views/templates/FHC-Common.php +++ b/application/views/templates/FHC-Common.php @@ -38,6 +38,7 @@ $filterwidget = isset($filterwidget) ? $filterwidget : false; $navigationcomponent = isset($navigationcomponent) ? $navigationcomponent : false; $navigationwidget = isset($navigationwidget) ? $navigationwidget : false; + $restclient = isset($restclient) ? $restclient : false; $tablecomponent = isset($tablecomponent) ? $tablecomponent : false; $tablewidget = isset($tablewidget) ? $tablewidget : false; $udfs = isset($udfs) ? $udfs : false; diff --git a/application/views/templates/FHC-Footer.php b/application/views/templates/FHC-Footer.php index 70f2f6b3f..c6e4d2a24 100644 --- a/application/views/templates/FHC-Footer.php +++ b/application/views/templates/FHC-Footer.php @@ -32,7 +32,7 @@ // From vendor folder // Axios V0.27 - if ($axios027 === true) generateJSsInclude('vendor/axios/axios/axios.min.js'); + if ($axios027 === true) generateJSsInclude('vendor/axios/axios/dist/axios.min.js'); // Bootstrap 5 JS if ($bootstrap5 === true) generateJSsInclude('vendor/twbs/bootstrap5/dist/js/bootstrap.min.js'); @@ -132,6 +132,9 @@ // PhrasesLib JS if ($phrases != null) generateJSsInclude('public/js/PhrasesLib.js'); + // RESTClient + if ($restclient === true) generateJSsInclude('public/js/RESTClient.js'); + // TableWidget JS if ($tablewidget === true) generateJSsInclude('public/js/TableWidget.js'); diff --git a/composer.json b/composer.json index e5ebd2893..84839bf70 100644 --- a/composer.json +++ b/composer.json @@ -43,8 +43,8 @@ "name": "axios/axios", "version": "0.27.2", "dist": { - "url": "https://unpkg.com/axios@0.27.2/dist/axios.min.js", - "type": "file" + "url": "https://github.com/axios/axios/archive/refs/tags/v0.27.2.zip", + "type": "zip" } } }, @@ -343,7 +343,7 @@ "afarkas/html5shiv": "3.7.*", "alvaro-prieto/colresizable": "1.6", - "axios/axios": "0.27.2", + "axios/axios": "0.27.*", "blackrockdigital/startbootstrap-sb-admin-2": "3.3.*", "borgar/textile-js": "2.0.4", diff --git a/public/js/RESTClient.js b/public/js/RESTClient.js new file mode 100644 index 000000000..eb0ebaa63 --- /dev/null +++ b/public/js/RESTClient.js @@ -0,0 +1,225 @@ +/** + * FH-Complete + * + * @package FHC-Helper + * @author FHC-Team + * @copyright Copyright (c) 2022 fhcomplete.net + * @license GPLv3 + * @link https://fhcomplete.net + * @since Version 1.0.0 + */ + +//-------------------------------------------------------------------------------------------------------------------- +// Configs + +// To see debug messages into the browser console set this parameter as true +const CORE_REST_CLIENT_DEBUG = false; + +// Default timeout (milliseconds) +const CORE_REST_CLIENT_TIMEOUT = 1000; + +//-------------------------------------------------------------------------------------------------------------------- +// Constants + +// Success +const CORE_REST_CLIENT_SUCCESS = 0; + +// Properties present in a response +const CORE_REST_CLIENT_ERROR = "error"; +const CORE_REST_CLIENT_RETVAL = "retval"; + +// HTTP method parameters +const CRC_HTTP_GET_METHOD = "get"; +const CRC_HTTP_POST_METHOD = "post"; + +/** + * Definition and initialization of the object CoreRESTClient + */ +const CoreRESTClient = { + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + /** + * Performs a call using the HTTP GET method + * wsParameters is an object + * axiosParameters is an object + */ + get: function(wsURL, wsParameters, axiosParameters = null) { + return CoreRESTClient._axiosCall(wsURL, wsParameters, CRC_HTTP_GET_METHOD, axiosParameters); + }, + + /** + * Performs a call using the HTTP POST method + * wsParameters is an object + * axiosParameters is an object + */ + post: function(wsURL, wsParameters, axiosParameters = null) { + return CoreRESTClient._axiosCall(wsURL, wsParameters, CRC_HTTP_POST_METHOD, axiosParameters); + }, + + /** + * Checks if the response is a success + */ + isSuccess: function(response) { + + if (typeof response === "object" && response.hasOwnProperty(CORE_REST_CLIENT_ERROR) + && response.hasOwnProperty(CORE_REST_CLIENT_RETVAL) && response.error == CORE_REST_CLIENT_SUCCESS) + { + return true; + } + + return false; + }, + + /** + * Checks if the response is an error + */ + isError: function(response) { + return !CoreRESTClient.isSuccess(response); + }, + + /** + * Checks if the response has data + */ + hasData: function(response) { + + if (CoreRESTClient.isSuccess(response)) + { + if ((typeof response[CORE_REST_CLIENT_RETVAL] === "object" && Object.keys(response[CORE_REST_CLIENT_RETVAL]).length > 0) + || (typeof response[CORE_REST_CLIENT_RETVAL] === "array" && response[CORE_REST_CLIENT_RETVAL].length > 0) + || (typeof response[CORE_REST_CLIENT_RETVAL] === "string" && response[CORE_REST_CLIENT_RETVAL].trim() != "") + || typeof response[CORE_REST_CLIENT_RETVAL] === "number") + { + return true; + } + } + + return false; + }, + + /** + * Retrives data from response object + */ + getData: function(response) { + + if (CoreRESTClient.hasData(response)) + { + return response[CORE_REST_CLIENT_RETVAL]; + } + + return null; + }, + + /** + * Retrives error message from response object + */ + getError: function(response) { + + if (typeof response[CORE_REST_CLIENT_RETVAL] === "object" + && Object.keys(response[CORE_REST_CLIENT_RETVAL]).length > 0 + && response.hasOwnProperty(CORE_REST_CLIENT_RETVAL)) + { + return response[CORE_REST_CLIENT_RETVAL]; + } + + return "Generic error"; + }, + + /** + * Retrives code from response object + */ + getErrorCode: function(response) { + + if (typeof response[CORE_REST_CLIENT_RETVAL] === "object" && response.hasOwnProperty(CORE_REST_CLIENT_ERROR)) + { + return response[CORE_REST_CLIENT_ERROR]; + } + + return 1; // Generic error + }, + + //------------------------------------------------------------------------------------------------------------------ + // Private methods + + /** + * Generate the router URI using the connection parameters + */ + _generateRouterURI: function(wsURL) { + var uri = null; + + // Checks if global JS object FHC_JS_DATA_STORAGE_OBJECT exists + if (typeof FHC_JS_DATA_STORAGE_OBJECT !== "undefined") + { + uri = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/" + wsURL; + } + + return uri; + }, + + /** + * Method to print debug info after a controller has been called + */ + _printDebug: function(parameters, response, errorThrown) { + + if (CORE_REST_CLIENT_DEBUG === true) // If global const CORE_REST_CLIENT_DEBUG is true, but really true! + { + // Print info about called controller + console.log("Called controller: " + parameters.remoteController); + console.log("Call parameters:"); // parameters given to this call + console.log(parameters); + + if (response != null) // if there is a response... + { + console.log("Controller Response:"); + console.log(response); // ...print it + } + if (errorThrown != null) // if there is a jQuery error... + { + console.log("jQuery error:"); + console.log(errorThrown); // ...print it + } + console.log("--------------------------------------------------------------------------------------------"); + } + }, + + /** + * Performs a call to the server were the CI PHP layer is running + * - wsURL: alias of the core controller to call + * - wsParameters: parameters to give to the called controller + * - type: POST or GET HTTP method + * - axiosParameters: an object to configure the axios call + */ + _axiosCall: function(wsURL, wsParameters, type, axiosParameters) { + + // Axios config object + let axiosCallObj = { + method: type, + url: CoreRESTClient._generateRouterURI(wsURL), + timeout: CORE_REST_CLIENT_TIMEOUT // default time out + }; + + // + if (type == CRC_HTTP_GET_METHOD) + { + axiosCallObj.params = wsParameters; + } + else + { + axiosCallObj.headers = { "Content-Type": "multipart/form-data" }; + axiosCallObj.data = wsParameters; + } + + // Check if axiosParameters is an object + if (typeof axiosParameters === "object") + { + // And then copies the its properties into axiosCallObj + for (var prop in axiosParameters) axiosCallObj[prop] = axiosParameters[prop]; + } + + console.log(axiosCallObj); + + // Perform the ajax call via axios + return axios(axiosCallObj); + } +}; + diff --git a/public/js/components/Fetch.js b/public/js/components/Fetch.js index a3a11dcfe..7e867e740 100644 --- a/public/js/components/Fetch.js +++ b/public/js/components/Fetch.js @@ -1,46 +1,66 @@ export const CoreFetchCmpt = { - props: ["apifunction"], - data: function() { - return { - loading: false, - error: null, - data: [] - }; - }, - template: ` -
- -

Loading ...

-
- -
{{ JSON.stringify(error, null, 4) }}
-
- -
{{ JSON.stringify(data, null, 4) }}
-
-
- `, - created: function () { - this.fetchData(); - }, - methods: { - fetchData: function() { - var that = this; - - this.loading = true; - this.error = null; - - this.apifunction() - .then(function(response) { - that.data = response.data; - that.$emit('datafetched', that.data.data); - }) - .catch(function(error) { - that.error = error; - }) - .finally(function() { - that.loading = false; - }); - } - } -}; \ No newline at end of file + data: function() { + return { + loading: false, + error: false, + errorMessage: null + }; + }, + emits: ['dataFetched'], + props: { + apiFunction: Function + }, + created: function () { + this.fetchData(); + }, + methods: { + fetchData: function() { + // Loader started + this.loading = true; + + // Checks if the apifunction is a callable function + if (typeof this.apiFunction == "function") + { + // Call the function stored in apiFunction + let apiFunctionResult = this.apiFunction(); + + // It is expected that the function returns a Promise + if (apiFunctionResult instanceof Promise) + { + apiFunctionResult.then(this._success).catch(this._error).then(this._finally); + } + else // otherwise display an error + { + this._setError("The called apiFunction does not return a Promise"); + } + } + else // otherwise display an error + { + this._setError("Property apiFunction is not a function"); + } + }, + _setError(errorMessage) { + this.loading = false; + this.error = true; + this.errorMessage = errorMessage; + }, + _success: function(response) { + this.$emit('dataFetched', response.data); + }, + _error: function(error) { + this._setError(error.message); + }, + _finally: function() { + this.loading = false; + } + }, + template: ` + +
Loading...
+
+ +
{{ errorMessage }}
+
+ ` +}; + diff --git a/public/js/components/Filter.js b/public/js/components/Filter.js index 9fb3015ec..5c356b5fb 100644 --- a/public/js/components/Filter.js +++ b/public/js/components/Filter.js @@ -1,3 +1,7 @@ +import {CoreFetchCmpt} from '../components/Fetch.js'; + +const CORE_FILTER_CMPT_TIMEOUT = 7000; + export const CoreFilterCmpt = { emits: ['nwNewEntry'], data() { @@ -11,9 +15,10 @@ export const CoreFilterCmpt = { notFilterFields: null }; }, - created() { - this._fetchFilterData(); + components: { + CoreFetchCmpt }, + created() {}, updated() { let filterCmptTablesorter = $("#filterTableDataset"); @@ -40,17 +45,22 @@ export const CoreFilterCmpt = { }, methods: { saveCustomFilter(el) { - FHC_AjaxClient.ajaxCallPost( - "components/Filter/saveCustomFilter", + + CoreRESTClient.post( + 'components/Filter/saveCustomFilter', { filterUniqueId: this._getCurrentPage(), filterType: this.filterType, customFilterName: document.getElementById('customFilterName').value - }, + }, { - successCallback: function(data) {console.log(data)} + timeout: CORE_FILTER_CMPT_TIMEOUT } - ); + ) + .then(function (response) { console.log(response); }) + .catch(function (error) { + console.error(error); + }); }, applyFilterFields(el) { let filterFields = []; @@ -90,117 +100,171 @@ export const CoreFilterCmpt = { filterFields.push(filterField); } - FHC_AjaxClient.ajaxCallPost( - "components/Filter/applyFilterFields", + CoreRESTClient.post( + 'components/Filter/applyFilterFields', { filterUniqueId: this._getCurrentPage(), filterType: this.filterType, filterFields: filterFields - }, + }, { - successCallback: this._fetchFilterData + timeout: CORE_FILTER_CMPT_TIMEOUT } - ); + ) + .then(this.fetchFilterData) + .catch(function (error) { + console.error(error); + }); }, addFilterField(el) { - FHC_AjaxClient.ajaxCallPost( - "components/Filter/addFilterField", - { - filterUniqueId: this._getCurrentPage(), - filterType: this.filterType, - filterField: el.currentTarget.value - }, - { - successCallback: this._fetchFilterData - } - ); - }, - addSelectedField(el) { - FHC_AjaxClient.ajaxCallPost( - "components/Filter/addSelectedField", + + CoreRESTClient.post( + 'components/Filter/addFilterField', { filterUniqueId: this._getCurrentPage(), filterType: this.filterType, selectedField: el.currentTarget.value - }, + }, { - successCallback: this._fetchFilterData + timeout: CORE_FILTER_CMPT_TIMEOUT } - ); + ) + .then(this.fetchFilterData) + .catch(function (error) { + console.error(error); + }); + }, + addSelectedField(el) { + + CoreRESTClient.post( + 'components/Filter/addSelectedField', + { + filterUniqueId: this._getCurrentPage(), + filterType: this.filterType, + selectedField: el.currentTarget.value + }, + { + timeout: CORE_FILTER_CMPT_TIMEOUT + } + ) + .then(this.fetchFilterData) + .catch(function (error) { + console.error(error); + }); }, removeSelectedField(el) { - FHC_AjaxClient.ajaxCallPost( - "components/Filter/removeSelectedField", + + CoreRESTClient.post( + 'components/Filter/removeSelectedField', { filterUniqueId: this._getCurrentPage(), filterType: this.filterType, selectedField: el.currentTarget.getAttribute('field-to-remove') - }, + }, { - successCallback: this._fetchFilterData + timeout: CORE_FILTER_CMPT_TIMEOUT } - ); + ) + .then(this.fetchFilterData) + .catch(function (error) { + console.error(error); + }); }, removeFilterField(el) { - FHC_AjaxClient.ajaxCallPost( - "components/Filter/removeFilterField", + + CoreRESTClient.post( + 'components/Filter/removeFilterField', { filterUniqueId: this._getCurrentPage(), filterType: this.filterType, filterField: el.currentTarget.getAttribute('field-to-remove') - }, + }, { - successCallback: this._fetchFilterData + timeout: CORE_FILTER_CMPT_TIMEOUT } - ); + ) + .then(this.fetchFilterData) + .catch(function (error) { + console.error(error); + }); }, fetchFilterDataById(el) { - FHC_AjaxClient.ajaxCallGet( - "components/Filter/getFilter", + + var that = this; + + CoreRESTClient.get( + 'components/Filter/getFilter', { filterUniqueId: this._getCurrentPage(), filterType: this.filterType, filter_id: el.currentTarget.getAttribute("href").substring(1) - }, + }, { - successCallback: this._render + timeout: CORE_FILTER_CMPT_TIMEOUT } - ); + ) + .then(function (response) { that.render(response.data); }) + .catch(function (error) { + console.error(error); + }); }, - _fetchFilterData() { - FHC_AjaxClient.ajaxCallGet( - "components/Filter/getFilter", + fetchFilterData() { + + var that = this; + + CoreRESTClient.get( + 'components/Filter/getFilter', { filterUniqueId: this._getCurrentPage(), filterType: this.filterType // props!! - }, + }, { - successCallback: this._render + timeout: CORE_FILTER_CMPT_TIMEOUT + } + ) + .then(function (response) { that.render(response.data); }) + .catch(function (error) { + console.error(error); + }); + }, + fetchFilterDataApi() { + + return CoreRESTClient.get( + 'components/Filter/getFilter', + { + filterUniqueId: this._getCurrentPage(), + filterType: this.filterType // props!! + }, + { + timeout: CORE_FILTER_CMPT_TIMEOUT } ); }, _getCurrentPage: function() { return FHC_JS_DATA_STORAGE_OBJECT.called_path + "/" + FHC_JS_DATA_STORAGE_OBJECT.called_method; }, - _render(data) { - - if (FHC_AjaxClient.hasData(data)) + render(response) { + + console.log('render'); + + if (CoreRESTClient.hasData(response)) { - this.dataset = FHC_AjaxClient.getData(data).dataset; - this.fields = FHC_AjaxClient.getData(data).fields; - this.selectedFields = FHC_AjaxClient.getData(data).selectedFields; + let data = CoreRESTClient.getData(response); + this.dataset = data.dataset; + this.fields = data.fields; + this.selectedFields = data.selectedFields; this.notSelectedFields = this.fields.filter(x => this.selectedFields.indexOf(x) === -1); this.filterFields = []; let tmpFilterFields = []; - for (let i = 0; i < FHC_AjaxClient.getData(data).datasetMetadata.length; i++) + for (let i = 0; i < data.datasetMetadata.length; i++) { - for (let j = 0; j < FHC_AjaxClient.getData(data).filters.length; j++) + for (let j = 0; j < data.filters.length; j++) { - if (FHC_AjaxClient.getData(data).datasetMetadata[i].name == FHC_AjaxClient.getData(data).filters[j].name) + if (data.datasetMetadata[i].name == data.filters[j].name) { - let filter = FHC_AjaxClient.getData(data).filters[j]; - filter.type = FHC_AjaxClient.getData(data).datasetMetadata[i].type; + let filter = data.filters[j]; + filter.type = data.datasetMetadata[i].type; this.filterFields.push(filter); tmpFilterFields.push(filter.name); @@ -210,12 +274,12 @@ export const CoreFilterCmpt = { } this.notFilterFields = this.fields.filter(x => tmpFilterFields.indexOf(x) === -1); - this._setFieldsToDisplay(FHC_AjaxClient.getData(data)); - this._setSideMenu(FHC_AjaxClient.getData(data)); + this._setFieldsToDisplay(data); + this._setSideMenu(data); } else { - console.error(FHC_AjaxClient.getError(data)); + console.error(CoreRESTClient.getError(response)); } }, _setFieldsToDisplay(data) { @@ -277,6 +341,9 @@ export const CoreFilterCmpt = { } }, template: ` + + +
Filter options diff --git a/public/js/components/Navigation.js b/public/js/components/Navigation.js index 538268de8..fae490b3e 100644 --- a/public/js/components/Navigation.js +++ b/public/js/components/Navigation.js @@ -1,3 +1,5 @@ +import {CoreFetchCmpt} from '../components/Fetch.js'; + export const CoreNavigationCmpt = { data() { return { @@ -5,47 +7,39 @@ export const CoreNavigationCmpt = { sideMenu: {} }; }, - created() { - this.fetchDataHeader(); - this.fetchDataMenu(); - }, + created() {}, props: { addHeaderMenuEntries: Object, addSideMenuEntries: Object }, + components: { + CoreFetchCmpt + }, methods: { getNavigationPage() { return FHC_JS_DATA_STORAGE_OBJECT.called_path + "/" + FHC_JS_DATA_STORAGE_OBJECT.called_method; }, fetchDataHeader() { - // Retrives the header menu array - FHC_AjaxClient.ajaxCallGet( + return CoreRESTClient.get( 'system/Navigation/header', { navigation_page: this.getNavigationPage() - }, - { - successCallback: this.setHeaders } ); }, setHeaders(data) { - if (FHC_AjaxClient.hasData(data)) this.headerMenu = FHC_AjaxClient.getData(data); + if (CoreRESTClient.hasData(data)) this.headerMenu = CoreRESTClient.getData(data); }, fetchDataMenu() { - // Retrives the side menu array - FHC_AjaxClient.ajaxCallGet( + return CoreRESTClient.get( 'system/Navigation/menu', { navigation_page: this.getNavigationPage() - }, - { - successCallback: this.setSideMenu } ); }, setSideMenu(data) { - if (FHC_AjaxClient.hasData(data)) this.sideMenu = FHC_AjaxClient.getData(data); + if (CoreRESTClient.hasData(data)) this.sideMenu = CoreRESTClient.getData(data); }, getDataBsToggle(header) { return !header.children ? null : 'dropdown'; @@ -69,13 +63,22 @@ export const CoreNavigationCmpt = { } }, template: ` + + + + +