From 3357f2cb40b5ce3dd25cecda544bb2592279935a Mon Sep 17 00:00:00 2001 From: Llewellyn van der Merwe Date: Mon, 18 Jan 2021 19:20:26 +0200 Subject: [PATCH] Added new filters to language translation, and fields admin list view. resolved gh-651 --- README.md | 4 +- admin/README.txt | 4 +- admin/helpers/componentbuilder.php | 452 ++++++++++++++++++ .../en-GB/en-GB.com_componentbuilder.ini | 7 + admin/models/fields.php | 148 ++++++ admin/models/fields/listfields.php | 1 + admin/models/fieldtype.php | 46 ++ admin/models/language_translations.php | 171 +++++++ componentbuilder.xml | 2 +- site/helpers/componentbuilder.php | 452 ++++++++++++++++++ .../en-GB/en-GB.com_componentbuilder.ini | 5 + 11 files changed, 1287 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6338f2836..37c00cd22 100644 --- a/README.md +++ b/README.md @@ -143,11 +143,11 @@ TODO + *Author*: [Llewellyn van der Merwe](mailto:llewellyn@joomlacomponentbuilder.com) + *Name*: [Component Builder](https://github.com/vdm-io/Joomla-Component-Builder) + *First Build*: 30th April, 2015 -+ *Last Build*: 15th January, 2021 ++ *Last Build*: 18th January, 2021 + *Version*: 2.12.5 + *Copyright*: Copyright (C) 2015 Vast Development Method. All rights reserved. + *License*: GNU General Public License version 2 or later; see LICENSE.txt -+ *Line count*: **291684** ++ *Line count*: **292966** + *Field count*: **1611** + *File count*: **1934** + *Folder count*: **322** diff --git a/admin/README.txt b/admin/README.txt index 6338f2836..37c00cd22 100644 --- a/admin/README.txt +++ b/admin/README.txt @@ -143,11 +143,11 @@ TODO + *Author*: [Llewellyn van der Merwe](mailto:llewellyn@joomlacomponentbuilder.com) + *Name*: [Component Builder](https://github.com/vdm-io/Joomla-Component-Builder) + *First Build*: 30th April, 2015 -+ *Last Build*: 15th January, 2021 ++ *Last Build*: 18th January, 2021 + *Version*: 2.12.5 + *Copyright*: Copyright (C) 2015 Vast Development Method. All rights reserved. + *License*: GNU General Public License version 2 or later; see LICENSE.txt -+ *Line count*: **291684** ++ *Line count*: **292966** + *Field count*: **1611** + *File count*: **1934** + *Folder count*: **322** diff --git a/admin/helpers/componentbuilder.php b/admin/helpers/componentbuilder.php index 819519e94..dff0bcea2 100644 --- a/admin/helpers/componentbuilder.php +++ b/admin/helpers/componentbuilder.php @@ -1971,6 +1971,458 @@ abstract class ComponentbuilderHelper return false; } + /** + * get translation extention ids + **/ + public static function getTranslationExtensionsIds($extention, $type) + { + // only allow these columns (extention types) + $columns = array( + 'joomla_component' => 'components', + 'joomla_module' => 'modules', + 'joomla_plugin' => 'plugins' + ); + // check if the column name is correct + if (isset($columns[$type])) + { + $column = $columns[$type]; + $db = JFactory::getDbo(); + $query = $db->getQuery(true); + $query + ->select($db->quoteName(array('id', $column))) + ->from($db->quoteName('#__componentbuilder_language_translation')) + ->where($db->quoteName($column) . ' != ' . $db->quote('')); + $db->setQuery($query); + $db->execute(); + if ($db->getNumRows()) + { + $results = $db->loadAssocList(); + $matches = array(); + foreach ($results as $k => $v) + { + $value = json_decode($v[$column], true); + if (in_array($extention, $value)) + { + $matches[$v['id']] = $v['id']; + } + } + // Checks that we found matches + if (self::checkArray($matches)) + { + return array_values($matches); + } + } + } + return false; + } + + /** + * get translation ids + **/ + public static function getTranslationIds($language, $translated = true) + { + $db = JFactory::getDbo(); + $query = $db->getQuery(true); + + $query + ->select($db->quoteName('id')) + ->from($db->quoteName('#__componentbuilder_language_translation')); + + // Build the where condition + if ($translated === true) // Translated + { + if ($language === 'all') + { + if (($languages = self::getAvailableLanguages()) !== false) + { + $wheres = array(); + foreach ($languages as $k => $v) + { + $wheres[] = $db->quoteName('translation') . ' LIKE ' . $db->quote('%' . $k . '%'); + } + $query->where($wheres); + } + } + else + { + $query->where($db->quoteName('translation') . ' LIKE ' . $db->quote('%' . $language . '%')); + } + } + else // Not translated + { + if ($language === 'none') + { + $query->where( + array( + $db->quoteName('translation') . ' = ' . $db->quote(''), + $db->quoteName('translation') . ' = ' . $db->quote('[]'), + $db->quoteName('translation') . ' = ' . $db->quote('{}') + ), 'OR' + ); + } + else + { + $query->where($db->quoteName('translation') . ' NOT LIKE ' . $db->quote('%' . $language . '%')); + } + } + + $db->setQuery($query); + $db->execute(); + if ($db->getNumRows()) + { + return array_unique($db->loadColumn()); + } + return false; + } + + /** + * get available languages + **/ + public static function getAvailableLanguages() + { + $db = JFactory::getDbo(); + $query = $db->getQuery(true); + $query + ->select($db->quoteName(array('langtag', 'name'))) + ->from($db->quoteName('#__componentbuilder_language')) + ->where($db->quoteName('published') . ' = 1') + ->order($db->quoteName('name') . ' desc'); + $db->setQuery($query); + $db->execute(); + if ($db->getNumRows()) + { + return $db->loadAssocList('langtag', 'name'); + } + return false; + } + + /** + * get extensions grouped list xml + **/ + public static function getExtensionGroupedListXml() + { + // the extension types + $extensions = array( + 'joomla_component' => 'COM_COMPONENTBUILDER_COMPONENT', + 'joomla_module' => 'COM_COMPONENTBUILDER_MODULE', + 'joomla_plugin' => 'COM_COMPONENTBUILDER_PLUGIN' + ); + // get the extension values + foreach ($extensions as $extension => $label) + { + ${$extension} = self::getExtensionTypeIdSystemName($extension); + } + + $xml = new DOMDocument(); + $xml->formatOutput = true; + + $root = $xml->createElement('field'); + $root->setAttributeNode(new DOMAttr('name', 'extension')); + $root->setAttributeNode(new DOMAttr('type', 'groupedlist')); + $root->setAttributeNode(new DOMAttr('onchange', 'this.form.submit();')); + + $root + ->appendChild($xml->createElement('option', '- ' . JText::_('COM_COMPONENTBUILDER_SELECT_EXTENSION') . ' -')) + ->setAttributeNode(new DOMAttr('value', '')); + + foreach ($extensions as $extension => $label) + { + $extension_node = $xml->createElement('group'); + $extension_node->setAttributeNode(new DOMAttr('label', $label)); + if (!self::checkArray(${$extension})) + { + $extension_node + ->appendChild($xml->createElement('option', '- ' . JText::_('COM_COMPONENTBUILDER_NONE') . ' -')) + ->setAttributeNode(new DOMAttr('disabled', 'true')); + } + else + { + foreach (${$extension} as $id => $element) + { + $extension_node + ->appendChild($xml->createElement('option', $element)) + ->setAttributeNode(new DOMAttr('value', $extension . '__' . $id)); + } + } + $root->appendChild($extension_node); + } + $xml->appendChild($root); + return $xml->saveXML(); + } + + /** + * get extentions ids and system names + **/ + public static function getExtensionTypeIdSystemName($type, $limiter = null) + { + $db = JFactory::getDbo(); + $query = $db->getQuery(true); + + $query + ->select($db->quoteName(array('id', 'system_name'))) + ->from($db->quoteName('#__componentbuilder_' . $type)) + ->where($db->quoteName('published') . ' >= 1') + ->order($db->quoteName('modified') . ' desc') + ->order($db->quoteName('created') . ' desc'); + // check if we have a limter for admin views + if ($type === 'admin_view' && $limiter) + { + // first get all views + $adminviewIds = array(); + // if this is a plugin or a module, then no views + if (strpos($limiter, 'joomla_component') !== false) + { + $component = (int) str_replace('joomla_component__', '', $limiter); + // get the views of this component + if ($addViews = self::getVar('component_admin_views', (int) $component, 'joomla_component', 'addadmin_views')) + { + if (self::checkJson($addViews)) + { + $addViews = json_decode($addViews, true); + if (self::checkArray($addViews)) + { + foreach($addViews as $addView) + { + if (isset($addView['adminview'])) + { + $adminviewIds[(int) $addView['adminview']] = (int) $addView['adminview']; + } + } + } + } + } + } + // now check if we still have admin views + if (self::checkArray($adminviewIds)) + { + $query->where($db->quoteName('id') . ' IN (' . implode(',', $adminviewIds) . ')'); + } + else + { + return false; + } + } + $db->setQuery($query); + $db->execute(); + if ($db->getNumRows()) + { + return $db->loadAssocList('id', 'system_name'); + } + return false; + } + + /** + * get any extension field IDs + */ + public static function getExtensionFieldIDs($extension, $type) + { + // yes we use switches + switch ($type) + { + case 'joomla_component': + return self::getComponentFieldsIDs($extension); + break; + case 'joomla_module': + return self::getModuleFieldsIDs($extension); + break; + case 'joomla_plugin': + return self::getPluginFieldsIDs($extension); + break; + case 'admin_view': + return self::getAdminViewFieldsIDs($extension); + break; + default: + return false; + break; + } + } + + /** + * get a component fields IDs + */ + public static function getComponentFieldsIDs($id) + { + // we start the field array + $fieldIds = array(); + // first get all views + $adminviewIds = array(); + // get the views of this component + if ($addViews = self::getVar('component_admin_views', (int) $id, 'joomla_component', 'addadmin_views')) + { + if (self::checkJson($addViews)) + { + $addViews = json_decode($addViews, true); + if (self::checkArray($addViews)) + { + foreach($addViews as $addView) + { + if (isset($addView['adminview'])) + { + $adminviewIds[(int) $addView['adminview']] = (int) $addView['adminview']; + } + } + } + } + } + // check that we have views + if (self::checkArray($adminviewIds)) + { + foreach ($adminviewIds as $adminView) + { + // get all the fields linked to the admin view + if ($addFields = self::getVar('admin_fields', (int) $adminView, 'admin_view', 'addfields')) + { + if (self::checkJson($addFields)) + { + $addFields = json_decode($addFields, true); + if (self::checkArray($addFields)) + { + foreach($addFields as $addField) + { + if (isset($addField['field'])) + { + $fieldIds[(int) $addField['field']] = (int) $addField['field']; + } + } + } + } + } + } + } + // get config values + if ($addconfig = self::getVar('component_config', (int) $id, 'joomla_component', 'addconfig')) + { + if (self::checkJson($addconfig)) + { + $addconfig = json_decode($addconfig, true); + if (self::checkArray($addconfig)) + { + foreach($addconfig as $addconf) + { + if (isset($addconf['field'])) + { + $fieldIds[(int) $addconf['field']] = (int) $addconf['field']; + } + } + } + } + } + // check that we have fields + if (self::checkArray($fieldIds)) + { + return array_values($fieldIds); + } + return false; + } + + /** + * get a module fields IDs + */ + public static function getModuleFieldsIDs($id) + { + // we start the field array + $fieldIds = array(); + if ($fields = self::getVar('joomla_module', (int) $id, 'id', 'fields')) + { + if (self::checkJson($fields)) + { + $fields = json_decode($fields, true); + if (self::checkArray($fields)) + { + foreach($fields as $form) + { + if (isset($form['fields']) && self::checkArray($form['fields'])) + { + foreach ($form['fields'] as $field) + { + if (isset($field['field'])) + { + $fieldIds[(int) $field['field']] = (int) $field['field']; + } + } + } + } + } + } + } + // check that we have fields + if (self::checkArray($fieldIds)) + { + return array_values($fieldIds); + } + return false; + } + + /** + * get a plugin fields IDs + */ + public static function getPluginFieldsIDs($id) + { + // we start the field array + $fieldIds = array(); + if ($fields = self::getVar('joomla_plugin', (int) $id, 'id', 'fields')) + { + if (self::checkJson($fields)) + { + $fields = json_decode($fields, true); + if (self::checkArray($fields)) + { + foreach($fields as $form) + { + if (isset($form['fields']) && self::checkArray($form['fields'])) + { + foreach ($form['fields'] as $field) + { + if (isset($field['field'])) + { + $fieldIds[(int) $field['field']] = (int) $field['field']; + } + } + } + } + } + } + } + // check that we have fields + if (self::checkArray($fieldIds)) + { + return array_values($fieldIds); + } + return false; + } + + /** + * get an admin view fields IDs + */ + public static function getAdminViewFieldsIDs($id) + { + // we start the field array + $fieldIds = array(); + // get all the fields linked to the admin view + if ($addFields = self::getVar('admin_fields', (int) $id, 'admin_view', 'addfields')) + { + if (self::checkJson($addFields)) + { + $addFields = json_decode($addFields, true); + if (self::checkArray($addFields)) + { + foreach($addFields as $addField) + { + if (isset($addField['field'])) + { + $fieldIds[(int) $addField['field']] = (int) $addField['field']; + } + } + } + } + } + // check that we have fields + if (self::checkArray($fieldIds)) + { + return array_values($fieldIds); + } + return false; + } /** * The array of dynamic content * diff --git a/admin/language/en-GB/en-GB.com_componentbuilder.ini b/admin/language/en-GB/en-GB.com_componentbuilder.ini index 97ccf25f3..287b5df7d 100644 --- a/admin/language/en-GB/en-GB.com_componentbuilder.ini +++ b/admin/language/en-GB/en-GB.com_componentbuilder.ini @@ -1397,6 +1397,7 @@ COM_COMPONENTBUILDER_ALREADY_SELECTED_TRY_ANOTHER="Already selected, try another COM_COMPONENTBUILDER_ALREADY_TRANSLATED_INTO="Already translated into" COM_COMPONENTBUILDER_ALWAYS_ADD="Always Add" COM_COMPONENTBUILDER_ALWAYS_INSURE_THAT_YOU_HAVE_YOUR_LOCAL_COMPONENTS_BACKED_UP_BY_MAKING_AN_EXPORT_OF_ALL_YOUR_LOCAL_COMPONENTS_BEFORE_IMPORTING_ANY_NEW_COMPONENTS_SMALLMAKE_BSUREB_TO_MOVE_THIS_ZIPPED_BACKUP_PACKAGE_OUT_OF_THE_TMP_FOLDER_BEFORE_DOING_AN_IMPORTSMALLBR_IF_YOU_ARE_IMPORTING_A_PACKAGE_OF_A_THREERD_PARTY_JCB_PACKAGE_DEVELOPER_BMAKE_SURE_IT_IS_A_REPUTABLE_JCB_PACKAGE_DEVELOPERSB_A_SFIND_OUT_WHYA="Always insure that you have your local components backed up, by making an export of all your local components before importing any new components. (Make SURE to move this zipped backup package out of the tmp folder before doing an import)
If you are importing a package of a 3rd party JCB package developer, make sure it is a reputable JCB package developers! Find out why!" +COM_COMPONENTBUILDER_ANY_LANGUAGE="Any language" COM_COMPONENTBUILDER_ANY_SELECTION_ONLY_FOUR_LISTRADIOCHECKBOXESDYNAMIC_LIST="Any Selection (only 4 list/radio/checkboxes/dynamic_list)" COM_COMPONENTBUILDER_AN_ERROR_HAS_OCCURRED="An error has occurred" COM_COMPONENTBUILDER_ARCHIVED="Archived" @@ -4835,6 +4836,7 @@ COM_COMPONENTBUILDER_ERROR_THE_PATH_HAS_A_MISMATCH_AND_COULD_THEREFORE_NOT_RETRI COM_COMPONENTBUILDER_ERROR_THE_SNIPPET_IS_FAULTY_AND_COULD_NOT_BE_SAVED="Error! The snippet is faulty and could not be saved." COM_COMPONENTBUILDER_ERROR_YOU_DO_NOT_HAVE_ACCESS_TO_THE_SNIPPETS="Error! You do not have access to the snippets." COM_COMPONENTBUILDER_ERROR_YOU_DO_NOT_HAVE_PERMISSION_TO_CREATE_THE_SNIPPET="Error! You do not have permission to create the snippet." +COM_COMPONENTBUILDER_EVERY_LANGUAGE="Every language" COM_COMPONENTBUILDER_EXACT_LENGTH_ONLY_FOUR_TEXT_FIELD="Exact Length (only 4 text_field)" COM_COMPONENTBUILDER_EXAMPLE="Example" COM_COMPONENTBUILDER_EXIT_TRASH="Exit trash" @@ -8077,7 +8079,9 @@ COM_COMPONENTBUILDER_NOTICE_BOARD="Notice Board" COM_COMPONENTBUILDER_NOTRANSLATION="no-translation" COM_COMPONENTBUILDER_NOT_FOUND_OR_ACCESS_DENIED="Not found, or access denied." COM_COMPONENTBUILDER_NOT_SET="not set" +COM_COMPONENTBUILDER_NOT_TRANSLATED_IN="Not translated in" COM_COMPONENTBUILDER_NO_ACCESS_GRANTED="No Access Granted!" +COM_COMPONENTBUILDER_NO_ADMIN_VIEWS_FOUND="No Admin Views Found" COM_COMPONENTBUILDER_NO_COMPONENTS_WERE_SELECTED_PLEASE_MAKE_A_SELECTION_AND_TRY_AGAIN="No components were selected, please make a selection and try again!" COM_COMPONENTBUILDER_NO_COMPONENT_DETAILS_FOUND_SO_IT_IS_NOT_SAFE_TO_CONTINUE="No component details found, so it is not safe to continue!" COM_COMPONENTBUILDER_NO_COMPONENT_WAS_SELECTED_PLEASE_MAKE_A_SELECTION_OF_ONE_COMPONENT_AND_TRY_AGAIN="No component was selected, please make a selection of one component and try again!" @@ -8276,9 +8280,11 @@ COM_COMPONENTBUILDER_SBR_YOU_CAN_ADD_A_BGITHUB_ACCESS_TOKENB_TO_COMPONENTBUILDER COM_COMPONENTBUILDER_SEARCHABLE="Searchable" COM_COMPONENTBUILDER_SEE_ALL_IMPORT_INFO="See All Import Info" COM_COMPONENTBUILDER_SELECTION="selection" +COM_COMPONENTBUILDER_SELECT_ADMIN_VIEW="Select Admin View" COM_COMPONENTBUILDER_SELECT_AN_OPTION="Select an option" COM_COMPONENTBUILDER_SELECT_A_PROPERTY="Select a property" COM_COMPONENTBUILDER_SELECT_A_SNIPPET="select a snippet" +COM_COMPONENTBUILDER_SELECT_EXTENSION="Select extension" COM_COMPONENTBUILDER_SELECT_THE_COMPONENT_TO_COMPILE="Select the component to compile" COM_COMPONENTBUILDER_SELECT_THE_COMPONENT_YOUR_WOULD_LIKE_TO_IMPORT="Select the component your would like to import." COM_COMPONENTBUILDER_SELECT_THE_PACKAGE_TO_IMPORT="Select the package to import" @@ -9358,6 +9364,7 @@ COM_COMPONENTBUILDER_TO_CHANGE_THE_PACKAGE_OWNER_DEFAULTS_OPEN_THE_JCB_GLOBAL_OP COM_COMPONENTBUILDER_TO_SHARE_THESE_SNIPPETS_WITH_THE_REST_OF_THE_JCB_COMMUNITY="To share these snippets with the rest of the JCB community," COM_COMPONENTBUILDER_TO_SHARE_THIS_SNIPPET_WITH_THE_REST_OF_THE_JCB_COMMUNITY="To share this snippet with the rest of the JCB community," COM_COMPONENTBUILDER_TRANSLATE="Translate" +COM_COMPONENTBUILDER_TRANSLATED_IN="Translated in" COM_COMPONENTBUILDER_TRANSLATION_FAILED_SINCE_THERE_ARE_NO_COMPONENTS_LINKED_WITH_TRANSLATION_TOOLS="Translation failed, since there are no components linked with translation tools!" COM_COMPONENTBUILDER_TRASHED="Trashed" COM_COMPONENTBUILDER_TRASHED_ITEMS="Trashed items" diff --git a/admin/models/fields.php b/admin/models/fields.php index fe8d8f80a..63901ad6d 100644 --- a/admin/models/fields.php +++ b/admin/models/fields.php @@ -43,6 +43,62 @@ class ComponentbuilderModelFields extends JModelList } parent::__construct($config); + } + + /** + * Get the filter form - Override the parent method + * + * @param array $data data + * @param boolean $loadData load current data + * + * @return \JForm|boolean The \JForm object or false on error + * + * @since JCB 2.12.5 + */ + public function getFilterForm($data = array(), $loadData = true) + { + // load form from the parent class + $form = parent::getFilterForm($data, $loadData); + + // Create the "extension" filter + $form->setField(new SimpleXMLElement( + ComponentbuilderHelper::getExtensionGroupedListXml() + ),'filter'); + $form->setValue( + 'extension', + 'filter', + $this->state->get("filter.extension") + ); + array_push($this->filter_fields, 'extension'); + + // Create the "admin_view" filter + $attributes = array( + 'name' => 'admin_view', + 'type' => 'list', + 'onchange' => 'this.form.submit();', + ); + $options = array( + '' => '- ' . JText::_('COM_COMPONENTBUILDER_NO_ADMIN_VIEWS_FOUND') . ' -' + ); + // check if we have admin views (and limit to an extension if it is set) + if (($admin_views = ComponentbuilderHelper::getExtensionTypeIdSystemName('admin_view', $this->state->get("filter.extension"))) !== false) + { + $options = array( + '' => '- ' . JText::_('COM_COMPONENTBUILDER_SELECT_ADMIN_VIEW') . ' -' + ); + // make sure we do not lose the key values in normal merge + $options = $options + $admin_views; + } + + $form->setField(ComponentbuilderHelper::getFieldXML($attributes, $options),'filter'); + $form->setValue( + 'admin_view', + 'filter', + $this->state->get("filter.admin_view") + ); + array_push($this->filter_fields, 'admin_view'); + + return $form; } /** @@ -311,6 +367,52 @@ class ComponentbuilderModelFields extends JModelList $query->from($db->quoteName('#__componentbuilder_field', 'a')); $query->join('LEFT', $db->quoteName('#__categories', 'c') . ' ON (' . $db->quoteName('a.catid') . ' = ' . $db->quoteName('c.id') . ')'); + // do not use these filters in the export method + if (!isset($_export) || !$_export) + { + // Filtering "extension" + $filter_extension = $this->state->get("filter.extension"); + $field_ids = array(); + $get_ids = true; + if ($get_ids && $filter_extension !== null && !empty($filter_extension)) + { + // column name, and id + $type_extension = explode('__', $filter_extension); + if (($ids = ComponentbuilderHelper::getExtensionFieldIDs($type_extension[1], $type_extension[0])) !== false) + { + $field_ids = $ids; + } + else + { + // there is none + $query->where($db->quoteName('a.id') . ' = ' . 0); + $get_ids = false; + } + } + + // Filtering "admin_view" + $filter_admin_view = $this->state->get("filter.admin_view"); + if ($get_ids && $filter_admin_view !== null && !empty($filter_admin_view)) + { + if (($ids = ComponentbuilderHelper::getExtensionFieldIDs($filter_admin_view, 'admin_view')) !== false) + { + // view will return less fields, so we ignore the component + $field_ids = $ids; + } + else + { + // there is none + $query->where($db->quoteName('a.id') . ' = ' . 0); + $get_ids = false; + } + } + // now check if we have IDs + if ($get_ids && ComponentbuilderHelper::checkArray($field_ids)) + { + $query->where($db->quoteName('a.id') . ' IN (' . implode(',', $field_ids) . ')'); + } + } + // From the componentbuilder_fieldtype table. $query->select($db->quoteName('g.name','fieldtype_name')); $query->join('LEFT', $db->quoteName('#__componentbuilder_fieldtype', 'g') . ' ON (' . $db->quoteName('a.fieldtype') . ' = ' . $db->quoteName('g.id') . ')'); @@ -529,6 +631,52 @@ class ComponentbuilderModelFields extends JModelList { $query->where('a.id IN (' . implode(',',$pks) . ')'); } + + // do not use these filters in the export method + if (!isset($_export) || !$_export) + { + // Filtering "extension" + $filter_extension = $this->state->get("filter.extension"); + $field_ids = array(); + $get_ids = true; + if ($get_ids && $filter_extension !== null && !empty($filter_extension)) + { + // column name, and id + $type_extension = explode('__', $filter_extension); + if (($ids = ComponentbuilderHelper::getExtensionFieldIDs($type_extension[1], $type_extension[0])) !== false) + { + $field_ids = $ids; + } + else + { + // there is none + $query->where($db->quoteName('a.id') . ' = ' . 0); + $get_ids = false; + } + } + + // Filtering "admin_view" + $filter_admin_view = $this->state->get("filter.admin_view"); + if ($get_ids && $filter_admin_view !== null && !empty($filter_admin_view)) + { + if (($ids = ComponentbuilderHelper::getExtensionFieldIDs($filter_admin_view, 'admin_view')) !== false) + { + // view will return less fields, so we ignore the component + $field_ids = $ids; + } + else + { + // there is none + $query->where($db->quoteName('a.id') . ' = ' . 0); + $get_ids = false; + } + } + // now check if we have IDs + if ($get_ids && ComponentbuilderHelper::checkArray($field_ids)) + { + $query->where($db->quoteName('a.id') . ' IN (' . implode(',', $field_ids) . ')'); + } + } // Implement View Level Access if (!$user->authorise('core.options', 'com_componentbuilder')) { diff --git a/admin/models/fields/listfields.php b/admin/models/fields/listfields.php index f530318e2..43aada5e3 100644 --- a/admin/models/fields/listfields.php +++ b/admin/models/fields/listfields.php @@ -53,6 +53,7 @@ class JFormFieldListfields extends JFormFieldList // get the admin view ID $adminView = $jinput->getInt('refid', 0); } + // make sure we have the admin view ID if (is_numeric($adminView) && $adminView >= 1) { // get all the fields linked to the admin view diff --git a/admin/models/fieldtype.php b/admin/models/fieldtype.php index 360f4f4f2..ca1a56a5d 100644 --- a/admin/models/fieldtype.php +++ b/admin/models/fieldtype.php @@ -265,6 +265,52 @@ class ComponentbuilderModelFieldtype extends JModelAdmin $query->from($db->quoteName('#__componentbuilder_field', 'a')); $query->join('LEFT', $db->quoteName('#__categories', 'c') . ' ON (' . $db->quoteName('a.catid') . ' = ' . $db->quoteName('c.id') . ')'); + // do not use these filters in the export method + if (!isset($_export) || !$_export) + { + // Filtering "extension" + $filter_extension = $this->state->get("filter.extension"); + $field_ids = array(); + $get_ids = true; + if ($get_ids && $filter_extension !== null && !empty($filter_extension)) + { + // column name, and id + $type_extension = explode('__', $filter_extension); + if (($ids = ComponentbuilderHelper::getExtensionFieldIDs($type_extension[1], $type_extension[0])) !== false) + { + $field_ids = $ids; + } + else + { + // there is none + $query->where($db->quoteName('a.id') . ' = ' . 0); + $get_ids = false; + } + } + + // Filtering "admin_view" + $filter_admin_view = $this->state->get("filter.admin_view"); + if ($get_ids && $filter_admin_view !== null && !empty($filter_admin_view)) + { + if (($ids = ComponentbuilderHelper::getExtensionFieldIDs($filter_admin_view, 'admin_view')) !== false) + { + // view will return less fields, so we ignore the component + $field_ids = $ids; + } + else + { + // there is none + $query->where($db->quoteName('a.id') . ' = ' . 0); + $get_ids = false; + } + } + // now check if we have IDs + if ($get_ids && ComponentbuilderHelper::checkArray($field_ids)) + { + $query->where($db->quoteName('a.id') . ' IN (' . implode(',', $field_ids) . ')'); + } + } + // From the componentbuilder_fieldtype table. $query->select($db->quoteName('g.name','fieldtype_name')); $query->join('LEFT', $db->quoteName('#__componentbuilder_fieldtype', 'g') . ' ON (' . $db->quoteName('a.fieldtype') . ' = ' . $db->quoteName('g.id') . ')'); diff --git a/admin/models/language_translations.php b/admin/models/language_translations.php index a0c5a1b97..42e9d77a1 100644 --- a/admin/models/language_translations.php +++ b/admin/models/language_translations.php @@ -35,6 +35,75 @@ class ComponentbuilderModelLanguage_translations extends JModelList } parent::__construct($config); + } + + /** + * Get the filter form - Override the parent method + * + * @param array $data data + * @param boolean $loadData load current data + * + * @return \JForm|boolean The \JForm object or false on error + * + * @since JCB 2.12.5 + */ + public function getFilterForm($data = array(), $loadData = true) + { + // load form from the parent class + $form = parent::getFilterForm($data, $loadData); + + // Create the "extension" filter + $form->setField(new SimpleXMLElement( + ComponentbuilderHelper::getExtensionGroupedListXml() + ),'filter'); + $form->setValue( + 'extension', + 'filter', + $this->state->get("filter.extension") + ); + array_push($this->filter_fields, 'extension'); + + // Create the "translated in" filter + $attributes = array( + 'name' => 'translated', + 'type' => 'list', + 'onchange' => 'this.form.submit();', + ); + $options = array( + '' => '- ' . JText::_('COM_COMPONENTBUILDER_TRANSLATED_IN') . ' -', + 'all' => JText::_('COM_COMPONENTBUILDER_EVERY_LANGUAGE') + ); + $options = array_merge($options, ComponentbuilderHelper::getAvailableLanguages()); + + $form->setField(ComponentbuilderHelper::getFieldXML($attributes, $options),'filter'); + $form->setValue( + 'translated', + 'filter', + $this->state->get("filter.translated") + ); + array_push($this->filter_fields, 'translated'); + + // Create the "not translated in" filter + $attributes = array( + 'name' => 'not_translated', + 'type' => 'list', + 'onchange' => 'this.form.submit();', + ); + $options = array( + '' => '- ' . JText::_('COM_COMPONENTBUILDER_NOT_TRANSLATED_IN') . ' -', + 'none' => JText::_('COM_COMPONENTBUILDER_ANY_LANGUAGE') + ); + $options = array_merge($options, ComponentbuilderHelper::getAvailableLanguages()); + + $form->setField(ComponentbuilderHelper::getFieldXML($attributes, $options),'filter'); + $form->setValue( + 'not_translated', + 'filter', + $this->state->get("filter.not_translated") + ); + array_push($this->filter_fields, 'not_translated'); + + return $form; } /** @@ -205,6 +274,57 @@ class ComponentbuilderModelLanguage_translations extends JModelList // From the componentbuilder_item table $query->from($db->quoteName('#__componentbuilder_language_translation', 'a')); + // do not use these filters in the export method + if (!isset($_export) || !$_export) + { + // Filtering "translated in" + $filter_translated = $this->state->get("filter.translated"); + if ($filter_translated !== null && !empty($filter_translated)) + { + if (($ids = ComponentbuilderHelper::getTranslationIds($filter_translated)) !== false) + { + $query->where($db->quoteName('a.id') . ' IN (' . implode(',', $ids) . ')'); + } + else + { + // there is none + $query->where($db->quoteName('a.id') . ' = ' . 0); + } + } + + // Filtering "not translated in" + $filter_not_translated = $this->state->get("filter.not_translated"); + if ($filter_not_translated !== null && !empty($filter_not_translated)) + { + if (($ids = ComponentbuilderHelper::getTranslationIds($filter_not_translated, false)) !== false) + { + $query->where($db->quoteName('a.id') . ' IN (' . implode(',',$ids) . ')'); + } + else + { + // there is none + $query->where($db->quoteName('a.id') . ' = ' . 0); + } + } + + // Filtering "extension" + $filter_extension = $this->state->get("filter.extension"); + if ($filter_extension !== null && !empty($filter_extension)) + { + // column name, and id + $type_extension = explode('__', $filter_extension); + if (($ids = ComponentbuilderHelper::getTranslationExtensionsIds($type_extension[1], $type_extension[0])) !== false) + { + $query->where($db->quoteName('a.id') . ' IN (' . implode(',', $ids) . ')'); + } + else + { + // there is none + $query->where($db->quoteName('a.id') . ' = ' . 0); + } + } + } + // Filter by published state $published = $this->getState('filter.published'); if (is_numeric($published)) @@ -312,6 +432,57 @@ class ComponentbuilderModelLanguage_translations extends JModelList { $query->where('a.id IN (' . implode(',',$pks) . ')'); } + + // do not use these filters in the export method + if (!isset($_export) || !$_export) + { + // Filtering "translated in" + $filter_translated = $this->state->get("filter.translated"); + if ($filter_translated !== null && !empty($filter_translated)) + { + if (($ids = ComponentbuilderHelper::getTranslationIds($filter_translated)) !== false) + { + $query->where($db->quoteName('a.id') . ' IN (' . implode(',', $ids) . ')'); + } + else + { + // there is none + $query->where($db->quoteName('a.id') . ' = ' . 0); + } + } + + // Filtering "not translated in" + $filter_not_translated = $this->state->get("filter.not_translated"); + if ($filter_not_translated !== null && !empty($filter_not_translated)) + { + if (($ids = ComponentbuilderHelper::getTranslationIds($filter_not_translated, false)) !== false) + { + $query->where($db->quoteName('a.id') . ' IN (' . implode(',',$ids) . ')'); + } + else + { + // there is none + $query->where($db->quoteName('a.id') . ' = ' . 0); + } + } + + // Filtering "extension" + $filter_extension = $this->state->get("filter.extension"); + if ($filter_extension !== null && !empty($filter_extension)) + { + // column name, and id + $type_extension = explode('__', $filter_extension); + if (($ids = ComponentbuilderHelper::getTranslationExtensionsIds($type_extension[1], $type_extension[0])) !== false) + { + $query->where($db->quoteName('a.id') . ' IN (' . implode(',', $ids) . ')'); + } + else + { + // there is none + $query->where($db->quoteName('a.id') . ' = ' . 0); + } + } + } // Implement View Level Access if (!$user->authorise('core.options', 'com_componentbuilder')) { diff --git a/componentbuilder.xml b/componentbuilder.xml index de4cd956f..0931fe2a5 100644 --- a/componentbuilder.xml +++ b/componentbuilder.xml @@ -1,7 +1,7 @@ COM_COMPONENTBUILDER - 15th January, 2021 + 18th January, 2021 Llewellyn van der Merwe llewellyn@joomlacomponentbuilder.com http://www.joomlacomponentbuilder.com diff --git a/site/helpers/componentbuilder.php b/site/helpers/componentbuilder.php index a69d2ca1a..b2a889b28 100644 --- a/site/helpers/componentbuilder.php +++ b/site/helpers/componentbuilder.php @@ -1968,6 +1968,458 @@ abstract class ComponentbuilderHelper return false; } + /** + * get translation extention ids + **/ + public static function getTranslationExtensionsIds($extention, $type) + { + // only allow these columns (extention types) + $columns = array( + 'joomla_component' => 'components', + 'joomla_module' => 'modules', + 'joomla_plugin' => 'plugins' + ); + // check if the column name is correct + if (isset($columns[$type])) + { + $column = $columns[$type]; + $db = JFactory::getDbo(); + $query = $db->getQuery(true); + $query + ->select($db->quoteName(array('id', $column))) + ->from($db->quoteName('#__componentbuilder_language_translation')) + ->where($db->quoteName($column) . ' != ' . $db->quote('')); + $db->setQuery($query); + $db->execute(); + if ($db->getNumRows()) + { + $results = $db->loadAssocList(); + $matches = array(); + foreach ($results as $k => $v) + { + $value = json_decode($v[$column], true); + if (in_array($extention, $value)) + { + $matches[$v['id']] = $v['id']; + } + } + // Checks that we found matches + if (self::checkArray($matches)) + { + return array_values($matches); + } + } + } + return false; + } + + /** + * get translation ids + **/ + public static function getTranslationIds($language, $translated = true) + { + $db = JFactory::getDbo(); + $query = $db->getQuery(true); + + $query + ->select($db->quoteName('id')) + ->from($db->quoteName('#__componentbuilder_language_translation')); + + // Build the where condition + if ($translated === true) // Translated + { + if ($language === 'all') + { + if (($languages = self::getAvailableLanguages()) !== false) + { + $wheres = array(); + foreach ($languages as $k => $v) + { + $wheres[] = $db->quoteName('translation') . ' LIKE ' . $db->quote('%' . $k . '%'); + } + $query->where($wheres); + } + } + else + { + $query->where($db->quoteName('translation') . ' LIKE ' . $db->quote('%' . $language . '%')); + } + } + else // Not translated + { + if ($language === 'none') + { + $query->where( + array( + $db->quoteName('translation') . ' = ' . $db->quote(''), + $db->quoteName('translation') . ' = ' . $db->quote('[]'), + $db->quoteName('translation') . ' = ' . $db->quote('{}') + ), 'OR' + ); + } + else + { + $query->where($db->quoteName('translation') . ' NOT LIKE ' . $db->quote('%' . $language . '%')); + } + } + + $db->setQuery($query); + $db->execute(); + if ($db->getNumRows()) + { + return array_unique($db->loadColumn()); + } + return false; + } + + /** + * get available languages + **/ + public static function getAvailableLanguages() + { + $db = JFactory::getDbo(); + $query = $db->getQuery(true); + $query + ->select($db->quoteName(array('langtag', 'name'))) + ->from($db->quoteName('#__componentbuilder_language')) + ->where($db->quoteName('published') . ' = 1') + ->order($db->quoteName('name') . ' desc'); + $db->setQuery($query); + $db->execute(); + if ($db->getNumRows()) + { + return $db->loadAssocList('langtag', 'name'); + } + return false; + } + + /** + * get extensions grouped list xml + **/ + public static function getExtensionGroupedListXml() + { + // the extension types + $extensions = array( + 'joomla_component' => 'COM_COMPONENTBUILDER_COMPONENT', + 'joomla_module' => 'COM_COMPONENTBUILDER_MODULE', + 'joomla_plugin' => 'COM_COMPONENTBUILDER_PLUGIN' + ); + // get the extension values + foreach ($extensions as $extension => $label) + { + ${$extension} = self::getExtensionTypeIdSystemName($extension); + } + + $xml = new DOMDocument(); + $xml->formatOutput = true; + + $root = $xml->createElement('field'); + $root->setAttributeNode(new DOMAttr('name', 'extension')); + $root->setAttributeNode(new DOMAttr('type', 'groupedlist')); + $root->setAttributeNode(new DOMAttr('onchange', 'this.form.submit();')); + + $root + ->appendChild($xml->createElement('option', '- ' . JText::_('COM_COMPONENTBUILDER_SELECT_EXTENSION') . ' -')) + ->setAttributeNode(new DOMAttr('value', '')); + + foreach ($extensions as $extension => $label) + { + $extension_node = $xml->createElement('group'); + $extension_node->setAttributeNode(new DOMAttr('label', $label)); + if (!self::checkArray(${$extension})) + { + $extension_node + ->appendChild($xml->createElement('option', '- ' . JText::_('COM_COMPONENTBUILDER_NONE') . ' -')) + ->setAttributeNode(new DOMAttr('disabled', 'true')); + } + else + { + foreach (${$extension} as $id => $element) + { + $extension_node + ->appendChild($xml->createElement('option', $element)) + ->setAttributeNode(new DOMAttr('value', $extension . '__' . $id)); + } + } + $root->appendChild($extension_node); + } + $xml->appendChild($root); + return $xml->saveXML(); + } + + /** + * get extentions ids and system names + **/ + public static function getExtensionTypeIdSystemName($type, $limiter = null) + { + $db = JFactory::getDbo(); + $query = $db->getQuery(true); + + $query + ->select($db->quoteName(array('id', 'system_name'))) + ->from($db->quoteName('#__componentbuilder_' . $type)) + ->where($db->quoteName('published') . ' >= 1') + ->order($db->quoteName('modified') . ' desc') + ->order($db->quoteName('created') . ' desc'); + // check if we have a limter for admin views + if ($type === 'admin_view' && $limiter) + { + // first get all views + $adminviewIds = array(); + // if this is a plugin or a module, then no views + if (strpos($limiter, 'joomla_component') !== false) + { + $component = (int) str_replace('joomla_component__', '', $limiter); + // get the views of this component + if ($addViews = self::getVar('component_admin_views', (int) $component, 'joomla_component', 'addadmin_views')) + { + if (self::checkJson($addViews)) + { + $addViews = json_decode($addViews, true); + if (self::checkArray($addViews)) + { + foreach($addViews as $addView) + { + if (isset($addView['adminview'])) + { + $adminviewIds[(int) $addView['adminview']] = (int) $addView['adminview']; + } + } + } + } + } + } + // now check if we still have admin views + if (self::checkArray($adminviewIds)) + { + $query->where($db->quoteName('id') . ' IN (' . implode(',', $adminviewIds) . ')'); + } + else + { + return false; + } + } + $db->setQuery($query); + $db->execute(); + if ($db->getNumRows()) + { + return $db->loadAssocList('id', 'system_name'); + } + return false; + } + + /** + * get any extension field IDs + */ + public static function getExtensionFieldIDs($extension, $type) + { + // yes we use switches + switch ($type) + { + case 'joomla_component': + return self::getComponentFieldsIDs($extension); + break; + case 'joomla_module': + return self::getModuleFieldsIDs($extension); + break; + case 'joomla_plugin': + return self::getPluginFieldsIDs($extension); + break; + case 'admin_view': + return self::getAdminViewFieldsIDs($extension); + break; + default: + return false; + break; + } + } + + /** + * get a component fields IDs + */ + public static function getComponentFieldsIDs($id) + { + // we start the field array + $fieldIds = array(); + // first get all views + $adminviewIds = array(); + // get the views of this component + if ($addViews = self::getVar('component_admin_views', (int) $id, 'joomla_component', 'addadmin_views')) + { + if (self::checkJson($addViews)) + { + $addViews = json_decode($addViews, true); + if (self::checkArray($addViews)) + { + foreach($addViews as $addView) + { + if (isset($addView['adminview'])) + { + $adminviewIds[(int) $addView['adminview']] = (int) $addView['adminview']; + } + } + } + } + } + // check that we have views + if (self::checkArray($adminviewIds)) + { + foreach ($adminviewIds as $adminView) + { + // get all the fields linked to the admin view + if ($addFields = self::getVar('admin_fields', (int) $adminView, 'admin_view', 'addfields')) + { + if (self::checkJson($addFields)) + { + $addFields = json_decode($addFields, true); + if (self::checkArray($addFields)) + { + foreach($addFields as $addField) + { + if (isset($addField['field'])) + { + $fieldIds[(int) $addField['field']] = (int) $addField['field']; + } + } + } + } + } + } + } + // get config values + if ($addconfig = self::getVar('component_config', (int) $id, 'joomla_component', 'addconfig')) + { + if (self::checkJson($addconfig)) + { + $addconfig = json_decode($addconfig, true); + if (self::checkArray($addconfig)) + { + foreach($addconfig as $addconf) + { + if (isset($addconf['field'])) + { + $fieldIds[(int) $addconf['field']] = (int) $addconf['field']; + } + } + } + } + } + // check that we have fields + if (self::checkArray($fieldIds)) + { + return array_values($fieldIds); + } + return false; + } + + /** + * get a module fields IDs + */ + public static function getModuleFieldsIDs($id) + { + // we start the field array + $fieldIds = array(); + if ($fields = self::getVar('joomla_module', (int) $id, 'id', 'fields')) + { + if (self::checkJson($fields)) + { + $fields = json_decode($fields, true); + if (self::checkArray($fields)) + { + foreach($fields as $form) + { + if (isset($form['fields']) && self::checkArray($form['fields'])) + { + foreach ($form['fields'] as $field) + { + if (isset($field['field'])) + { + $fieldIds[(int) $field['field']] = (int) $field['field']; + } + } + } + } + } + } + } + // check that we have fields + if (self::checkArray($fieldIds)) + { + return array_values($fieldIds); + } + return false; + } + + /** + * get a plugin fields IDs + */ + public static function getPluginFieldsIDs($id) + { + // we start the field array + $fieldIds = array(); + if ($fields = self::getVar('joomla_plugin', (int) $id, 'id', 'fields')) + { + if (self::checkJson($fields)) + { + $fields = json_decode($fields, true); + if (self::checkArray($fields)) + { + foreach($fields as $form) + { + if (isset($form['fields']) && self::checkArray($form['fields'])) + { + foreach ($form['fields'] as $field) + { + if (isset($field['field'])) + { + $fieldIds[(int) $field['field']] = (int) $field['field']; + } + } + } + } + } + } + } + // check that we have fields + if (self::checkArray($fieldIds)) + { + return array_values($fieldIds); + } + return false; + } + + /** + * get an admin view fields IDs + */ + public static function getAdminViewFieldsIDs($id) + { + // we start the field array + $fieldIds = array(); + // get all the fields linked to the admin view + if ($addFields = self::getVar('admin_fields', (int) $id, 'admin_view', 'addfields')) + { + if (self::checkJson($addFields)) + { + $addFields = json_decode($addFields, true); + if (self::checkArray($addFields)) + { + foreach($addFields as $addField) + { + if (isset($addField['field'])) + { + $fieldIds[(int) $addField['field']] = (int) $addField['field']; + } + } + } + } + } + // check that we have fields + if (self::checkArray($fieldIds)) + { + return array_values($fieldIds); + } + return false; + } /** * The array of dynamic content * diff --git a/site/language/en-GB/en-GB.com_componentbuilder.ini b/site/language/en-GB/en-GB.com_componentbuilder.ini index 08b50de4a..532d6f61c 100644 --- a/site/language/en-GB/en-GB.com_componentbuilder.ini +++ b/site/language/en-GB/en-GB.com_componentbuilder.ini @@ -10,6 +10,7 @@ COM_COMPONENTBUILDER_BGET_THE_KEY_FROMB_A_SSA="Get the key from %s< COM_COMPONENTBUILDER_BGET_THE_KEY_FROM_SB_FOR_A_SSA="Get the key from %s for %s" COM_COMPONENTBUILDER_CHECK_YOUR_OWNER_DETAILS_IT_HAS_NOT_BEEN_SET_OPEN_THE_JCB_GLOBAL_OPTIONS_GO_TO_THE_COMPANY_TAB_AND_ADD_THE_CORRECT_COMPANY_DETAILS_THERE="Check your owner details, it has not been set. Open the JCB Global Options, go to the Company tab and add the correct company details there." COM_COMPONENTBUILDER_COMPANY_S="Company: %s" +COM_COMPONENTBUILDER_COMPONENT="Component" COM_COMPONENTBUILDER_COMPONENT_DID_NOT_COMPILE="Component did not compile!" COM_COMPONENTBUILDER_COMPONENT_IS_NOT_PUBLISHED_OR_IS_CHECKED_OUT="Component is not published, or is checked out!" COM_COMPONENTBUILDER_COMPONENT_WAS_NOT_FOUND="Component was not found!" @@ -50,7 +51,9 @@ COM_COMPONENTBUILDER_KEY_HAS_NOT_CHANGED="Key has not changed" COM_COMPONENTBUILDER_LICENSE_S="License: %s" COM_COMPONENTBUILDER_LINK="Link" COM_COMPONENTBUILDER_LOCAL="Local" +COM_COMPONENTBUILDER_MODULE="Module" COM_COMPONENTBUILDER_NEW="New" +COM_COMPONENTBUILDER_NONE="None" COM_COMPONENTBUILDER_NOT_FOUND_OR_ACCESS_DENIED="Not found or access denied!" COM_COMPONENTBUILDER_NO_ACCESS_GRANTED="No Access Granted!" COM_COMPONENTBUILDER_NO_COMPONENT_DETAILS_FOUND_SO_IT_IS_NOT_SAFE_TO_CONTINUE="No component details found, so it is not safe to continue!" @@ -62,8 +65,10 @@ COM_COMPONENTBUILDER_PACKAGE_OWNER_DETAILS="Package Owner Details" COM_COMPONENTBUILDER_PACKAGE_OWNER_DETAILS_NOT_FOUND="Package owner details not found!" COM_COMPONENTBUILDER_PACKAGE_OWNER_NOT_SET="Package Owner Not Set" COM_COMPONENTBUILDER_PAIDLOCKED="Paid/Locked" +COM_COMPONENTBUILDER_PLUGIN="Plugin" COM_COMPONENTBUILDER_PROPERTY="Property" COM_COMPONENTBUILDER_SBR_YOU_CAN_ADD_A_BGITHUB_ACCESS_TOKENB_TO_COMPONENTBUILDER_GLOBAL_OPTIONS_TO_MAKE_AUTHENTICATED_REQUESTS_TO_GITHUB_AN_ACCESS_TOKEN_WITH_ONLY_PUBLIC_ACCESS_WILL_DO_TO_RETRIEVE_S="%s
You can add a gitHub Access Token to Componentbuilder global options to make authenticated requests to gitHub. An access token with only public access will do to retrieve %s." +COM_COMPONENTBUILDER_SELECT_EXTENSION="Select extension" COM_COMPONENTBUILDER_SINCE_THE_OWNER_DETAILS_ARE_DISPLAYED_DURING_BIMPORT_PROCESSB_BEFORE_ADDING_THE_KEY_THIS_WAY_IF_THE_USERDEV_BDOES_NOTB_HAVE_THE_KEY_THEY_CAN_SEE_BWHERE_TO_GET_ITB="Since the owner details are displayed during import process before adding the key, this way if the user/dev does not have the key they can see where to get it." COM_COMPONENTBUILDER_SINCE_THE_OWNER_DETAILS_ARE_DISPLAYED_DURING_IMPORT_PROCESS_BEFORE_ADDING_THE_KEY_THIS_WAY_IF_THE_USERDEV_DOES_NOT_HAVE_THE_KEY_THEY_CAN_SEE_WHERE_TO_GET_IT="Since the owner details are displayed during import process before adding the key, this way if the user/dev does not have the key they can see where to get it." COM_COMPONENTBUILDER_SORRY_THIS_PLACEHOLDER_IS_ALREADY_IN_USE="Sorry this placeholder is already in use!"