-
-
Notifications
You must be signed in to change notification settings - Fork 46
Closed
Description
New language relevant PR in upstream repo: joomla/joomla-cms#35788 Here are the upstream changes:
Click to expand the diff!
diff --git a/administrator/language/en-GB/plg_webservices_media.ini b/administrator/language/en-GB/plg_webservices_media.ini
new file mode 100644
index 000000000000..b2b25bba1114
--- /dev/null
+++ b/administrator/language/en-GB/plg_webservices_media.ini
@@ -0,0 +1,7 @@
+; Joomla! Project
+; (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+; License GNU General Public License version 2 or later; see LICENSE.txt
+; Note : All ini files need to be saved as UTF-8
+
+PLG_WEBSERVICES_MEDIA="Web Services - Media"
+PLG_WEBSERVICES_MEDIA_XML_DESCRIPTION="Add media routes to the API for your website."
diff --git a/administrator/language/en-GB/plg_webservices_media.sys.ini b/administrator/language/en-GB/plg_webservices_media.sys.ini
new file mode 100644
index 000000000000..b2b25bba1114
--- /dev/null
+++ b/administrator/language/en-GB/plg_webservices_media.sys.ini
@@ -0,0 +1,7 @@
+; Joomla! Project
+; (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+; License GNU General Public License version 2 or later; see LICENSE.txt
+; Note : All ini files need to be saved as UTF-8
+
+PLG_WEBSERVICES_MEDIA="Web Services - Media"
+PLG_WEBSERVICES_MEDIA_XML_DESCRIPTION="Add media routes to the API for your website."
diff --git a/api/components/com_media/src/Controller/AdaptersController.php b/api/components/com_media/src/Controller/AdaptersController.php
new file mode 100644
index 000000000000..c8c23df127dc
--- /dev/null
+++ b/api/components/com_media/src/Controller/AdaptersController.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * @package Joomla.API
+ * @subpackage com_media
+ *
+ * @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Media\Api\Controller;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\MVC\Controller\ApiController;
+use Joomla\Component\Media\Administrator\Exception\InvalidPathException;
+use Joomla\Component\Media\Api\Helper\AdapterTrait;
+
+/**
+ * Media web service controller.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+class AdaptersController extends ApiController
+{
+ use AdapterTrait;
+
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since __DEPLOY_VERSION__
+ */
+ protected $contentType = 'adapters';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected $default_view = 'adapters';
+
+ /**
+ * Display one specific adapter.
+ *
+ * @param string $path The path of the file to display. Leave empty if you want to retrieve data from the request.
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @throws InvalidPathException
+ * @throws \Exception
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function displayItem($path = '')
+ {
+ // Set the id as the parent sets it as int
+ $this->modelState->set('id', $this->input->get('id', '', 'string'));
+
+ return parent::displayItem();
+ }
+}
diff --git a/api/components/com_media/src/Controller/MediaController.php b/api/components/com_media/src/Controller/MediaController.php
new file mode 100644
index 000000000000..379d275f99be
--- /dev/null
+++ b/api/components/com_media/src/Controller/MediaController.php
@@ -0,0 +1,410 @@
+<?php
+/**
+ * @package Joomla.API
+ * @subpackage com_media
+ *
+ * @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Media\Api\Controller;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\Access\Exception\NotAllowed;
+use Joomla\CMS\Component\ComponentHelper;
+use Joomla\CMS\Filter\InputFilter;
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\MVC\Controller\ApiController;
+use Joomla\Component\Media\Administrator\Exception\FileExistsException;
+use Joomla\Component\Media\Administrator\Exception\InvalidPathException;
+use Joomla\Component\Media\Api\Helper\AdapterTrait;
+use Joomla\Component\Media\Api\Model\MediumModel;
+use Joomla\String\Inflector;
+use Tobscure\JsonApi\Exception\InvalidParameterException;
+
+/**
+ * Media web service controller.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+class MediaController extends ApiController
+{
+ use AdapterTrait;
+
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since __DEPLOY_VERSION__
+ */
+ protected $contentType = 'media';
+
+ /**
+ * Query parameters => model state mappings
+ *
+ * @var array
+ * @since __DEPLOY_VERSION__
+ */
+ private static $listQueryModelStateMap = [
+ 'path' => [
+ 'name' => 'path',
+ 'type' => 'STRING',
+ ],
+ 'url' => [
+ 'name' => 'url',
+ 'type' => 'BOOLEAN',
+ ],
+ 'temp' => [
+ 'name' => 'temp',
+ 'type' => 'BOOLEAN',
+ ],
+ 'content' => [
+ 'name' => 'content',
+ 'type' => 'BOOLEAN',
+ ],
+ ];
+
+ /**
+ * Item query parameters => model state mappings
+ *
+ * @var array
+ * @since __DEPLOY_VERSION__
+ */
+ private static $itemQueryModelStateMap = [
+ 'path' => [
+ 'name' => 'path',
+ 'type' => 'STRING',
+ ],
+ 'url' => [
+ 'name' => 'url',
+ 'type' => 'BOOLEAN',
+ ],
+ 'temp' => [
+ 'name' => 'temp',
+ 'type' => 'BOOLEAN',
+ ],
+ 'content' => [
+ 'name' => 'content',
+ 'type' => 'BOOLEAN',
+ ],
+ ];
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected $default_view = 'media';
+
+ /**
+ * Display a list of files and/or folders.
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @throws \Exception
+ */
+ public function displayList()
+ {
+ // Set list specific request parameters in model state.
+ $this->setModelState(self::$listQueryModelStateMap);
+
+ // Display files in specific path.
+ if ($this->input->exists('path'))
+ {
+ $this->modelState->set('path', $this->input->get('path', '', 'STRING'));
+ }
+
+ // Return files (not folders) as urls.
+ if ($this->input->exists('url'))
+ {
+ $this->modelState->set('url', $this->input->get('url', true, 'BOOLEAN'));
+ }
+
+ // Map JSON:API compliant filter[search] to com_media model state.
+ $apiFilterInfo = $this->input->get('filter', [], 'array');
+ $filter = InputFilter::getInstance();
+
+ // Search for files matching (part of) a name or glob pattern.
+ if ($doSearch = array_key_exists('search', $apiFilterInfo))
+ {
+ $this->modelState->set('search', $filter->clean($apiFilterInfo['search'], 'STRING'));
+
+ // Tell model to search recursively
+ $this->modelState->set('search_recursive', $this->input->get('search_recursive', false, 'BOOLEAN'));
+ }
+
+ return parent::displayList();
+ }
+
+ /**
+ * Display one specific file or folder.
+ *
+ * @param string $path The path of the file to display. Leave empty if you want to retrieve data from the request.
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @throws InvalidPathException
+ * @throws \Exception
+ */
+ public function displayItem($path = '')
+ {
+ // Set list specific request parameters in model state.
+ $this->setModelState(self::$itemQueryModelStateMap);
+
+ // Display files in specific path.
+ $this->modelState->set('path', $path ?: $this->input->get('path', '', 'STRING'));
+
+ // Return files (not folders) as urls.
+ if ($this->input->exists('url'))
+ {
+ $this->modelState->set('url', $this->input->get('url', true, 'BOOLEAN'));
+ }
+
+ return parent::displayItem();
+ }
+
+ /**
+ * Set model state using a list of mappings between query parameters and model state names.
+ *
+ * @param array $mappings A list of mappings between query parameters and model state names..
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function setModelState(array $mappings): void
+ {
+ foreach ($mappings as $queryName => $modelState)
+ {
+ if ($this->input->exists($queryName))
+ {
+ $this->modelState->set($modelState['name'], $this->input->get($queryName, '', $modelState['type']));
+ }
+ }
+ }
+
+ /**
+ * Method to add a new file or folder.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @throws FileExistsException
+ * @throws InvalidPathException
+ * @throws InvalidParameterException
+ * @throws \RuntimeException
+ * @throws \Exception
+ */
+ public function add(): void
+ {
+ $path = $this->input->json->get('path', '', 'STRING');
+ $content = $this->input->json->get('content', '', 'RAW');
+
+ $missingParameters = [];
+
+ if (empty($path))
+ {
+ $missingParameters[] = 'path';
+ }
+
+ // Content is only required when it is a file
+ if (empty($content) && strpos($path, '.') !== false)
+ {
+ $missingParameters[] = 'content';
+ }
+
+ if (\count($missingParameters))
+ {
+ throw new InvalidParameterException(
+ Text::sprintf('WEBSERVICE_COM_MEDIA_MISSING_REQUIRED_PARAMETERS', implode(' & ', $missingParameters))
+ );
+ }
+
+ $this->modelState->set('path', $this->input->json->get('path', '', 'STRING'));
+
+ // Check if an existing file may be overwritten. Defaults to false.
+ $this->modelState->set('override', $this->input->json->get('override', false));
+
+ parent::add();
+ }
+
+ /**
+ * Method to check if it's allowed to add a new file or folder
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function allowAdd($data = array()): bool
+ {
+ $user = $this->app->getIdentity();
+
+ return $user->authorise('core.create', 'com_media');
+ }
+
+ /**
+ * Method to modify an existing file or folder.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @throws FileExistsException
+ * @throws InvalidPathException
+ * @throws \RuntimeException
+ * @throws \Exception
+ */
+ public function edit(): void
+ {
+ // Access check.
+ if (!$this->allowEdit())
+ {
+ throw new NotAllowed('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED', 403);
+ }
+
+ $path = $this->input->json->get('path', '', 'STRING');
+ $content = $this->input->json->get('content', '', 'RAW');
+
+ if (empty($path) && empty($content))
+ {
+ throw new InvalidParameterException(
+ Text::sprintf('WEBSERVICE_COM_MEDIA_MISSING_REQUIRED_PARAMETERS', 'path | content')
+ );
+ }
+
+ $this->modelState->set('path', $this->input->json->get('path', '', 'STRING'));
+ // For renaming/moving files, we need the path to the existing file or folder.
+ $this->modelState->set('old_path', $this->input->get('path', '', 'STRING'));
+ // Check if an existing file may be overwritten. Defaults to true.
+ $this->modelState->set('override', $this->input->json->get('override', true));
+
+ $recordId = $this->save();
+
+ $this->displayItem($recordId);
+ }
+
+ /**
+ * Method to check if it's allowed to modify an existing file or folder.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function allowEdit($data = array(), $key = 'id'): bool
+ {
+ $user = $this->app->getIdentity();
+
+ // com_media's access rules contains no specific update rule.
+ return $user->authorise('core.edit', 'com_media');
+ }
+
+ /**
+ * Method to create or modify a file or folder.
+ *
+ * @param integer $recordKey The primary key of the item (if exists)
+ *
+ * @return string The path
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function save($recordKey = null)
+ {
+ // Explicitly get the single item model name.
+ $modelName = $this->input->get('model', Inflector::singularize($this->contentType));
+
+ /** @var MediumModel $model */
+ $model = $this->getModel($modelName, '', ['ignore_request' => true, 'state' => $this->modelState]);
+
+ $json = $this->input->json;
+
+ // Decode content, if any
+ if ($content = base64_decode($json->get('content', '', 'raw')))
+ {
+ $this->checkContent();
+ }
+
+ // If there is no content, com_media assumes the path refers to a folder.
+ $this->modelState->set('content', $content);
+
+ return $model->save();
+ }
+
+ /**
+ * Performs various checks to see if it is allowed to save the content.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @throws \RuntimeException
+ */
+ private function checkContent(): void
+ {
+ $params = ComponentHelper::getParams('com_media');
+ $helper = new \Joomla\CMS\Helper\MediaHelper();
+ $serverlength = $this->input->server->getInt('CONTENT_LENGTH');
+
+ // Check if the size of the request body does not exceed various server imposed limits.
+ if (($params->get('upload_maxsize', 0) > 0 && $serverlength > ($params->get('upload_maxsize', 0) * 1024 * 1024))
+ || $serverlength > $helper->toBytes(ini_get('upload_max_filesize'))
+ || $serverlength > $helper->toBytes(ini_get('post_max_size'))
+ || $serverlength > $helper->toBytes(ini_get('memory_limit')))
+ {
+ throw new \RuntimeException(Text::_('COM_MEDIA_ERROR_WARNFILETOOLARGE'), 400);
+ }
+ }
+
+ /**
+ * Method to delete an existing file or folder.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @throws InvalidPathException
+ * @throws \RuntimeException
+ * @throws \Exception
+ */
+ public function delete($id = null): void
+ {
+ if (!$this->allowDelete())
+ {
+ throw new NotAllowed('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED', 403);
+ }
+
+ $this->modelState->set('path', $this->input->get('path', '', 'STRING'));
+
+ $modelName = $this->input->get('model', Inflector::singularize($this->contentType));
+ $model = $this->getModel($modelName, '', ['ignore_request' => true, 'state' => $this->modelState]);
+
+ $model->delete();
+
+ $this->app->setHeader('status', 204);
+ }
+
+ /**
+ * Method to check if it's allowed to delete an existing file or folder.
+ *
+ * @return boolean
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function allowDelete(): bool
+ {
+ $user = $this->app->getIdentity();
+
+ return $user->authorise('core.delete', 'com_media');
+ }
+}
diff --git a/api/components/com_media/src/Helper/AdapterTrait.php b/api/components/com_media/src/Helper/AdapterTrait.php
new file mode 100644
index 000000000000..54155908cef0
--- /dev/null
+++ b/api/components/com_media/src/Helper/AdapterTrait.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * @package Joomla.API
+ * @subpackage com_media
+ *
+ * @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Media\Api\Helper;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\Component\ComponentHelper;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Plugin\PluginHelper;
+use Joomla\Component\Media\Administrator\Adapter\AdapterInterface;
+use Joomla\Component\Media\Administrator\Event\MediaProviderEvent;
+use Joomla\Component\Media\Administrator\Provider\ProviderInterface;
+use Joomla\Component\Media\Administrator\Provider\ProviderManager;
+
+/**
+ * Trait for classes that need adapters.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+trait AdapterTrait
+{
+ /**
+ * Holds the available media file adapters.
+ *
+ * @var ProviderManager
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private $providerManager = null;
+
+ /**
+ * The default adapter name.
+ *
+ * @var string
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private $defaultAdapterName = null;
+
+ /**
+ * Returns an array with the adapter name as key and the path of the file.
+ *
+ * @return array
+ *
+ * @throws \Exception
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function resolveAdapterAndPath(String $path): array
+ {
+ $result = [];
+ $parts = explode(':', $path, 2);
+
+ // If we have 2 parts, we have both an adapter name and a file path
+ if (\count($parts) == 2)
+ {
+ $result['adapter'] = $parts[0];
+ $result['path'] = $parts[1];
+
+ return $result;
+ }
+
+ if (!$this->getDefaultAdapterName())
+ {
+ throw new \InvalidArgumentException('No adapter found');
+ }
+
+ // If we have less than 2 parts, we return a default adapter name
+ $result['adapter'] = $this->getDefaultAdapterName();
+
+ // If we have 1 part, we return it as the path. Otherwise we return a default path
+ $result['path'] = \count($parts) ? $parts[0] : '/';
+
+ return $result;
+ }
+
+ /**
+ * Returns a provider for the given id.
+ *
+ * @return ProviderInterface
+ *
+ * @throws \Exception
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function getProvider(String $id): ProviderInterface
+ {
+ return $this->getProviderManager()->getProvider($id);
+ }
+
+ /**
+ * Return an adapter for the given name.
+ *
+ * @return AdapterInterface
+ *
+ * @throws \Exception
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function getAdapter(String $name): AdapterInterface
+ {
+ return $this->getProviderManager()->getAdapter($name);
+ }
+
+ /**
+ * Returns the default adapter name.
+ *
+ * @return string|null
+ *
+ * @throws \Exception
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function getDefaultAdapterName(): ?string
+ {
+ if ($this->defaultAdapterName)
+ {
+ return $this->defaultAdapterName;
+ }
+
+ $defaultAdapter = $this->getAdapter('local-' . ComponentHelper::getParams('com_media')->get('file_path', 'images'));
+
+ if (!$defaultAdapter
+ && $this->getProviderManager()->getProvider('local')
+ && $this->getProviderManager()->getProvider('local')->getAdapters())
+ {
+ $defaultAdapter = $this->getProviderManager()->getProvider('local')->getAdapters()[0];
+ }
+
+ if (!$defaultAdapter)
+ {
+ return null;
+ }
+
+ $this->defaultAdapterName = 'local-' . $defaultAdapter->getAdapterName();
+
+ return $this->defaultAdapterName;
+ }
+
+ /**
+ * Return a provider manager.
+ *
+ * @return ProviderManager
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function getProviderManager(): ProviderManager
+ {
+ if (!$this->providerManager)
+ {
+ $this->providerManager = new ProviderManager;
+
+ // Fire the event to get the results
+ $eventParameters = ['context' => 'AdapterManager', 'providerManager' => $this->providerManager];
+ $event = new MediaProviderEvent('onSetupProviders', $eventParameters);
+ PluginHelper::importPlugin('filesystem');
+ Factory::getApplication()->triggerEvent('onSetupProviders', $event);
+ }
+
+ return $this->providerManager;
+ }
+}
diff --git a/api/components/com_media/src/Model/AdapterModel.php b/api/components/com_media/src/Model/AdapterModel.php
new file mode 100644
index 000000000000..381306c9bb63
--- /dev/null
+++ b/api/components/com_media/src/Model/AdapterModel.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * @package Joomla.API
+ * @subpackage com_media
+ *
+ * @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Media\Api\Model;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\MVC\Model\BaseModel;
+use Joomla\Component\Media\Api\Helper\AdapterTrait;
+
+/**
+ * Media web service model supporting a single adapter item.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+class AdapterModel extends BaseModel
+{
+ use AdapterTrait;
+
+ /**
+ * Method to get a single adapter.
+ *
+ * @return \stdClass The adapter.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getItem(): \stdClass
+ {
+ list($provider, $account) = array_pad(explode('-', $this->getState('id'), 2), 2, null);
+
+ if ($account === null)
+ {
+ throw new \Exception('Account was not set');
+ }
+
+ $provider = $this->getProvider($provider);
+ $adapter = $this->getAdapter($this->getState('id'));
+
+ $obj = new \stdClass();
+ $obj->id = $provider->getID() . '-' . $adapter->getAdapterName();
+ $obj->provider_id = $provider->getID();
+ $obj->name = $adapter->getAdapterName();
+ $obj->path = $provider->getID() . '-' . $adapter->getAdapterName() . ':/';
+
+ return $obj;
+ }
+}
diff --git a/api/components/com_media/src/Model/AdaptersModel.php b/api/components/com_media/src/Model/AdaptersModel.php
new file mode 100644
index 000000000000..351b79ee9aba
--- /dev/null
+++ b/api/components/com_media/src/Model/AdaptersModel.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * @package Joomla.API
+ * @subpackage com_media
+ *
+ * @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Media\Api\Model;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\MVC\Model\BaseModel;
+use Joomla\CMS\MVC\Model\ListModelInterface;
+use Joomla\CMS\Pagination\Pagination;
+use Joomla\Component\Media\Api\Helper\AdapterTrait;
+
+/**
+ * Media web service model supporting lists of media adapters.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+class AdaptersModel extends BaseModel implements ListModelInterface
+{
+ use AdapterTrait;
+
+ /**
+ * A hacky way to enable the standard jsonapiView::displayList() to create a Pagination object,
+ * since com_media's ApiModel does not support pagination as we know from regular ListModel derived models.
+ *
+ * @var int
+ * @since __DEPLOY_VERSION__
+ */
+ private $total = 0;
+
+ /**
+ * Method to get a list of files and/or folders.
+ *
+ * @return array An array of data items.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getItems(): array
+ {
+ $adapters = [];
+ foreach ($this->getProviderManager()->getProviders() as $provider)
+ {
+ foreach ($provider->getAdapters() as $adapter)
+ {
+ $obj = new \stdClass();
+ $obj->id = $provider->getID() . '-' . $adapter->getAdapterName();
+ $obj->provider_id = $provider->getID();
+ $obj->name = $adapter->getAdapterName();
+ $obj->path = $provider->getID() . '-' . $adapter->getAdapterName() . ':/';
+
+ $adapters[] = $obj;
+ }
+ }
+
+ // A hacky way to enable the standard jsonapiView::displayList() to create a Pagination object.
+ $this->total = \count($adapters);
+
+ return $adapters;
+ }
+
+ /**
+ * Method to get a \JPagination object for the data set.
+ *
+ * @return Pagination A Pagination object for the data set.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getPagination(): Pagination
+ {
+ return new Pagination($this->getTotal(), $this->getStart(), 0);
+ }
+
+ /**
+ * Method to get the starting number of items for the data set. Because com_media's ApiModel
+ * does not support pagination as we know from regular ListModel derived models,
+ * we always start at the top.
+ *
+ * @return integer The starting number of items available in the data set.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getStart(): int
+ {
+ return 0;
+ }
+
+ /**
+ * Method to get the total number of items for the data set.
+ *
+ * @return integer The total number of items available in the data set.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getTotal(): int
+ {
+ return $this->total;
+ }
+}
diff --git a/api/components/com_media/src/Model/MediaModel.php b/api/components/com_media/src/Model/MediaModel.php
new file mode 100644
index 000000000000..572ec19e3e16
--- /dev/null
+++ b/api/components/com_media/src/Model/MediaModel.php
@@ -0,0 +1,134 @@
+<?php
+/**
+ * @package Joomla.API
+ * @subpackage com_media
+ *
+ * @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Media\Api\Model;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\MVC\Controller\Exception\ResourceNotFound;
+use Joomla\CMS\MVC\Model\BaseModel;
+use Joomla\CMS\MVC\Model\ListModelInterface;
+use Joomla\CMS\Pagination\Pagination;
+use Joomla\Component\Media\Administrator\Exception\FileNotFoundException;
+use Joomla\Component\Media\Administrator\Model\ApiModel;
+use Joomla\Component\Media\Api\Helper\AdapterTrait;
+
+/**
+ * Media web service model supporting lists of media items.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+class MediaModel extends BaseModel implements ListModelInterface
+{
+ use AdapterTrait;
+
+ /**
+ * Instance of com_media's ApiModel
+ *
+ * @var ApiModel
+ * @since __DEPLOY_VERSION__
+ */
+ private $mediaApiModel;
+
+ /**
+ * A hacky way to enable the standard jsonapiView::displayList() to create a Pagination object,
+ * since com_media's ApiModel does not support pagination as we know from regular ListModel derived models.
+ *
+ * @var int
+ * @since __DEPLOY_VERSION__
+ */
+ private $total = 0;
+
+ public function __construct($config = [])
+ {
+ parent::__construct($config);
+
+ $this->mediaApiModel = new ApiModel();
+ }
+
+ /**
+ * Method to get a list of files and/or folders.
+ *
+ * @return array An array of data items.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getItems(): array
+ {
+ // Map web service model state to com_media options.
+ $options = [
+ 'url' => $this->getState('url', false),
+ 'temp' => $this->getState('temp', false),
+ 'search' => $this->getState('search', ''),
+ 'recursive' => $this->getState('search_recursive', false),
+ 'content' => $this->getState('content', false),
+ ];
+
+ ['adapter' => $adapterName, 'path' => $path] = $this->resolveAdapterAndPath($this->getState('path', ''));
+ try
+ {
+ $files = $this->mediaApiModel->getFiles($adapterName, $path, $options);
+ }
+ catch (FileNotFoundException $e)
+ {
+ throw new ResourceNotFound(
+ Text::sprintf('WEBSERVICE_COM_MEDIA_FILE_NOT_FOUND', $path),
+ 404
+ );
+ }
+
+ /**
+ * A hacky way to enable the standard jsonapiView::displayList() to create a Pagination object.
+ * Because com_media's ApiModel does not support pagination as we know from regular ListModel
+ * derived models, we always return all retrieved items.
+ */
+ $this->total = \count($files);
+
+ return $files;
+ }
+
+ /**
+ * Method to get a \JPagination object for the data set.
+ *
+ * @return Pagination A Pagination object for the data set.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getPagination(): Pagination
+ {
+ return new Pagination($this->getTotal(), $this->getStart(), 0);
+ }
+
+ /**
+ * Method to get the starting number of items for the data set. Because com_media's ApiModel
+ * does not support pagination as we know from regular ListModel derived models,
+ * we always start at the top.
+ *
+ * @return int The starting number of items available in the data set.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getStart(): int
+ {
+ return 0;
+ }
+
+ /**
+ * Method to get the total number of items for the data set.
+ *
+ * @return int The total number of items available in the data set.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getTotal(): int
+ {
+ return $this->total;
+ }
+}
diff --git a/api/components/com_media/src/Model/MediumModel.php b/api/components/com_media/src/Model/MediumModel.php
new file mode 100644
index 000000000000..9768526274bf
--- /dev/null
+++ b/api/components/com_media/src/Model/MediumModel.php
@@ -0,0 +1,271 @@
+<?php
+/**
+ * @package Joomla.API
+ * @subpackage com_media
+ *
+ * @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Media\Api\Model;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\MVC\Controller\Exception\ResourceNotFound;
+use Joomla\CMS\MVC\Controller\Exception\Save;
+use Joomla\CMS\MVC\Model\BaseModel;
+use Joomla\Component\Media\Administrator\Exception\FileExistsException;
+use Joomla\Component\Media\Administrator\Exception\FileNotFoundException;
+use Joomla\Component\Media\Administrator\Exception\InvalidPathException;
+use Joomla\Component\Media\Administrator\Model\ApiModel;
+use Joomla\Component\Media\Api\Helper\AdapterTrait;
+
+/**
+ * Media web service model supporting a single media item.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+class MediumModel extends BaseModel
+{
+ use AdapterTrait;
+
+ /**
+ * Instance of com_media's ApiModel
+ *
+ * @var ApiModel
+ * @since __DEPLOY_VERSION__
+ */
+ private $mediaApiModel;
+
+ public function __construct($config = [])
+ {
+ parent::__construct($config);
+
+ $this->mediaApiModel = new ApiModel();
+ }
+
+ /**
+ * Method to get a single files or folder.
+ *
+ * @return \stdClass A file or folder object.
+ *
+ * @since __DEPLOY_VERSION__
+ * @throws ResourceNotFound
+ */
+ public function getItem()
+ {
+ $options = [
+ 'path' => $this->getState('path', ''),
+ 'url' => $this->getState('url', false),
+ 'temp' => $this->getState('temp', false),
+ 'content' => $this->getState('content', false),
+ ];
+
+ ['adapter' => $adapterName, 'path' => $path] = $this->resolveAdapterAndPath($this->getState('path', ''));
+
+ try
+ {
+ return $this->mediaApiModel->getFile($adapterName, $path, $options);
+ }
+ catch (FileNotFoundException $e)
+ {
+ throw new ResourceNotFound(
+ Text::sprintf('WEBSERVICE_COM_MEDIA_FILE_NOT_FOUND', $path),
+ 404
+ );
+ }
+ }
+
+ /**
+ * Method to save a file or folder.
+ *
+ * @param string $path The primary key of the item (if exists)
+ *
+ * @return string The path
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @throws Save
+ */
+ public function save($path = null): string
+ {
+ $path = $this->getState('path', '');
+ $oldPath = $this->getState('old_path', '');
+ $content = $this->getState('content', null);
+ $override = $this->getState('override', false);
+
+ ['adapter' => $adapterName, 'path' => $path] = $this->resolveAdapterAndPath($path);
+
+ $resultPath = '';
+
+ /**
+ * If we have a (new) path and an old path, we want to move an existing
+ * file or folder. This must be done before updating the content of a file,
+ * if also requested (see below).
+ */
+ if ($path && $oldPath)
+ {
+ try
+ {
+ // ApiModel::move() (or actually LocalAdapter::move()) returns a path with leading slash.
+ $resultPath = trim(
+ $this->mediaApiModel->move($adapterName, $oldPath, $path, $override),
+ '/'
+ );
+ }
+ catch (FileNotFoundException $e)
+ {
+ throw new Save(
+ Text::sprintf(
+ 'WEBSERVICE_COM_MEDIA_FILE_NOT_FOUND',
+ $oldPath
+ ),
+ 404
+ );
+ }
+ }
+
+ // If we have a (new) path but no old path, we want to create a
+ // new file or folder.
+ if ($path && !$oldPath)
+ {
+ // com_media expects separate directory and file name.
+ // If we moved the file before, we must use the new path.
+ $basename = basename($resultPath ?: $path);
+ $dirname = dirname($resultPath ?: $path);
+
+ try
+ {
+ // If there is content, com_media's assumes the new item is a file.
+ // Otherwise a folder is assumed.
+ $name = $content
+ ? $this->mediaApiModel->createFile(
+ $adapterName,
+ $basename,
+ $dirname,
+ $content,
+ $override
+ )
+ : $this->mediaApiModel->createFolder(
+ $adapterName,
+ $basename,
+ $dirname,
+ $override
+ );
+
+ $resultPath = $dirname . '/' . $name;
+ }
+ catch (FileNotFoundException $e)
+ {
+ throw new Save(
+ Text::sprintf(
+ 'WEBSERVICE_COM_MEDIA_FILE_NOT_FOUND',
+ $dirname . '/' . $basename
+ ),
+ 404
+ );
+ }
+ catch (FileExistsException $e)
+ {
+ throw new Save(
+ Text::sprintf(
+ 'WEBSERVICE_COM_MEDIA_FILE_EXISTS',
+ $dirname . '/' . $basename
+ ),
+ 400
+ );
+ }
+ catch (InvalidPathException $e)
+ {
+ throw new Save(
+ Text::sprintf(
+ 'WEBSERVICE_COM_MEDIA_BAD_FILE_TYPE',
+ $dirname . '/' . $basename
+ ),
+ 400
+ );
+ }
+ }
+
+ // If we have no (new) path but we do have an old path and we have content,
+ // we want to update the contents of an existing file.
+ if ($oldPath && $content)
+ {
+ // com_media expects separate directory and file name.
+ // If we moved the file before, we must use the new path.
+ $basename = basename($resultPath ?: $oldPath);
+ $dirname = dirname($resultPath ?: $oldPath);
+
+ try
+ {
+ $this->mediaApiModel->updateFile(
+ $adapterName,
+ $basename,
+ $dirname,
+ $content
+ );
+ }
+ catch (FileNotFoundException $e)
+ {
+ throw new Save(
+ Text::sprintf(
+ 'WEBSERVICE_COM_MEDIA_FILE_NOT_FOUND',
+ $dirname . '/' . $basename
+ ),
+ 404
+ );
+ }
+ catch (InvalidPathException $e)
+ {
+ throw new Save(
+ Text::sprintf(
+ 'WEBSERVICE_COM_MEDIA_BAD_FILE_TYPE',
+ $dirname . '/' . $basename
+ ),
+ 400
+ );
+ }
+
+ $resultPath = $resultPath ?: $oldPath;
+ }
+
+ // If we still have no result path, something fishy is going on.
+ if (!$resultPath)
+ {
+ throw new Save(
+ Text::_(
+ 'WEBSERVICE_COM_MEDIA_UNSUPPORTED_PARAMETER_COMBINATION'
+ ),
+ 400
+ );
+ }
+
+ return $resultPath;
+ }
+
+ /**
+ * Method to delete an existing file or folder.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ * @throws Save
+ */
+ public function delete(): void
+ {
+ ['adapter' => $adapterName, 'path' => $path] = $this->resolveAdapterAndPath($this->getState('path', ''));
+
+ try
+ {
+ $this->mediaApiModel->delete($adapterName, $path);
+ }
+ catch (FileNotFoundException $e)
+ {
+ throw new Save(
+ Text::sprintf('WEBSERVICE_COM_MEDIA_FILE_NOT_FOUND', $path),
+ 404
+ );
+ }
+ }
+}
diff --git a/api/components/com_media/src/View/Adapters/JsonapiView.php b/api/components/com_media/src/View/Adapters/JsonapiView.php
new file mode 100644
index 000000000000..7a2d05b8b211
--- /dev/null
+++ b/api/components/com_media/src/View/Adapters/JsonapiView.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * @package Joomla.API
+ * @subpackage com_media
+ *
+ * @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Media\Api\View\Adapters;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\MVC\View\JsonApiView as BaseApiView;
+use Joomla\Component\Media\Api\Helper\AdapterTrait;
+
+/**
+ * Media web service view
+ *
+ * @since __DEPLOY_VERSION__
+ */
+class JsonapiView extends BaseApiView
+{
+ use AdapterTrait;
+
+ /**
+ * The fields to render item in the documents
+ *
+ * @var array
+ * @since __DEPLOY_VERSION__
+ */
+ protected $fieldsToRenderItem = [
+ 'provider_id',
+ 'name',
+ 'path',
+ ];
+
+ /**
+ * The fields to render items in the documents
+ *
+ * @var array
+ * @since __DEPLOY_VERSION__
+ */
+ protected $fieldsToRenderList = [
+ 'provider_id',
+ 'name',
+ 'path',
+ ];
+}
diff --git a/api/components/com_media/src/View/Media/JsonapiView.php b/api/components/com_media/src/View/Media/JsonapiView.php
new file mode 100644
index 000000000000..84ee67bf32ed
--- /dev/null
+++ b/api/components/com_media/src/View/Media/JsonapiView.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * @package Joomla.API
+ * @subpackage com_media
+ *
+ * @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Media\Api\View\Media;
+
+\defined('_JEXEC') or die;
+
+use Joomla\CMS\MVC\View\JsonApiView as BaseApiView;
+use Joomla\Component\Media\Administrator\Provider\ProviderManager;
+use Joomla\Component\Media\Api\Helper\AdapterTrait;
+
+/**
+ * Media web service view
+ *
+ * @since __DEPLOY_VERSION__
+ */
+class JsonapiView extends BaseApiView
+{
+ use AdapterTrait;
+
+ /**
+ * The fields to render item in the documents
+ *
+ * @var array
+ * @since __DEPLOY_VERSION__
+ */
+ protected $fieldsToRenderItem = [
+ 'type',
+ 'name',
+ 'path',
+ 'extension',
+ 'size',
+ 'mime_type',
+ 'width',
+ 'height',
+ 'create_date',
+ 'create_date_formatted',
+ 'modified_date',
+ 'modified_date_formatted',
+ 'thumb_path',
+ 'adapter',
+ 'content',
+ 'url',
+ 'tempUrl',
+ ];
+
+ /**
+ * The fields to render items in the documents
+ *
+ * @var array
+ * @since __DEPLOY_VERSION__
+ */
+ protected $fieldsToRenderList = [
+ 'type',
+ 'name',
+ 'path',
+ 'extension',
+ 'size',
+ 'mime_type',
+ 'width',
+ 'height',
+ 'create_date',
+ 'create_date_formatted',
+ 'modified_date',
+ 'modified_date_formatted',
+ 'thumb_path',
+ 'adapter',
+ 'content',
+ 'url',
+ 'tempUrl',
+ ];
+
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function prepareItem($item)
+ {
+ // Media resources have no id.
+ $item->id = '0';
+
+ return $item;
+ }
+}
diff --git a/api/language/en-GB/com_media.ini b/api/language/en-GB/com_media.ini
new file mode 100644
index 000000000000..3e36202c201d
--- /dev/null
+++ b/api/language/en-GB/com_media.ini
@@ -0,0 +1,11 @@
+; Joomla! Project
+; (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+; License GNU General Public License version 2 or later; see LICENSE.txt
+; Note : All ini files need to be saved as UTF-8
+
+WEBSERVICE_COM_MEDIA="Media web service"
+WEBSERVICE_COM_MEDIA_MISSING_REQUIRED_PARAMETERS="Missing required parameter(s): %s"
+WEBSERVICE_COM_MEDIA_FILE_NOT_FOUND="File not found: %s"
+WEBSERVICE_COM_MEDIA_FILE_EXISTS="File exists and overwriting not requested: %s"
+WEBSERVICE_COM_MEDIA_BAD_FILE_TYPE="Invalid path or file type not allowed: %s"
+WEBSERVICE_COM_MEDIA_UNSUPPORTED_PARAMETER_COMBINATION="Unexpected or unsupported query parameter combination"
diff --git a/composer.json b/composer.json
index 75a1266667df..c760f66ef3f7 100644
--- a/composer.json
+++ b/composer.json
@@ -99,7 +99,8 @@
"codeception/module-db": "^1.0",
"codeception/module-rest": "^1.0",
"codeception/module-webdriver": "^1.0",
- "codeception/module-phpbrowser": "^1.0"
+ "codeception/module-phpbrowser": "^1.0",
+ "hoa/console": "^3.17"
},
"replace": {
"paragonie/random_compat": "9.99.99"
diff --git a/composer.lock b/composer.lock
index ed849b302350..df658ab10845 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "1da875478fc037b5b7b0c997043f2416",
+ "content-hash": "7a38a492e1140d3acdd45a4fb7f42486",
"packages": [
{
"name": "algo26-matthias/idna-convert",
@@ -6725,6 +6725,636 @@
],
"time": "2021-10-06T17:43:30+00:00"
},
+ {
+ "name": "hoa/consistency",
+ "version": "1.17.05.02",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Consistency.git",
+ "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Consistency/zipball/fd7d0adc82410507f332516faf655b6ed22e4c2f",
+ "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/exception": "~1.0",
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "hoa/stream": "~1.0",
+ "hoa/test": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Consistency\\": "."
+ },
+ "files": [
+ "Prelude.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "[email protected]"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Consistency library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "autoloader",
+ "callable",
+ "consistency",
+ "entity",
+ "flex",
+ "keyword",
+ "library"
+ ],
+ "support": {
+ "docs": "https://central.hoa-project.net/Documentation/Library/Consistency",
+ "email": "[email protected]",
+ "forum": "https://users.hoa-project.net/",
+ "irc": "irc://chat.freenode.net/hoaproject",
+ "issues": "https://github.com/hoaproject/Consistency/issues",
+ "source": "https://central.hoa-project.net/Resource/Library/Consistency"
+ },
+ "abandoned": true,
+ "time": "2017-05-02T12:18:12+00:00"
+ },
+ {
+ "name": "hoa/console",
+ "version": "3.17.05.02",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Console.git",
+ "reference": "e231fd3ea70e6d773576ae78de0bdc1daf331a66"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Console/zipball/e231fd3ea70e6d773576ae78de0bdc1daf331a66",
+ "reference": "e231fd3ea70e6d773576ae78de0bdc1daf331a66",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/event": "~1.0",
+ "hoa/exception": "~1.0",
+ "hoa/file": "~1.0",
+ "hoa/protocol": "~1.0",
+ "hoa/stream": "~1.0",
+ "hoa/ustring": "~4.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "suggest": {
+ "ext-pcntl": "To enable hoa://Event/Console/Window:resize.",
+ "hoa/dispatcher": "To use the console kit.",
+ "hoa/router": "To use the console kit."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Console\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "[email protected]"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Console library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "autocompletion",
+ "chrome",
+ "cli",
+ "console",
+ "cursor",
+ "getoption",
+ "library",
+ "option",
+ "parser",
+ "processus",
+ "readline",
+ "terminfo",
+ "tput",
+ "window"
+ ],
+ "support": {
+ "docs": "https://central.hoa-project.net/Documentation/Library/Console",
+ "email": "[email protected]",
+ "forum": "https://users.hoa-project.net/",
+ "irc": "irc://chat.freenode.net/hoaproject",
+ "issues": "https://github.com/hoaproject/Console/issues",
+ "source": "https://central.hoa-project.net/Resource/Library/Console"
+ },
+ "abandoned": true,
+ "time": "2017-05-02T12:26:19+00:00"
+ },
+ {
+ "name": "hoa/event",
+ "version": "1.17.01.13",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Event.git",
+ "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Event/zipball/6c0060dced212ffa3af0e34bb46624f990b29c54",
+ "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/exception": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Event\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "[email protected]"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Event library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "event",
+ "library",
+ "listener",
+ "observer"
+ ],
+ "support": {
+ "docs": "https://central.hoa-project.net/Documentation/Library/Event",
+ "email": "[email protected]",
+ "forum": "https://users.hoa-project.net/",
+ "irc": "irc://chat.freenode.net/hoaproject",
+ "issues": "https://github.com/hoaproject/Event/issues",
+ "source": "https://central.hoa-project.net/Resource/Library/Event"
+ },
+ "abandoned": true,
+ "time": "2017-01-13T15:30:50+00:00"
+ },
+ {
+ "name": "hoa/exception",
+ "version": "1.17.01.16",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Exception.git",
+ "reference": "091727d46420a3d7468ef0595651488bfc3a458f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Exception/zipball/091727d46420a3d7468ef0595651488bfc3a458f",
+ "reference": "091727d46420a3d7468ef0595651488bfc3a458f",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/event": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Exception\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "[email protected]"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Exception library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "exception",
+ "library"
+ ],
+ "support": {
+ "docs": "https://central.hoa-project.net/Documentation/Library/Exception",
+ "email": "[email protected]",
+ "forum": "https://users.hoa-project.net/",
+ "irc": "irc://chat.freenode.net/hoaproject",
+ "issues": "https://github.com/hoaproject/Exception/issues",
+ "source": "https://central.hoa-project.net/Resource/Library/Exception"
+ },
+ "abandoned": true,
+ "time": "2017-01-16T07:53:27+00:00"
+ },
+ {
+ "name": "hoa/file",
+ "version": "1.17.07.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/File.git",
+ "reference": "35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/File/zipball/35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca",
+ "reference": "35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/event": "~1.0",
+ "hoa/exception": "~1.0",
+ "hoa/iterator": "~2.0",
+ "hoa/stream": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\File\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "[email protected]"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\File library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "Socket",
+ "directory",
+ "file",
+ "finder",
+ "library",
+ "link",
+ "temporary"
+ ],
+ "support": {
+ "docs": "https://central.hoa-project.net/Documentation/Library/File",
+ "email": "[email protected]",
+ "forum": "https://users.hoa-project.net/",
+ "irc": "irc://chat.freenode.net/hoaproject",
+ "issues": "https://github.com/hoaproject/File/issues",
+ "source": "https://central.hoa-project.net/Resource/Library/File"
+ },
+ "abandoned": true,
+ "time": "2017-07-11T07:42:15+00:00"
+ },
+ {
+ "name": "hoa/iterator",
+ "version": "2.17.01.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Iterator.git",
+ "reference": "d1120ba09cb4ccd049c86d10058ab94af245f0cc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Iterator/zipball/d1120ba09cb4ccd049c86d10058ab94af245f0cc",
+ "reference": "d1120ba09cb4ccd049c86d10058ab94af245f0cc",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/exception": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Iterator\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "[email protected]"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Iterator library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "iterator",
+ "library"
+ ],
+ "support": {
+ "docs": "https://central.hoa-project.net/Documentation/Library/Iterator",
+ "email": "[email protected]",
+ "forum": "https://users.hoa-project.net/",
+ "irc": "irc://chat.freenode.net/hoaproject",
+ "issues": "https://github.com/hoaproject/Iterator/issues",
+ "source": "https://central.hoa-project.net/Resource/Library/Iterator"
+ },
+ "abandoned": true,
+ "time": "2017-01-10T10:34:47+00:00"
+ },
+ {
+ "name": "hoa/protocol",
+ "version": "1.17.01.14",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Protocol.git",
+ "reference": "5c2cf972151c45f373230da170ea015deecf19e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Protocol/zipball/5c2cf972151c45f373230da170ea015deecf19e2",
+ "reference": "5c2cf972151c45f373230da170ea015deecf19e2",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/exception": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Protocol\\": "."
+ },
+ "files": [
+ "Wrapper.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "[email protected]"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Protocol library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "library",
+ "protocol",
+ "resource",
+ "stream",
+ "wrapper"
+ ],
+ "support": {
+ "docs": "https://central.hoa-project.net/Documentation/Library/Protocol",
+ "email": "[email protected]",
+ "forum": "https://users.hoa-project.net/",
+ "irc": "irc://chat.freenode.net/hoaproject",
+ "issues": "https://github.com/hoaproject/Protocol/issues",
+ "source": "https://central.hoa-project.net/Resource/Library/Protocol"
+ },
+ "abandoned": true,
+ "time": "2017-01-14T12:26:10+00:00"
+ },
+ {
+ "name": "hoa/stream",
+ "version": "1.17.02.21",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Stream.git",
+ "reference": "3293cfffca2de10525df51436adf88a559151d82"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Stream/zipball/3293cfffca2de10525df51436adf88a559151d82",
+ "reference": "3293cfffca2de10525df51436adf88a559151d82",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/event": "~1.0",
+ "hoa/exception": "~1.0",
+ "hoa/protocol": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Stream\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "[email protected]"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Stream library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "Context",
+ "bucket",
+ "composite",
+ "filter",
+ "in",
+ "library",
+ "out",
+ "protocol",
+ "stream",
+ "wrapper"
+ ],
+ "support": {
+ "docs": "https://central.hoa-project.net/Documentation/Library/Stream",
+ "email": "[email protected]",
+ "forum": "https://users.hoa-project.net/",
+ "irc": "irc://chat.freenode.net/hoaproject",
+ "issues": "https://github.com/hoaproject/Stream/issues",
+ "source": "https://central.hoa-project.net/Resource/Library/Stream"
+ },
+ "abandoned": true,
+ "time": "2017-02-21T16:01:06+00:00"
+ },
+ {
+ "name": "hoa/ustring",
+ "version": "4.17.01.16",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Ustring.git",
+ "reference": "e6326e2739178799b1fe3fdd92029f9517fa17a0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Ustring/zipball/e6326e2739178799b1fe3fdd92029f9517fa17a0",
+ "reference": "e6326e2739178799b1fe3fdd92029f9517fa17a0",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/exception": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "suggest": {
+ "ext-iconv": "ext/iconv must be present (or a third implementation) to use Hoa\\Ustring::transcode().",
+ "ext-intl": "To get a better Hoa\\Ustring::toAscii() and Hoa\\Ustring::compareTo()."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Ustring\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "[email protected]"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Ustring library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "library",
+ "search",
+ "string",
+ "unicode"
+ ],
+ "support": {
+ "docs": "https://central.hoa-project.net/Documentation/Library/Ustring",
+ "email": "[email protected]",
+ "forum": "https://users.hoa-project.net/",
+ "irc": "irc://chat.freenode.net/hoaproject",
+ "issues": "https://github.com/hoaproject/Ustring/issues",
+ "source": "https://central.hoa-project.net/Resource/Library/Ustring"
+ },
+ "abandoned": true,
+ "time": "2017-01-16T07:08:25+00:00"
+ },
{
"name": "joomla-projects/joomla-browser",
"version": "v4.0.0.x-dev",
diff --git a/installation/sql/mysql/base.sql b/installation/sql/mysql/base.sql
index d7a5ea81915d..82fd5d03509e 100644
--- a/installation/sql/mysql/base.sql
+++ b/installation/sql/mysql/base.sql
@@ -353,6 +353,7 @@ INSERT INTO `#__extensions` (`package_id`, `name`, `type`, `element`, `folder`,
(0, 'plg_webservices_content', 'plugin', 'content', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 4, 0),
(0, 'plg_webservices_installer', 'plugin', 'installer', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 5, 0),
(0, 'plg_webservices_languages', 'plugin', 'languages', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 6, 0),
+(0, 'plg_webservices_media', 'plugin', 'media', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 7, 0),
(0, 'plg_webservices_menus', 'plugin', 'menus', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 7, 0),
(0, 'plg_webservices_messages', 'plugin', 'messages', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 8, 0),
(0, 'plg_webservices_modules', 'plugin', 'modules', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 9, 0),
diff --git a/installation/sql/postgresql/base.sql b/installation/sql/postgresql/base.sql
index 55d319d745aa..8786f98bd20e 100644
--- a/installation/sql/postgresql/base.sql
+++ b/installation/sql/postgresql/base.sql
@@ -359,6 +359,7 @@ INSERT INTO "#__extensions" ("package_id", "name", "type", "element", "folder",
(0, 'plg_webservices_content', 'plugin', 'content', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 4, 0),
(0, 'plg_webservices_installer', 'plugin', 'installer', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 5, 0),
(0, 'plg_webservices_languages', 'plugin', 'languages', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 6, 0),
+(0, 'plg_webservices_media', 'plugin', 'media', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 7, 0),
(0, 'plg_webservices_menus', 'plugin', 'menus', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 7, 0),
(0, 'plg_webservices_messages', 'plugin', 'messages', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 8, 0),
(0, 'plg_webservices_modules', 'plugin', 'modules', 'webservices', 0, 1, 1, 0, 1, '', '{}', '', 9, 0),
diff --git a/libraries/src/Error/JsonApi/SaveExceptionHandler.php b/libraries/src/Error/JsonApi/SaveExceptionHandler.php
index fe76941e55e2..a8ae0a15dffe 100644
--- a/libraries/src/Error/JsonApi/SaveExceptionHandler.php
+++ b/libraries/src/Error/JsonApi/SaveExceptionHandler.php
@@ -53,7 +53,10 @@ public function handle(Exception $e)
$status = $e->getCode();
}
- $error = ['title' => $e->getMessage()];
+ $error = [
+ 'title' => $e->getMessage(),
+ 'code' => $status,
+ ];
return new ResponseBag($status, [$error]);
}
diff --git a/plugins/webservices/media/media.php b/plugins/webservices/media/media.php
new file mode 100644
index 000000000000..cde9ffe1c45e
--- /dev/null
+++ b/plugins/webservices/media/media.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * @package Joomla.Plugin
+ * @subpackage Webservices.Media
+ *
+ * @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+defined('_JEXEC') or die;
+
+use Joomla\CMS\Plugin\CMSPlugin;
+use Joomla\CMS\Router\ApiRouter;
+use Joomla\Router\Route;
+
+/**
+ * Web Services adapter for com_media.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+class PlgWebservicesMedia extends CMSPlugin
+{
+ /**
+ * Load the language file on instantiation.
+ *
+ * @var boolean
+ * @since __DEPLOY_VERSION__
+ */
+ protected $autoloadLanguage = true;
+
+ /**
+ * Registers com_media's API's routes in the application.
+ *
+ * @param ApiRouter &$router The API Routing object
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function onBeforeApiRoute(&$router): void
+ {
+ $this->createAdapterReadRoutes(
+ $router,
+ 'v1/media/adapters',
+ 'adapters',
+ ['component' => 'com_media']
+ );
+ $this->createMediaCRUDRoutes(
+ $router,
+ 'v1/media/files',
+ 'media',
+ ['component' => 'com_media']
+ );
+ }
+
+ /**
+ * Creates adapter read routes.
+ *
+ * @param ApiRouter &$router The API Routing object
+ * @param string $baseName The base name of the component.
+ * @param string $controller The name of the controller that contains CRUD functions.
+ * @param array $defaults An array of default values that are used when the URL is matched.
+ * @param bool $publicGets Allow the public to make GET requests.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function createAdapterReadRoutes(&$router, $baseName, $controller, $defaults = [], $publicGets = false): void
+ {
+ $getDefaults = array_merge(['public' => $publicGets], $defaults);
+
+ $routes = [
+ new Route(['GET'], $baseName, $controller . '.displayList', [], $getDefaults),
+ new Route(['GET'], $baseName . '/:id', $controller . '.displayItem', [], $getDefaults),
+ ];
+
+ $router->addRoutes($routes);
+ }
+
+ /**
+ * Creates media CRUD routes.
+ *
+ * @param ApiRouter &$router The API Routing object
+ * @param string $baseName The base name of the component.
+ * @param string $controller The name of the controller that contains CRUD functions.
+ * @param array $defaults An array of default values that are used when the URL is matched.
+ * @param bool $publicGets Allow the public to make GET requests.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function createMediaCRUDRoutes(&$router, $baseName, $controller, $defaults = [], $publicGets = false): void
+ {
+ $getDefaults = array_merge(['public' => $publicGets], $defaults);
+
+ $routes = [
+ new Route(['GET'], $baseName, $controller . '.displayList', [], $getDefaults),
+ // When the path ends with a backslash, then list the items
+ new Route(['GET'], $baseName . '/:path/', $controller . '.displayList', ['path' => '.*\/'], $getDefaults),
+ new Route(['GET'], $baseName . '/:path', $controller . '.displayItem', ['path' => '.*'], $getDefaults),
+ new Route(['POST'], $baseName, $controller . '.add', [], $defaults),
+ new Route(['PATCH'], $baseName . '/:path', $controller . '.edit', ['path' => '.*'], $defaults),
+ new Route(['DELETE'], $baseName . '/:path', $controller . '.delete', ['path' => '.*'], $defaults),
+ ];
+
+ $router->addRoutes($routes);
+ }
+}
diff --git a/plugins/webservices/media/media.xml b/plugins/webservices/media/media.xml
new file mode 100644
index 000000000000..95574782634d
--- /dev/null
+++ b/plugins/webservices/media/media.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<extension type="plugin" group="webservices" method="upgrade">
+ <name>plg_webservices_media</name>
+ <author>Joomla! Project</author>
+ <creationDate>May 2021</creationDate>
+ <copyright>(C) 2021 Open Source Matters, Inc.</copyright>
+ <license>GNU General Public License version 2 or later; see LICENSE.txt</license>
+ <authorEmail>[email protected]</authorEmail>
+ <authorUrl>www.joomla.org</authorUrl>
+ <version>__DEPLOY_VERSION__</version>
+ <description>PLG_WEBSERVICES_MEDIA_XML_DESCRIPTION</description>
+ <files>
+ <filename plugin="media">media.php</filename>
+ </files>
+ <languages>
+ <language tag="en-GB">language/en-GB/en-GB.plg_webservices_media.ini</language>
+ <language tag="en-GB">language/en-GB/en-GB.plg_webservices_media.sys.ini</language>
+ </languages>
+</extension>
diff --git a/tests/Codeception/_support/Helper/Api.php b/tests/Codeception/_support/Helper/Api.php
index c0c4e97747a7..cf6fd685e4b3 100644
--- a/tests/Codeception/_support/Helper/Api.php
+++ b/tests/Codeception/_support/Helper/Api.php
@@ -21,4 +21,58 @@
*/
class Api extends Module
{
+ /**
+ * Creates a user for API authentication and returns a bearer token.
+ *
+ * @return string The token
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getBearerToken(): string
+ {
+ /** @var JoomlaDb $db */
+ $db = $this->getModule('Helper\\JoomlaDb');
+
+ $desiredUserId = 3;
+
+ if (!$db->grabFromDatabase('users', 'id', ['id' => $desiredUserId]))
+ {
+ $db->haveInDatabase(
+ 'users',
+ [
+ 'id' => $desiredUserId,
+ 'name' => 'API',
+ 'email' => '[email protected]',
+ 'username' => 'api',
+ 'password' => '123',
+ 'block' => 0,
+ 'registerDate' => '2000-01-01',
+ 'params' => '{}'
+ ],
+ []
+ );
+ $db->haveInDatabase('user_usergroup_map', ['user_id' => $desiredUserId, 'group_id' => 8]);
+ $enabledData = ['user_id' => $desiredUserId, 'profile_key' => 'joomlatoken.enabled', 'profile_value' => 1];
+ $tokenData = ['user_id' => $desiredUserId, 'profile_key' => 'joomlatoken.token', 'profile_value' => 'dOi2m1NRrnBHlhaWK/WWxh3B5tqq1INbdf4DhUmYTI4='];
+ $db->haveInDatabase('user_profiles', $enabledData);
+ $db->haveInDatabase('user_profiles', $tokenData);
+ }
+
+ return 'c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==';
+ }
+
+ /**
+ * Creates a user for API authentication and returns a bearer token.
+ *
+ * @param string $name The name of the config key
+ * @param string $module The module
+ *
+ * @return string The config key
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getConfig($name, $module = 'Helper\Api'): string
+ {
+ return $this->getModule($module)->_getConfig()[$name];
+ }
}
diff --git a/tests/Codeception/_support/Helper/JoomlaDb.php b/tests/Codeception/_support/Helper/JoomlaDb.php
index d508c65704a6..d3a892b6eda7 100644
--- a/tests/Codeception/_support/Helper/JoomlaDb.php
+++ b/tests/Codeception/_support/Helper/JoomlaDb.php
@@ -164,6 +164,23 @@ public function updateInDatabase($table, array $data, array $criteria = [])
parent::updateInDatabase($table, $data, $criteria);
}
+ /**
+ * Deletes records in a database.
+ *
+ * @param string $table Table name
+ * @param array $criteria Search criteria [Optional]
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function deleteFromDatabase($table, $criteria = []): void
+ {
+ $table = $this->addPrefix($table);
+
+ $this->driver->deleteQueryByCriteria($table, $criteria);
+ }
+
/**
* Add the table prefix.
*
diff --git a/tests/Codeception/acceptance/01-install/InstallCest.php b/tests/Codeception/acceptance/01-install/InstallCest.php
index d7832b7042d4..2e3ef6b32496 100644
--- a/tests/Codeception/acceptance/01-install/InstallCest.php
+++ b/tests/Codeception/acceptance/01-install/InstallCest.php
@@ -26,7 +26,7 @@ class InstallCest
public function installJoomla(AcceptanceTester $I)
{
$I->am('Administrator');
- $I->installJoomlaRemovingInstallationFolder();
+ $I->installJoomla();
}
/**
diff --git a/tests/Codeception/api.suite.dist.yml b/tests/Codeception/api.suite.dist.yml
index 12a74716bb1e..a8f50914482e 100644
--- a/tests/Codeception/api.suite.dist.yml
+++ b/tests/Codeception/api.suite.dist.yml
@@ -2,7 +2,7 @@ actor: ApiTester
modules:
enabled:
- Helper\JoomlaDb
- - \Helper\Api
+ - Helper\Api
- REST:
url: http://localhost/test-install/api/index.php/v1
depends: PhpBrowser
@@ -13,3 +13,7 @@ modules:
user: 'root'
password: 'joomla_ut'
prefix: 'jos_'
+ Helper\Api:
+ url: 'http://localhost/test-install'
+ cmsPath: '/tests/www/test-install'
+ localUser: 'www-data'
diff --git a/tests/Codeception/api/BasicCest.php b/tests/Codeception/api/BasicCest.php
index 784628f2848e..8e30340b758c 100644
--- a/tests/Codeception/api/BasicCest.php
+++ b/tests/Codeception/api/BasicCest.php
@@ -17,39 +17,6 @@
*/
class BasicCest
{
- /**
- * Api test before running.
- *
- * @param mixed ApiTester $I Api tester
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function _before(ApiTester $I)
- {
- // TODO: Improve this to retrieve a specific ID to replace with a known ID
- $desiredUserId = 3;
- $I->updateInDatabase('users', ['id' => 3], []);
- $I->updateInDatabase('user_usergroup_map', ['user_id' => 3], []);
- $enabledData = ['user_id' => $desiredUserId, 'profile_key' => 'joomlatoken.enabled', 'profile_value' => 1];
- $tokenData = ['user_id' => $desiredUserId, 'profile_key' => 'joomlatoken.token', 'profile_value' => 'dOi2m1NRrnBHlhaWK/WWxh3B5tqq1INbdf4DhUmYTI4='];
- $I->haveInDatabase('user_profiles', $enabledData);
- $I->haveInDatabase('user_profiles', $tokenData);
- }
-
- /**
- * Api test after running.
- *
- * @param mixed ApiTester $I Api tester
- *
- * @return void
- * @since 4.0.0
- */
- public function _after(ApiTester $I)
- {
- }
-
/**
* Test logging in with wrong credentials.
*
@@ -78,7 +45,7 @@ public function testWrongCredentials(ApiTester $I)
*/
public function testContentNegotiation(ApiTester $I)
{
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'text/xml');
$I->sendGET('/content/articles/1');
$I->seeResponseCodeIs(Codeception\Util\HttpCode::NOT_ACCEPTABLE);
@@ -95,7 +62,7 @@ public function testContentNegotiation(ApiTester $I)
*/
public function testRouteNotFound(ApiTester $I)
{
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
$I->sendGET('/not/existing/1');
$I->seeResponseCodeIs(Codeception\Util\HttpCode::NOT_FOUND);
diff --git a/tests/Codeception/api/com_banners/BannerCest.php b/tests/Codeception/api/com_banners/BannerCest.php
index d2ac914ab6d0..3eb10f39fb6e 100644
--- a/tests/Codeception/api/com_banners/BannerCest.php
+++ b/tests/Codeception/api/com_banners/BannerCest.php
@@ -29,27 +29,8 @@ class BannerCest
*/
public function _before(ApiTester $I)
{
- // TODO: Improve this to retrieve a specific ID to replace with a known ID
- $desiredUserId = 3;
- $I->updateInDatabase('users', ['id' => 3], []);
- $I->updateInDatabase('user_usergroup_map', ['user_id' => 3], []);
- $enabledData = ['user_id' => $desiredUserId, 'profile_key' => 'joomlatoken.enabled', 'profile_value' => 1];
- $tokenData = ['user_id' => $desiredUserId, 'profile_key' => 'joomlatoken.token', 'profile_value' => 'dOi2m1NRrnBHlhaWK/WWxh3B5tqq1INbdf4DhUmYTI4='];
- $I->haveInDatabase('user_profiles', $enabledData);
- $I->haveInDatabase('user_profiles', $tokenData);
- }
-
- /**
- * Api test after running.
- *
- * @param mixed ApiTester $I Api tester
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function _after(ApiTester $I)
- {
+ $I->deleteFromDatabase('banners');
+ $I->deleteFromDatabase('categories', ['id >' => 7]);
}
/**
@@ -65,7 +46,7 @@ public function _after(ApiTester $I)
*/
public function testCrudOnBanner(ApiTester $I)
{
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
@@ -86,23 +67,24 @@ public function testCrudOnBanner(ApiTester $I)
$I->sendPOST('/banners', $testBanner);
$I->seeResponseCodeIs(HttpCode::OK);
+ $id = $I->grabDataFromResponseByJsonPath('$.data.id')[0];
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
- $I->sendGET('/banners/1');
+ $I->sendGET('/banners/' . $id);
$I->seeResponseCodeIs(HttpCode::OK);
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
// Category is a required field for this patch request for now TODO: Remove this dependency
- $I->sendPATCH('/banners/1', ['name' => 'Different Custom Advert', 'state' => -2, 'catid' => 3]);
+ $I->sendPATCH('/banners/' . $id, ['name' => 'Different Custom Advert', 'state' => -2, 'catid' => 3]);
$I->seeResponseCodeIs(HttpCode::OK);
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
- $I->sendDELETE('/banners/1');
+ $I->sendDELETE('/banners/' . $id);
$I->seeResponseCodeIs(HttpCode::NO_CONTENT);
}
@@ -119,7 +101,7 @@ public function testCrudOnBanner(ApiTester $I)
*/
public function testCrudOnCategory(ApiTester $I)
{
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
@@ -133,12 +115,12 @@ public function testCrudOnCategory(ApiTester $I)
$I->seeResponseCodeIs(HttpCode::OK);
$categoryId = $I->grabDataFromResponseByJsonPath('$.data.id')[0];
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
$I->sendGET('/banners/categories/' . $categoryId);
$I->seeResponseCodeIs(HttpCode::OK);
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
@@ -146,7 +128,7 @@ public function testCrudOnCategory(ApiTester $I)
$I->sendPATCH('/banners/categories/' . $categoryId, ['title' => 'Another Title', 'published' => -2]);
$I->seeResponseCodeIs(HttpCode::OK);
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
$I->sendDELETE('/banners/categories/' . $categoryId);
$I->seeResponseCodeIs(HttpCode::NO_CONTENT);
diff --git a/tests/Codeception/api/com_contact/ContactCest.php b/tests/Codeception/api/com_contact/ContactCest.php
index d2bad11bb63d..ce3f93e865d6 100644
--- a/tests/Codeception/api/com_contact/ContactCest.php
+++ b/tests/Codeception/api/com_contact/ContactCest.php
@@ -29,27 +29,8 @@ class ContactCest
*/
public function _before(ApiTester $I)
{
- // TODO: Improve this to retrieve a specific ID to replace with a known ID
- $desiredUserId = 3;
- $I->updateInDatabase('users', ['id' => 3], []);
- $I->updateInDatabase('user_usergroup_map', ['user_id' => 3], []);
- $enabledData = ['user_id' => $desiredUserId, 'profile_key' => 'joomlatoken.enabled', 'profile_value' => 1];
- $tokenData = ['user_id' => $desiredUserId, 'profile_key' => 'joomlatoken.token', 'profile_value' => 'dOi2m1NRrnBHlhaWK/WWxh3B5tqq1INbdf4DhUmYTI4='];
- $I->haveInDatabase('user_profiles', $enabledData);
- $I->haveInDatabase('user_profiles', $tokenData);
- }
-
- /**
- * Api test after running.
- *
- * @param mixed ApiTester $I Api tester
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function _after(ApiTester $I)
- {
+ $I->deleteFromDatabase('contact_details');
+ $I->deleteFromDatabase('categories', ['id >' => 7]);
}
/**
@@ -65,7 +46,7 @@ public function _after(ApiTester $I)
*/
public function testCrudOnContact(ApiTester $I)
{
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
@@ -79,23 +60,24 @@ public function testCrudOnContact(ApiTester $I)
$I->sendPOST('/contacts', $testarticle);
$I->seeResponseCodeIs(HttpCode::OK);
+ $id = $I->grabDataFromResponseByJsonPath('$.data.id')[0];
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
- $I->sendGET('/contacts/1');
+ $I->sendGET('/contacts/' . $id);
$I->seeResponseCodeIs(HttpCode::OK);
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
// Category is a required field for this patch request for now TODO: Remove this dependency
- $I->sendPATCH('/contacts/1', ['name' => 'Frankie Blogs', 'catid' => 4, 'published' => -2]);
+ $I->sendPATCH('/contacts/' . $id, ['name' => 'Frankie Blogs', 'catid' => 4, 'published' => -2]);
$I->seeResponseCodeIs(HttpCode::OK);
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
- $I->sendDELETE('/contacts/1');
+ $I->sendDELETE('/contacts/' . $id);
$I->seeResponseCodeIs(HttpCode::NO_CONTENT);
}
@@ -112,7 +94,7 @@ public function testCrudOnContact(ApiTester $I)
*/
public function testCrudOnCategory(ApiTester $I)
{
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
@@ -129,18 +111,18 @@ public function testCrudOnCategory(ApiTester $I)
$I->seeResponseCodeIs(HttpCode::OK);
$categoryId = $I->grabDataFromResponseByJsonPath('$.data.id')[0];
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
$I->sendGET('/contacts/categories/' . $categoryId);
$I->seeResponseCodeIs(HttpCode::OK);
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
$I->sendPATCH('/contacts/categories/' . $categoryId, ['title' => 'Another Title', 'published' => -2]);
$I->seeResponseCodeIs(HttpCode::OK);
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
$I->sendDELETE('/contacts/categories/' . $categoryId);
$I->seeResponseCodeIs(HttpCode::NO_CONTENT);
diff --git a/tests/Codeception/api/com_content/ContentCest.php b/tests/Codeception/api/com_content/ContentCest.php
index 08ac412e7785..9a75b6cc35e7 100644
--- a/tests/Codeception/api/com_content/ContentCest.php
+++ b/tests/Codeception/api/com_content/ContentCest.php
@@ -29,27 +29,8 @@ class ContentCest
*/
public function _before(ApiTester $I)
{
- // TODO: Improve this to retrieve a specific ID to replace with a known ID
- $desiredUserId = 3;
- $I->updateInDatabase('users', ['id' => 3], []);
- $I->updateInDatabase('user_usergroup_map', ['user_id' => 3], []);
- $enabledData = ['user_id' => $desiredUserId, 'profile_key' => 'joomlatoken.enabled', 'profile_value' => 1];
- $tokenData = ['user_id' => $desiredUserId, 'profile_key' => 'joomlatoken.token', 'profile_value' => 'dOi2m1NRrnBHlhaWK/WWxh3B5tqq1INbdf4DhUmYTI4='];
- $I->haveInDatabase('user_profiles', $enabledData);
- $I->haveInDatabase('user_profiles', $tokenData);
- }
-
- /**
- * Api test after running.
- *
- * @param mixed ApiTester $I Api tester
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function _after(ApiTester $I)
- {
+ $I->deleteFromDatabase('content');
+ $I->deleteFromDatabase('categories', ['id >' => 7]);
}
/**
@@ -65,7 +46,7 @@ public function _after(ApiTester $I)
*/
public function testCrudOnArticle(ApiTester $I)
{
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
@@ -80,21 +61,22 @@ public function testCrudOnArticle(ApiTester $I)
$I->sendPOST('/content/articles', $testarticle);
$I->seeResponseCodeIs(HttpCode::OK);
+ $id = $I->grabDataFromResponseByJsonPath('$.data.id')[0];
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
- $I->sendGET('/content/articles/1');
+ $I->sendGET('/content/articles/' . $id);
$I->seeResponseCodeIs(HttpCode::OK);
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
- $I->sendPATCH('/content/articles/1', ['title' => 'Another Title', 'state' => -2, 'catid' => 2]);
+ $I->sendPATCH('/content/articles/' . $id, ['title' => 'Another Title', 'state' => -2, 'catid' => 2]);
$I->seeResponseCodeIs(HttpCode::OK);
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
- $I->sendDELETE('/content/articles/1');
+ $I->sendDELETE('/content/articles/' . $id);
$I->seeResponseCodeIs(HttpCode::NO_CONTENT);
}
@@ -112,7 +94,7 @@ public function testCrudOnArticle(ApiTester $I)
public function testCrudOnCategory(ApiTester $I)
{
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
@@ -129,18 +111,18 @@ public function testCrudOnCategory(ApiTester $I)
$I->seeResponseCodeIs(HttpCode::OK);
$categoryId = $I->grabDataFromResponseByJsonPath('$.data.id')[0];
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
$I->sendGET('/content/categories/' . $categoryId);
$I->seeResponseCodeIs(HttpCode::OK);
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
$I->sendPATCH('/content/categories/' . $categoryId, ['title' => 'Another Title', 'params' => ['workflow_id' => 'inherit'], 'published' => -2]);
$I->seeResponseCodeIs(HttpCode::OK);
- $I->amBearerAuthenticated('c2hhMjU2OjM6ZTJmMjJlYTNlNTU0NmM1MDJhYTIzYzMwN2MxYzAwZTQ5NzJhMWRmOTUyNjY5MTk2YjE5ODJmZWMwZTcxNzgwMQ==');
+ $I->amBearerAuthenticated($I->getBearerToken());
$I->haveHttpHeader('Accept', 'application/vnd.api+json');
$I->sendDELETE('/content/categories/' . $categoryId);
$I->seeResponseCodeIs(HttpCode::NO_CONTENT);
diff --git a/tests/Codeception/api/com_media/MediaCest.php b/tests/Codeception/api/com_media/MediaCest.php
new file mode 100644
index 000000000000..e6f973cba5d1
--- /dev/null
+++ b/tests/Codeception/api/com_media/MediaCest.php
@@ -0,0 +1,405 @@
+<?php
+/**
+ * @package Joomla.Tests
+ * @subpackage Api.tests
+ *
+ * @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+use Codeception\Util\FileSystem;
+use Codeception\Util\HttpCode;
+
+/**
+ * Class MediaCest.
+ *
+ * Basic com_media (files) tests.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+class MediaCest
+{
+ /**
+ * The name of the test directory, which gets deleted after each test.
+ *
+ * @var string
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private $testDirectory = 'test-dir';
+
+ /**
+ * Runs before every test.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @throws Exception
+ */
+ public function _before(ApiTester $I)
+ {
+ if (file_exists($this->getImagesDirectory($I)))
+ {
+ FileSystem::deleteDir($this->getImagesDirectory($I));
+ }
+
+ // Copied from \Step\Acceptance\Administrator\Media:createDirectory()
+ $oldUmask = @umask(0);
+ @mkdir($this->getImagesDirectory($I), 0755, true);
+
+ if (!empty($user = $I->getConfig('localUser')))
+ {
+ @chown($this->getImagesDirectory($I), $user);
+ }
+
+ @umask($oldUmask);
+ }
+
+ /**
+ * Runs after every test.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @throws Exception
+ */
+ public function _after(ApiTester $I)
+ {
+ // Delete the test directory
+ FileSystem::deleteDir($this->getImagesDirectory($I));
+ }
+
+ /**
+ * Test the GET media adapter endpoint of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testGetAdapters(ApiTester $I)
+ {
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendGET('/media/adapters');
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['provider_id' => 'local', 'name' => 'images']);
+ }
+
+ /**
+ * Test the GET media adapter endpoint for a single adapter of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testGetAdapter(ApiTester $I)
+ {
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendGET('/media/adapters/local-images');
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['provider_id' => 'local', 'name' => 'images']);
+ }
+
+ /**
+ * Test the GET media files endpoint of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testGetFiles(ApiTester $I)
+ {
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendGET('/media/files');
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['data' => ['attributes' => ['type' => 'dir', 'name' => 'banners']]]);
+ $I->seeResponseContainsJson(['data' => ['attributes' => ['type' => 'file', 'name' => 'joomla_black.png']]]);
+ }
+
+ /**
+ * Test the GET media files endpoint of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testGetFilesInSubfolder(ApiTester $I)
+ {
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendGET('/media/files/sampledata/cassiopeia/');
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['data' => ['attributes' => ['type' => 'file', 'name' => 'nasa1-1200.jpg']]]);
+ }
+
+ /**
+ * Test the GET media files endpoint of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testGetFilesWithAdapter(ApiTester $I)
+ {
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendGET('/media/files/local-images:/sampledata/cassiopeia/');
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['data' => ['attributes' => ['type' => 'file', 'name' => 'nasa1-1200.jpg']]]);
+ }
+
+ /**
+ * Test the GET media files endpoint of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testSearchFiles(ApiTester $I)
+ {
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendGET('/media/files?filter[search]=joomla');
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['data' => ['attributes' => ['type' => 'file', 'name' => 'joomla_black.png']]]);
+ $I->dontSeeResponseContainsJson(['data' => ['attributes' => ['type' => 'dir', 'name' => 'powered_by.png']]]);
+ $I->dontSeeResponseContainsJson(['data' => ['attributes' => ['type' => 'dir', 'name' => 'banners']]]);
+ }
+
+ /**
+ * Test the GET media files endpoint for a single file of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testGetFile(ApiTester $I)
+ {
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendGET('/media/files/joomla_black.png');
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['data' => ['attributes' => ['type' => 'file', 'name' => 'joomla_black.png']]]);
+ $I->dontSeeResponseContainsJson(['data' => ['attributes' => ['url' => $I->getConfig('url') . '/images/joomla_black.png']]]);
+ }
+
+ /**
+ * Test the GET media files endpoint for a single file of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testGetFileWithUrl(ApiTester $I)
+ {
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendGET('/media/files/joomla_black.png?url=1');
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['data' => ['attributes' => ['url' => $I->getConfig('url') . '/images/joomla_black.png']]]);
+ }
+
+ /**
+ * Test the GET media files endpoint for a single file of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testGetFolder(ApiTester $I)
+ {
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendGET('/media/files/sampledata/cassiopeia');
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['data' => ['attributes' => ['type' => 'dir', 'name' => 'cassiopeia']]]);
+ }
+
+ /**
+ * Test the POST media files endpoint of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testCreateFile(ApiTester $I)
+ {
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Content-Type', 'application/json');
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendPost(
+ '/media/files',
+ [
+ 'path' => $this->testDirectory . '/test.jpg',
+ 'content' => base64_encode(file_get_contents(codecept_data_dir() . '/com_media/test-image-1.jpg'))
+ ]
+ );
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['data' => ['attributes' => ['type' => 'file', 'name' => 'test.jpg']]]);
+ }
+
+ /**
+ * Test the POST media files endpoint of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testCreateFolder(ApiTester $I)
+ {
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Content-Type', 'application/json');
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendPost(
+ '/media/files',
+ ['path' => $this->testDirectory . '/test-from-create']
+ );
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['data' => ['attributes' => ['type' => 'dir', 'name' => 'test-from-create']]]);
+ }
+
+ /**
+ * Test the PATCH media files endpoint of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testUpdateFile(ApiTester $I)
+ {
+ file_put_contents($this->getImagesDirectory($I) . '/override.jpg', '1');
+
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Content-Type', 'application/json');
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendPatch(
+ '/media/files/' . $this->testDirectory . '/override.jpg',
+ [
+ 'path' => $this->testDirectory . '/override.jpg',
+ 'content' => base64_encode(file_get_contents(codecept_data_dir() . '/com_media/test-image-1.jpg'))
+ ]
+ );
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['data' => ['attributes' => ['type' => 'file', 'name' => 'override.jpg']]]);
+ $I->dontSeeResponseContainsJson(['data' => ['attributes' => ['content' => '1']]]);
+ }
+
+ /**
+ * Test the PATCH media files endpoint of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testUpdateFolder(ApiTester $I)
+ {
+ mkdir($this->getImagesDirectory($I) . '/override');
+
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Content-Type', 'application/json');
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendPatch(
+ '/media/files/' . $this->testDirectory . '/override',
+ ['path' => $this->testDirectory . '/override-new']
+ );
+
+ $I->seeResponseCodeIs(HttpCode::OK);
+ $I->seeResponseContainsJson(['data' => ['attributes' => ['type' => 'dir', 'name' => 'override-new']]]);
+ }
+
+ /**
+ * Test the DELETE media files endpoint of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testDeleteFile(ApiTester $I)
+ {
+ touch($this->getImagesDirectory($I) . '/todelete.jpg');
+
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendDelete('/media/files/' . $this->testDirectory . '/todelete.jpg');
+
+ $I->seeResponseCodeIs(HttpCode::NO_CONTENT);
+ }
+
+ /**
+ * Test the DELETE media files endpoint of com_media from the API.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function testDeleteFolder(ApiTester $I)
+ {
+ mkdir($this->getImagesDirectory($I) . '/todelete');
+
+ $I->amBearerAuthenticated($I->getBearerToken());
+ $I->haveHttpHeader('Accept', 'application/vnd.api+json');
+ $I->sendDelete('/media/files/' . $this->testDirectory . '/todelete');
+
+ $I->seeResponseCodeIs(HttpCode::NO_CONTENT);
+ }
+
+ /**
+ * Returns the absolute tmp image folder path to work on.
+ *
+ * @param mixed ApiTester $I Api tester
+ *
+ * @return string The absolute folder path
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function getImagesDirectory(ApiTester $I): string
+ {
+ return $I->getConfig('cmsPath') . '/images/' . $this->testDirectory;
+ }
+}