We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PR w związku ze zmianą oryginału joomla/joomla-cms#35788 Poniżej zmiany w oryginale:
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; + } +}
The text was updated successfully, but these errors were encountered:
Zmiany do 4.1
9d70b4e
fix #60 fix #66 fix #67 fix #68 fix #69
wojsmol
zwiastunsw
No branches or pull requests
PR w związku ze zmianą oryginału joomla/joomla-cms#35788 Poniżej zmiany w oryginale:
Click to expand the diff!
The text was updated successfully, but these errors were encountered: