diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2e57f65
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.idea
+.DS_Store
+plugin/vendor
+build/*
+plugin/renderer/item/positions.config
+plugin/composer.lock
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8a9722c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,29 @@
+# Algolia integration for ZOO
+
+Plugin to allow any ZOO item to be indexed into an algolia index.
+Each ZOO application has its own Algolia configuration, allowing for different indexes per-application.
+
+Each ZOO type can be mapped through a simple drag and drop configuration using the standard ZOO layouts.
+
+## Installation
+
+Download [a release here on github](https://github.com/Weble/ZOOalgolia/releases) and install it using Joomla Installer.
+
+## Usage
+
+1. Install the plugin
+2. Enable it
+3. Go into your ZOO application instance configuration and fill in the required settings 
+4. Go into the ZOO layout configuration and map your type. Use the "Label" to set the key of that property. 
+
+Now, every time you create / save / delete an item from zoo, it will be instantly indexed in Algolia.
+
+## Console Commands
+
+If you have [JoomlaCommands](https://github.com/Weble/JoomlaCommands) installed, the plugin provides an handy `algolia:sync` command to deal with syncing from the command line
+
+```php bin/console algolia:sync {--app=[ID] --type=[type] --ids=1,2,3}```
+
+## Build from source
+
+```./build.sh```
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..ccd2b06
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,9 @@
+# Remove old files
+rm -f build/*.zip
+
+# Zip Plugin
+cd plugin/
+composer install
+zip -qr ../build/plg_system_zooalgolia.zip ./*
+
+cd ../
diff --git a/img.png b/img.png
new file mode 100644
index 0000000..5963b8e
Binary files /dev/null and b/img.png differ
diff --git a/img/config.jpg b/img/config.jpg
new file mode 100644
index 0000000..aee2d06
Binary files /dev/null and b/img/config.jpg differ
diff --git a/img/layout.jpg b/img/layout.jpg
new file mode 100644
index 0000000..fa7e877
Binary files /dev/null and b/img/layout.jpg differ
diff --git a/plugin/composer.json b/plugin/composer.json
new file mode 100644
index 0000000..2a05136
--- /dev/null
+++ b/plugin/composer.json
@@ -0,0 +1,18 @@
+{
+ "require": {
+ "php": "^7.4 || ^8.0",
+ "algolia/algoliasearch-client-php": "^3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Weble\\ZOOAlgolia\\": [
+ "src"
+ ]
+ }
+ },
+ "config": {
+ "platform": {
+ "php": "7.4.22"
+ }
+ }
+}
diff --git a/plugin/config/application.xml b/plugin/config/application.xml
new file mode 100644
index 0000000..fede9f4
--- /dev/null
+++ b/plugin/config/application.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugin/install.php b/plugin/install.php
new file mode 100644
index 0000000..b37bb45
--- /dev/null
+++ b/plugin/install.php
@@ -0,0 +1,36 @@
+enqueueMessage($msg, 'warning');
+
+ return false;
+ }
+ }
+
+ function install($parent)
+ {
+ // $db = \JFactory::getDBO();
+ }
+}
diff --git a/plugin/renderer/index.html b/plugin/renderer/index.html
new file mode 100644
index 0000000..fa6d84e
--- /dev/null
+++ b/plugin/renderer/index.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugin/renderer/item/algolia.php b/plugin/renderer/item/algolia.php
new file mode 100644
index 0000000..6dadf6f
--- /dev/null
+++ b/plugin/renderer/item/algolia.php
@@ -0,0 +1,3 @@
+
diff --git a/plugin/renderer/item/index.html b/plugin/renderer/item/index.html
new file mode 100644
index 0000000..fa6d84e
--- /dev/null
+++ b/plugin/renderer/item/index.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugin/renderer/item/metadata.xml b/plugin/renderer/item/metadata.xml
new file mode 100644
index 0000000..dc5db54
--- /dev/null
+++ b/plugin/renderer/item/metadata.xml
@@ -0,0 +1,7 @@
+
+
+
+ Algolia
+ Map ZOO items to Algolia
+
+
diff --git a/plugin/renderer/item/positions.xml b/plugin/renderer/item/positions.xml
new file mode 100644
index 0000000..c7b8557
--- /dev/null
+++ b/plugin/renderer/item/positions.xml
@@ -0,0 +1,6 @@
+
+
+
+ Search
+
+
diff --git a/plugin/src/AlgoliaSync.php b/plugin/src/AlgoliaSync.php
new file mode 100644
index 0000000..1fa68fa
--- /dev/null
+++ b/plugin/src/AlgoliaSync.php
@@ -0,0 +1,549 @@
+application = $application;
+ $this->zoo = $application->app;
+ $this->renderer = $this->zoo->renderer->create('item', ['path' => $this->zoo->path]);
+
+ if ($application->getParams()->get('global.config.algolia_app_id') && $application->getParams()->get('global.config.algolia_app_id')) {
+ $this->client = SearchClient::create(
+ $application->getParams()->get('global.config.algolia_app_id'),
+ $application->getParams()->get('global.config.algolia_secret_key')
+ );
+ }
+
+ if ($this->client && $application->getParams()->get('global.config.algolia_index')) {
+ $this->index = $this->client->initIndex($application->getParams()->get('global.config.algolia_index'));
+ }
+ }
+
+ public function isConfigured(): bool
+ {
+ return $this->client && $this->index;
+ }
+
+ public function batchSync(array $items): void
+ {
+ if (!$this->isConfigured()) {
+ return;
+ }
+
+ $save = [];
+ $delete = [];
+ foreach ($items as $item) {
+ $data = $this->algoliaData($item);
+
+ if (!$data) {
+ $delete[] = $item;
+ continue;
+ }
+
+ $save[] = $data;
+ }
+
+
+ if (count($delete) > 0) {
+ $this->index->deleteObjects($delete, [
+ 'objectIDKey' => 'id'
+ ]);
+ }
+
+ if (count($save) > 0) {
+ $this->index->saveObjects($save, [
+ 'objectIDKey' => 'id'
+ ]);
+ }
+ }
+
+ public function batchDelete(array $items): void
+ {
+ if (!$this->isConfigured()) {
+ return;
+ }
+
+ if (count($items) > 0) {
+ $this->index->deleteObjects($items);
+ }
+ }
+
+ public function clearSync(): void
+ {
+ if (!$this->isConfigured()) {
+ return;
+ }
+
+ $this->index->clearObjects();
+ }
+
+ public function sync(\Item $item): bool
+ {
+ if (!$this->isConfigured()) {
+ return false;
+ }
+
+ $data = $this->algoliaData($item);
+
+ if ($data) {
+ return $this->index->saveObject($data, [
+ 'objectIDKey' => 'id'
+ ])->valid();
+ }
+
+ return $this->index->deleteObject($item->id)->valid();
+ }
+
+ private function algoliaData(\Item $item): ?array
+ {
+ if (!$item->isPublished()) {
+ return null;
+ }
+
+ /** @var Application $application */
+ $application = $this->application;
+
+ /** @var Type $type */
+ $type = $item->getType();
+
+ $this->categories = $application->getCategoryTree();
+ $data = [
+ 'id' => $item->id,
+ 'url' => [],
+ 'category_ids' => array_filter(array_merge($item->getRelatedCategoryIds(), array_flatten(array_map(function($id) {
+ /** @var Category $category */
+ $category = $this->categories[$id] ?? null;
+ if (!$category) {
+ return [];
+ }
+
+ return array_map(function($parent) {
+ return $parent->id;
+ }, $category->getPathway());
+
+ }, $item->getRelatedCategoryIds()))))
+ ];
+
+ foreach (LanguageHelper::getContentLanguages() as $lang => $languageDetails) {
+ $data['url'][$lang] = $this->getItemUrls($item, $lang);
+ };
+
+
+ $config = new Registry($this->renderer->getConfig('root:plugins/system/zooalgolia/renderer/item')->get($application->getGroup() . '.' . $type->identifier . '.algolia'));
+ $config = $config->get('search', []);
+ foreach ($config as $elementConfig) {
+ $element = $item->getElement($elementConfig['element']);
+
+ if (!$element) {
+ continue;
+ }
+
+ $key = $elementConfig['element'];
+
+ if ($elementConfig['altlabel'] ?? false) {
+ $key = $elementConfig['altlabel'];
+ }
+
+ $value = $this->elementValueFor($element, $elementConfig);
+
+ $data[$key] = $value;
+
+ $parts = explode(".", $key);
+
+ if (count($parts) > 1) {
+ $newKey = array_shift($parts);
+ $previousData = $data[$newKey] ?? [];
+ $language = array_shift($parts);
+ $data[$newKey] = $previousData + [$language => $value];
+
+ unset($data[$key]);
+
+ }
+ }
+
+ return $data;
+ }
+
+ private function elementValueFor(\Element $element, array $params)
+ {
+ if ($element instanceof \ElementItemName) {
+ return $element->getItem()->name;
+ }
+
+ if ($element instanceof \ElementItemPrimaryCategory) {
+ $category = $element->getItem()->getPrimaryCategory();
+
+ return $this->getDataForCategory($category);
+ }
+
+ if ($element instanceof \ElementTextPro || $element instanceof ElementTextareaPro) {
+
+ $values = array_filter(array_map(function ($item) use ($params) {
+ $value = $item['value'] ?? '';
+ $value = strip_tags($value);
+ $max_length = $params['specific._max_car'] ?? 0;
+ $tr_suffix = $params['specific._max_car_suffix'] ?? '';
+
+ if ($max_length > 0) {
+ $value = $item->app->zlstring->truncate($value, $max_length, $tr_suffix);
+ }
+
+ return strlen($value) > 0 ? $value : null;
+ }, $element->data()));
+
+ $repeatable = $element->config->get('repeatable', false);
+ if ($params['filter']['_limit'] ?? null === 1) {
+ $repeatable = false;
+ }
+
+
+ if ($repeatable) {
+ return $values;
+ }
+
+ return array_shift($values);
+ }
+
+ if ($element instanceof \ElementItemCategory) {
+ $categories = $element->getItem()->getRelatedCategories();
+
+ $data = [];
+ foreach ($categories as $category) {
+ $data[] = $this->getDataForCategory($category);
+ }
+
+ return $data;
+ }
+
+ if ($element instanceof \ElementFilesPro) {
+
+ if (!$element->data()) {
+ return null;
+ }
+
+ /* If image */
+ if ($element instanceof \ElementImagePro) {
+
+ $values = [];
+
+ foreach ($element->data() as $item) {
+
+ if (!isset($item['file'])) {
+ continue;
+ }
+
+
+ $values[] = '/' . $item['file'];
+ }
+
+ if ($element->config->get('repeatable', false)) {
+ return $values;
+ }
+
+ return $values ? array_shift($values) : null;
+ }
+
+ /* If file */
+ $values = array_filter(array_map(function ($item) use ($element) {
+
+ if (!isset($item['file'])) {
+ return null;
+ }
+
+ $file = $this->zoo->zoo->resizeImage(JPATH_ROOT . '/' . $item['file'], 200, 300);
+
+ $source_dir = $element->getConfig()->files['_source_dir'];
+
+ if ($source_dir) {
+ $file = Folder::move($file, $source_dir);
+ }
+
+ $url = $this->zoo->path->relative($file);
+
+ return $url ? '/' . $url : null;
+
+ }, $element->data()));
+
+ if ($element->config->get('repeatable', false)) {
+ return $values;
+ }
+
+ return $values ? array_shift($values) : null;
+ }
+
+ if ($element instanceof \ElementOption) {
+
+ $values = $element->get('option', []);
+
+ $options = [];
+ foreach ($element->config->get('option', []) as $option) {
+ if (in_array($option['value'], $values)) {
+ $options[] = $option['name'];
+ }
+ }
+
+ return [
+ 'values' => $element->get('option', []),
+ 'names' => $options
+ ];
+ }
+
+ if ($element instanceof \ElementRepeatable) {
+
+ $values = array_filter(array_map(function ($item) {
+ $value = $item['value'] ?? '';
+ return strlen($value) > 0 ? $value : null;
+ }, $element->data()));
+
+ if ($element->config->get('repeatable', false)) {
+ return $values;
+ }
+
+ return array_shift($values);
+ }
+
+ if ($element instanceof \ElementItemTag) {
+ return $element->getItem()->getTags();
+ }
+
+ if ($value = $element->get('value', null)) {
+ return $value;
+ }
+
+ return null;
+ }
+
+ protected function findMenuItem($type, $id, $lang)
+ {
+ $zoo = App::getInstance('zoo');
+ if ($this->menuItems === null) {
+ $this->menuItems = array_fill_keys(
+ [
+ 'frontpage',
+ 'category',
+ 'item',
+ 'submission',
+ 'mysubmissions'
+ ],
+ []
+ );
+
+ foreach (LanguageHelper::getContentLanguages() as $langCode => $language) {
+ $menu_items = $zoo->system->application->getMenu('site')->getItems([
+ 'language',
+ 'component_id'
+ ], [
+ $langCode,
+ \JComponentHelper::getComponent('com_zoo')->id
+ ]) ?: [];
+
+ /** @var MenuItem $menu_item */
+ foreach ($menu_items as $menu_item) {
+ /** @var Registry $menuItemParams */
+ $menuItemParams = $zoo->parameter->create($menu_item->params);
+ $menuItemLanguage = $menu_item->language;
+
+ switch (@$menu_item->query['view']) {
+ case 'frontpage':
+ $this->menuItems['frontpage'][$menuItemParams->get('application')][$menuItemLanguage] = $menu_item;
+ break;
+ case 'category':
+ $this->menuItems['category'][$menuItemParams->get('category')][$menuItemLanguage] = $menu_item;
+ break;
+ case 'item':
+ $this->menuItems['item'][$menuItemParams->get('item_id')][$menuItemLanguage] = $menu_item;
+ break;
+ case 'submission':
+ $this->menuItems[(@$menu_item->query['layout'] == 'submission' ? 'submission' : 'mysubmissions')][$menuItemParams->get('submission')][$menuItemLanguage] = $menu_item;
+ break;
+ }
+ }
+ }
+ }
+
+ return @$this->menuItems[$type][$id][$lang] ?: @$this->menuItems[$type][$id]['*'];
+ }
+
+ private function getItemUrls(\Item $item, $lang)
+ {
+ $urls = [];
+ // Priority 1: direct link to item
+ if ($menu_item = $this->findMenuItem('item', $item->id, $lang)) {
+ return [
+ 'default' => str_replace('/item/', '/',
+ Route::link('site', $menu_item->link . '&Itemid=' . $menu_item->id))
+ ];
+ }
+
+ $menu_item_frontpage = $this->findMenuItem('frontpage', $item->application_id, $lang);
+
+ // build item link
+ $langCode = $lang === 'en-GB' ? 'en' : 'it';
+ $link = $this->getLinkBase() . '&task=item&item_id=' . $item->id . '&lang=' . $langCode;
+ $categories = $item->getRelatedCategories(true);
+ $primary_category = $item->getPrimaryCategory();
+
+ if (!$categories && $menu_item_frontpage) {
+ return [
+ 'default' => str_replace('/item/', '/',
+ Route::link('site', $link . '&Itemid=' . $menu_item_frontpage->id))
+ ];
+ }
+
+ if (!$categories) {
+ return ['default' => str_replace('/item/', '/', Route::link('site', $link))];
+ }
+
+ foreach ($categories as $category) {
+
+ $link_cat = $link;
+ $itemid = null;
+
+ /* If not category */
+ if (!$category || !$category->id) {
+ $urls['default'] = str_replace('/item/', '/', Route::link('site', $link_cat));
+ continue;
+ }
+
+ /* Else */
+ // direct link to category
+ if ($menu_item = $this->findMenuItem('category', $category->id, $lang)) {
+ $itemid = $menu_item->id;
+ // find in category path
+ } elseif ($menu_item = $this->findInCategoryPath($category, $lang)) {
+ $itemid = $menu_item->id;
+ } elseif ($menu_item_frontpage) {
+ $itemid = $menu_item_frontpage->id;
+ }
+
+ if ($itemid) {
+ $link_cat .= '&Itemid=' . $itemid;
+ }
+
+ if ($category->id) {
+ $urls[$category->id] = str_replace('/item/', '/', Route::link('site', $link_cat));
+ }
+
+ if ($primary_category && $primary_category->id == $category->id) {
+ $urls['default'] = str_replace('/item/', '/', Route::link('site', $link_cat));
+ }
+ }
+
+ return $urls;
+ }
+
+ private function getCategoryUrl(\Category $category, $lang)
+ {
+ $urls = [];
+ // Priority 1: direct link to item
+ if ($menu_item = $this->findMenuItem('category', $category->id, $lang)) {
+ return str_replace('/category/', '/', Route::link('site', $menu_item->link . '&Itemid=' . $menu_item->id));
+ }
+
+ $menu_item_frontpage = $this->findMenuItem('frontpage', $category->application_id, $lang);
+
+ // build category link
+ $langCode = $lang === 'en-GB' ? 'en' : 'it';
+ $link = $this->getLinkBase() . '&task=category&category_id=' . $category->id . '&lang=' . $langCode;
+
+ $itemid = null;
+ if ($menu_item = $this->findInCategoryPath($category, $lang)) {
+ $itemid = $menu_item->id;
+ } elseif ($menu_item_frontpage) {
+ $itemid = $menu_item_frontpage->id;
+ }
+
+ return str_replace('/category/', '/', Route::link('site', $link . '&Itemid=' . $itemid));
+ }
+
+ /**
+ * Finds the category in the pathway
+ *
+ * @param Category $category
+ * @return stdClass menu item
+ * @since 2.0
+ */
+ protected function findInCategoryPath($category, $lang)
+ {
+ foreach ($category->getPathway() as $id => $cat) {
+ if ($menu_item = $this->findMenuItem('category', $id, $lang)) {
+ return $menu_item;
+ }
+ }
+ }
+
+ /**
+ * Gets this route helpers link base
+ *
+ * @return string the link base
+ * @since 2.0
+ */
+ public function getLinkBase()
+ {
+ return 'index.php?option=com_zoo';
+ }
+
+
+ private function getDataForCategory(?Category $category = null, bool $pathway = true): ?array
+ {
+ if (!$category) {
+ return null;
+ }
+
+ if (!isset($this->categories[$category->id])) {
+ return null;
+ }
+
+ /** @var Category $category */
+ $category = $this->categories[$category->id];
+
+ $image = $category->getParams()->get('content.teaser_image');
+ $data = [
+ 'id' => $category->id,
+ 'name' => [],
+ 'url' => [],
+ 'image' => $image ? '/' . $image : null
+ ];
+
+ if ($pathway) {
+ $data['path'] = [];
+ foreach ($category->getPathWay() as $cat) {
+ $data['path'][] = $this->getDataForCategory($cat, false);
+ }
+ }
+
+ foreach (LanguageHelper::getContentLanguages() as $lang => $languageDetail) {
+ $data['name'][$lang] = $category->getParams()->get('content.name_translation')[$lang] ?? $category->name;
+ $data['url'][$lang] = $this->getCategoryUrl($category, $lang);
+ }
+
+ return $data;
+ }
+}
diff --git a/plugin/src/AlgoliaSyncCommand.php b/plugin/src/AlgoliaSyncCommand.php
new file mode 100644
index 0000000..9e14a92
--- /dev/null
+++ b/plugin/src/AlgoliaSyncCommand.php
@@ -0,0 +1,82 @@
+setDescription('Import ZOO items into Algolia');
+ $this->addOption('app', 'a', InputArgument::OPTIONAL, 'The id of the app to import types from');
+ $this->addOption('type', 't', InputArgument::OPTIONAL, 'The type to import');
+ $this->addOption('ids', null, InputArgument::OPTIONAL, 'The comma separated list of the ids of the items to import');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ require_once(JPATH_BASE.'/plugins/system/zlframework/config.php');
+ require_once JPATH_SITE . '/plugins/system/zooalgolia/vendor/autoload.php';
+
+ $zoo = App::getInstance('zoo');
+
+ if ($input->getOption('app')) {
+ $applications = [$zoo->table->application->get($input->getOption('app'))];
+ } else {
+ /** @var \Application[] $applications */
+ $applications = $zoo->table->application->all();
+ }
+
+ $type = $input->getOption('type');
+
+ $ids = $input->getOption('ids');
+ if ($ids) {
+ $ids = array_map('intval', explode(",", $ids));
+ }
+
+ foreach ($applications as $application) {
+ $algoliaSync = new AlgoliaSync($application);
+ if (!$algoliaSync->isConfigured()) {
+ continue;
+ }
+
+ $output->writeln('Import Items from ' . $application->name);
+
+ /** @var Item[] $items */
+ if ($ids) {
+ $items = $zoo->table->item->all(['conditions' => 'application_id = ' . $application->id . ' AND id IN ( ' . implode(",", $ids) . ')']);
+ $total = count($items);
+ } else {
+ $items = $zoo->table->item->findAll($application->id);
+ $total = $application->getItemCount();
+ }
+
+
+ $progress = new ProgressBar($output, $total);
+
+ foreach ($items as $item) {
+
+ if ($type !== null && $type !== $item->getType()->id) {
+ continue;
+ }
+
+ $progress->advance();
+ $algoliaSync->sync($item);
+ }
+
+ $progress->finish();
+ }
+
+ return 0;
+ }
+}
diff --git a/plugin/zooalgolia.php b/plugin/zooalgolia.php
new file mode 100644
index 0000000..96ddd24
--- /dev/null
+++ b/plugin/zooalgolia.php
@@ -0,0 +1,220 @@
+init();
+ }
+
+ public function onGetConsoleCommands(ConsoleApplication $console)
+ {
+ $console->addCommands([
+ new AlgoliaSyncCommand()
+ ]);
+ }
+
+ protected function init()
+ {
+ /** @var App $zoo */
+ $zoo = App::getInstance('zoo');
+
+ // register plugin path
+ if ($path = $zoo->path->path('root:plugins/system/zooalgolia')) {
+ $zoo->path->register($path, 'zooalgolia');
+ }
+
+ $zoo->event->dispatcher->connect('layout:init', [
+ $this,
+ 'initTypeLayouts'
+ ]);
+
+ $zoo->event->dispatcher->connect('item:deleted', [
+ $this,
+ 'removeItems'
+ ]);
+ $zoo->event->dispatcher->connect('item:saved', [
+ $this,
+ 'sync'
+ ]);
+
+ // only if not submission
+ if (!strstr(\Joomla\CMS\Factory::getApplication()->input->getCmd('controller'), 'submission')) {
+ $zoo->event->dispatcher->connect('application:init', array(
+ $this,
+ 'applicationAlgoliaConfiguration'
+ ));
+ }
+ }
+
+ public function applicationAlgoliaConfiguration(AppEvent $event)
+ {
+ /** @var Application $app */
+ $app = $event->getSubject();
+
+ // Call the helper method
+ $file = __DIR__ . '/config/application.xml';
+
+ $this->addApplicationParams($app, $file);
+ }
+
+ public function sync(AppEvent $event)
+ {
+ /** @var \Item $item */
+ $item = $event->getSubject();
+
+ $algoliaSync = new AlgoliaSync($item->getApplication());
+ $algoliaSync->sync($item);
+ }
+
+ public function removeItems(AppEvent $event)
+ {
+ /** @var \Item $item */
+ $item = $event->getSubject();
+
+ $algoliaSync = new AlgoliaSync($item->getApplication());
+ $algoliaSync->batchDelete([$event->getSubject()->id]);
+ }
+
+ public function initTypeLayouts(AppEvent $event)
+ {
+ $zoo = App::getInstance('zoo');
+ $extensions = (array)$event->getReturnValue();
+
+ // clean all previous layout references
+ $newExtensions = array();
+ foreach ($extensions as $ext) {
+ if (stripos($ext['name'], 'zooalgolia') === false) {
+ $newExtensions[] = $ext;
+ }
+ }
+
+ // add new ones
+ $newExtensions[] = [
+ 'name' => 'Algolia',
+ 'path' => $zoo->path->path('zooalgolia:'),
+ 'type' => 'plugin'
+ ];
+
+ $event->setReturnValue($newExtensions);
+ }
+
+ private function addApplicationParams(Application $app, string $file)
+ {
+ // Custom XML File
+ $xml = simplexml_load_file($file);
+
+
+ // Appication XML file
+ $old_file = $app->app->path->path($app->getResource() . $app->metaxml_file);
+ $old_xml = simplexml_load_file($old_file);
+
+
+ // Application is right?
+ if (!isset($xml->application)) {
+ return;
+ }
+
+ if (!$this->generateNewXmlFile($old_xml, $xml, $app)) {
+ return;
+ }
+
+ // Save the new file and set it as the default one
+ $new_file = $app->app->path->path($app->getResource()) . '/' . \Joomla\Filesystem\File::stripExt($app->metaxml_file) . '_zooalgolia.xml';
+
+ // Save the new version
+ $data = $old_xml->asXML();
+ \Joomla\Filesystem\File::write($new_file, $data);
+
+ // set it as the default one
+ $app->metaxml_file = \Joomla\Filesystem\File::stripExt($app->metaxml_file) . '_zooalgolia.xml';
+ }
+
+ private function appendChild(\SimpleXMLElement $parent, \SimpleXMLElement $child): void
+ {
+ // use dom for this kind of things
+ $domparent = dom_import_simplexml($parent);
+ $domchild = dom_import_simplexml($child);
+
+ // Import
+ $domchild = $domparent->ownerDocument->importNode($domchild, true);
+
+ // Append
+ $domparent->appendChild($domchild);
+ }
+
+ private function generateNewXmlFile(\SimpleXMLElement $old_xml, \SimpleXMLElement $xml, Application $app): bool
+ {
+ $app_file_changed = false;
+
+ foreach ($xml->application as $a) {
+ // Check the parameter group
+ $group = (string)$a->attributes()->group ? (string)$a->attributes()->group : 'all';
+ if ($group !== 'all' && $group !== $app->application_group) {
+ continue;
+ }
+
+ if (!isset($a->params)) {
+ continue;
+ }
+
+ foreach ($a->params as $param) {
+ // Second level grouping
+ $group = (string)$param->attributes()->group ? (string)$param->attributes()->group : '_default';
+ $new_params = new \SimpleXMLElement(' ');
+ $new_params->addAttribute('group', $group);
+
+ if (!@$old_xml->params) {
+ continue;
+ }
+
+ $param_added = false;
+ // Merge with already existing param groups
+ foreach ($old_xml->params as $ops) {
+
+ if ((string)$ops->attributes()->group !== $group) {
+ continue;
+ }
+
+ $param_added = true;
+
+ // Check for addPath
+ if (($a->params->attributes()->addpath != '') && !($old_xml->params->attributes()->addpath)) {
+ @$ops->addAttribute('addpath', $a->params->attributes()->addpath);
+ $app_file_changed = true;
+ }
+
+ // Add the parameters for this group
+ foreach ($param->param as $p) {
+ // If it doesn't exists already
+ if (!count($ops->xpath("param[@name='" . $p->attributes()->name . "']"))) {
+ // Push changes with default
+ $p->attributes()->default = $this->params->get($p->attributes()->name, $p->attributes()->default);
+ $this->appendChild($ops, $p);
+ $app_file_changed = true;
+ }
+ }
+ }
+
+ // Create a new param group if necessary
+ if (!$param_added) {
+ $this->appendChild($old_xml, $param);
+ $app_file_changed = true;
+ }
+ }
+ }
+
+ return $app_file_changed;
+ }
+}
diff --git a/plugin/zooalgolia.xml b/plugin/zooalgolia.xml
new file mode 100644
index 0000000..8de9276
--- /dev/null
+++ b/plugin/zooalgolia.xml
@@ -0,0 +1,26 @@
+
+
+ ZOO - Algolia Integration
+ 2.0.1
+ October 2021
+ (C) 2021 Weble Srl
+ GNU GPLv2 or later
+ Weble Srl
+ daniele@weble.it
+ https://weble.it
+ Algolia Integration for ZOO
+ zooalgolia
+ install.php
+
+ zooalgolia.php
+ renderer
+ vendor
+ config
+
+
+
+
+
+
+
+