setka-8.x-1.0/src/Controller/SetkaEditorApiController.php
src/Controller/SetkaEditorApiController.php
<?php
namespace Drupal\setka_editor\Controller;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Asset\CssCollectionOptimizer;
use Drupal\Core\Asset\JsCollectionOptimizer;
use Drupal\Core\Asset\LibraryDiscovery;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\DatabaseBackend;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
use Drupal\Core\File\FileSystem;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\file\Entity\File;
use Drupal\file\FileUsage\FileUsageInterface;
use Drupal\setka_editor\SetkaEditorApi;
use Drupal\setka_editor\SetkaEditorHelper;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* Setka Editor API controller.
*/
class SetkaEditorApiController extends ControllerBase {
const SETKA_ALLOWED_MIME_TYPES = [
'image/jpeg',
'image/pjpeg',
'image/gif',
'image/png',
'image/svg+xml',
'image/vnd.wap.wbmp',
];
/**
* Setka Editor api service.
*
* @var \Drupal\setka_editor\SetkaEditorApi
*/
protected $editorApi;
/**
* Setka Editor config.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $setkaConfig;
/**
* Service to interact with $_SESSION.
*
* @var \Drupal\Core\TempStore\PrivateTempStore
*/
protected $sessionStore;
/**
* Drupal file usage interface.
*
* @var \Drupal\file\FileUsage\FileUsageInterface
*/
protected $fileUsage;
/**
* Drupal database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* Cache Discovery bin backend.
*
* @var \Drupal\Core\Cache\DatabaseBackend
*/
protected $cacheDiscovery;
/**
* Drupal CSS optimizer service.
*
* @var \Drupal\Core\Asset\CssCollectionOptimizer
*/
protected $cssOptimizer;
/**
* Drupal JS optimizer service.
*
* @var \Drupal\Core\Asset\JsCollectionOptimizer
*/
protected $jsOptimizer;
/**
* Drupal file_system service.
*
* @var \Drupal\Core\File\FileSystem
*/
protected $fileSystem;
/**
* Drupal queue factory.
*
* @var \Drupal\Core\Queue\QueueFactory
*/
protected $queueFactory;
/**
* Library discovery service.
*
* @var \Drupal\Core\Asset\LibraryDiscovery
*/
protected $libraryDiscovery;
/**
* Lock service.
*
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* {@inheritdoc}
*/
public function __construct(SetkaEditorApi $editorApi,
ConfigFactory $configFactory,
PrivateTempStoreFactory $privateTempstore,
FileUsageInterface $fileUsage,
Connection $database,
DatabaseBackend $cacheDiscovery,
CssCollectionOptimizer $cssOptimizer,
JsCollectionOptimizer $jsOptimizer,
FileSystem $fileSystem,
QueueFactory $queueFactory,
LibraryDiscovery $libraryDiscovery,
LockBackendInterface $lock) {
$this->editorApi = $editorApi;
$this->configFactory = $configFactory;
$this->setkaConfig = $configFactory->get('setka_editor.settings');
$this->sessionStore = $privateTempstore->get('setka_editor');
$this->fileUsage = $fileUsage;
$this->database = $database;
$this->cacheDiscovery = $cacheDiscovery;
$this->cssOptimizer = $cssOptimizer;
$this->jsOptimizer = $jsOptimizer;
$this->fileSystem = $fileSystem;
$this->queueFactory = $queueFactory;
$this->libraryDiscovery = $libraryDiscovery;
$this->lock = $lock;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('setka_editor.api'),
$container->get('config.factory'),
$container->get('user.private_tempstore'),
$container->get('file.usage'),
$container->get('database'),
$container->get('cache.discovery'),
$container->get('asset.css.collection_optimizer'),
$container->get('asset.js.collection_optimizer'),
$container->get('file_system'),
$container->get('queue'),
$container->get('library.discovery'),
$container->get('lock')
);
}
/**
* API gate to get setka editor files.
*/
public function editorConfig(Request $request) {
$token = $request->request->get('token');
$data = $request->request->get('data');
$licenseKey = $this->setkaConfig->get('setka_license_key');
$setkaUseCdn = $this->setkaConfig->get('setka_use_cdn');
$downloadFiles = (empty($setkaUseCdn) && SetkaEditorHelper::checkSetkaFolderPermissions($this->fileSystem));
if (!empty($data) && !empty($token) && $licenseKey == $token) {
if (!empty($data['plugins']) && !empty($data['content_editor_version'])
&& !empty($data['public_token']) && !empty($data['theme_files']) && !empty($data['content_editor_files'])) {
$newSettings = SetkaEditorHelper::parseStyleManagerData($data);
}
if (!empty($newSettings['setka_editor_js_cdn']) && !empty($newSettings['setka_editor_css_cdn']) &&
!empty($newSettings['setka_company_css_cdn']) && !empty($newSettings['setka_company_json_cdn'] &&
!empty($newSettings['setka_public_js_cdn']))) {
if ($downloadFiles) {
$queue = $this->queueFactory->get('update_setke_editor');
if (!$queue->numberOfItems()) {
$queue->createQueue();
}
$queue->createItem(['newSettings' => $newSettings]);
if ($this->lock->acquire('setka_editor_files_update')) {
while ($newSettingsItem = $queue->claimItem()) {
$newSettingsData = $newSettingsItem->data['newSettings'];
SetkaEditorHelper::buildSetkaFilesUpdateTask($this->setkaConfig, $this->state(), $newSettingsData);
$this->configFactory->getEditable('setka_editor.settings')
->set('setka_editor_version', $newSettingsData['setka_editor_version'])
->set('setka_editor_public_token', $newSettingsData['setka_editor_public_token'])
->set('setka_company_meta_data', $newSettingsData['setka_company_meta_data'])
->set('setka_editor_js_cdn', $newSettingsData['setka_editor_js_cdn'])
->set('setka_editor_css_cdn', $newSettingsData['setka_editor_css_cdn'])
->set('setka_company_css_cdn', $newSettingsData['setka_company_css_cdn'])
->set('setka_company_json_cdn', $newSettingsData['setka_company_json_cdn'])
->set('setka_public_js_cdn', $newSettingsData['setka_public_js_cdn'])
->save();
$this->libraryDiscovery->clearCachedDefinitions();
$this->configFactory->reset('setka_editor.settings');
SetkaEditorHelper::runSetkaFilesUpdateTask($this->state());
$queue->deleteItem($newSettingsItem);
}
foreach (Cache::getBins() as $cache_backend) {
$cache_backend->deleteAll();
}
$this->libraryDiscovery->clearCachedDefinitions();
$this->configFactory->reset('setka_editor.settings');
$this->cacheDiscovery->deleteAll();
$this->cssOptimizer->deleteAll();
$this->jsOptimizer->deleteAll();
_drupal_flush_css_js();
$this->lock->release('setka_editor_files_update');
}
}
else {
$this->configFactory->getEditable('setka_editor.settings')
->set('setka_editor_version', $newSettings['setka_editor_version'])
->set('setka_editor_public_token', $newSettings['setka_editor_public_token'])
->set('setka_company_meta_data', $newSettings['setka_company_meta_data'])
->set('setka_editor_js_cdn', $newSettings['setka_editor_js_cdn'])
->set('setka_editor_css_cdn', $newSettings['setka_editor_css_cdn'])
->set('setka_company_css_cdn', $newSettings['setka_company_css_cdn'])
->set('setka_company_json_cdn', $newSettings['setka_company_json_cdn'])
->set('setka_public_js_cdn', $newSettings['setka_public_js_cdn'])
->save();
$this->state()->setMultiple(
[
'setka_editor_js' => FALSE,
'setka_editor_css' => FALSE,
'setka_company_css' => FALSE,
'setka_company_json' => FALSE,
'setka_public_js' => FALSE,
]
);
$this->getLogger('setka_editor')->info('Setka Editor config update: successful update!');
foreach (Cache::getBins() as $cache_backend) {
$cache_backend
->deleteAll();
}
$this->libraryDiscovery->clearCachedDefinitions();
$this->configFactory->reset('setka_editor.settings');
$this->cacheDiscovery->deleteAll();
$this->cssOptimizer->deleteAll();
$this->jsOptimizer->deleteAll();
_drupal_flush_css_js();
}
}
else {
$this->getLogger('setka_editor')->error('Setka Editor config update error: required request data does not exist!');
}
}
return new JsonResponse();
}
/**
* Gate for Setka Editor API to upload images.
*/
public function uploadImages(Request $request) {
$validUuids = $this->sessionStore->get('setka_editor_valid_uuids');
$entityUuid = $request->request->get('entityUuid');
$entityId = $request->request->get('entityId');
$entityType = $request->request->get('entityType');
if ($this->checkEntityEditAccess($entityId, $entityType)) {
if ($validUuids && $entityUuid && in_array($entityUuid, $validUuids)) {
if (mb_strpos($request->headers->get('Content-Type'), 'multipart/form-data;') !== 0) {
$res = new Response();
$res->setStatusCode(400, $this->t('Unsupported content type.'));
return $res;
}
$uploadError = FALSE;
switch ($_FILES['file']['error']) {
case UPLOAD_ERR_INI_SIZE:
$uploadError = $this->t('File to large. Max file size: @size',
['@size' => ini_get('upload_max_filesize')]
);
break;
case UPLOAD_ERR_FORM_SIZE:
$uploadError = $this->t('File to large. Max file size: @size',
['@size' => ini_get('upload_max_filesize')]
);
break;
case UPLOAD_ERR_PARTIAL:
$uploadError = $this->t('File has uploaded partially.');
break;
case UPLOAD_ERR_NO_FILE:
$uploadError = $this->t('No file uploaded.');
break;
case UPLOAD_ERR_NO_TMP_DIR:
$uploadError = $this->t('Tmp directory does not exists.');
break;
case UPLOAD_ERR_CANT_WRITE:
$uploadError = $this->t('Write error.');
break;
case UPLOAD_ERR_EXTENSION:
$uploadError = $this->t('Extension has stopped upload.');
break;
}
if ($uploadError) {
$res = new Response();
$res->setStatusCode(400, $uploadError);
return $res;
}
$mime = $_FILES['file']['type'];
if (!in_array($mime, self::SETKA_ALLOWED_MIME_TYPES)) {
$res = new Response();
$res->setStatusCode(400, $this->t('Unsupported mime type.'));
return $res;
}
$directory = $this->fileSystem->realpath("public://setka");
$directoryError = FALSE;
if (!SetkaEditorHelper::checkSetkaFolderPermissions($this->fileSystem)) {
if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
$directoryError = TRUE;
$this->getLogger('setka_editor')->error('The directory %directory does not exist or is not writable.', ['%directory' => $directory]);
}
}
if ($directoryError) {
$res = new Response();
$res->setStatusCode(400, $this->t('Image directory permissions error.'));
return $res;
}
else {
$fileData = file_get_contents($_FILES['file']['tmp_name']);
$file = file_save_data($fileData, 'public://setka/' . $_FILES['file']['name'], FILE_EXISTS_RENAME);
$imageId = $file->id();
$imageUrl = file_create_url($file->getFileUri());
if ($entityId && $entityType && $entityId != 'null') {
$this->fileUsage->add($file, 'setka_editor', $entityType, $entityId);
}
else {
$setkaEditorImages = $this->sessionStore->get('setka_editor_images') ?? [];
$setkaEditorImages[$entityUuid][] = $imageId;
$this->sessionStore->set('setka_editor_images', $setkaEditorImages);
}
return new JsonResponse([
'id' => $imageId,
'url' => $imageUrl,
]);
}
}
}
$res = new Response();
$res->setStatusCode(400, $this->t('Unknown error.'));
return $res;
}
/**
* Gate for Setka Editor API to edit image alt.
*/
public function putImage($id, Request $request) {
$requestContent = $request->getContent();
if ($requestPayload = Json::decode($requestContent)) {
$imageId = (int) $id;
if (($requestPayload['entityId'] || $requestPayload['entityUuid']) && !empty($requestPayload['entityType']) && $requestPayload['alt'] && $imageId > 0) {
if ($this->checkEntityEditAccess($requestPayload['entityId'], $requestPayload['entityType'])) {
/** @var \Drupal\file\Entity\File $imageEntity */
$imageEntity = $this->entityTypeManager()->getStorage('file')->load($imageId);
if ($this->checkImageAttachedToEntity($imageEntity, $requestPayload['entityId'], $requestPayload['entityUuid'])) {
$imageUri = $imageEntity->getFileUri();
$imageUrl = file_create_url($imageUri);
$query = $this->database->update('file_managed');
$query->fields(['alt' => $requestPayload['alt']]);
$query->condition('fid', $id);
$query->execute();
return new JsonResponse([
'id' => $imageId,
'url' => $imageUrl,
'thumbUrl' => $imageUrl,
]);
}
}
else {
$res = new Response();
$res->setStatusCode(400, $this->t('Permissions error.'));
return $res;
}
}
}
$res = new Response();
$res->setStatusCode(400, $this->t('Unknown error.'));
return $res;
}
/**
* Gate for Setka Editor API to delete image.
*/
public function delImage($id, Request $request) {
$requestContent = $request->getContent();
if ($requestPayload = Json::decode($requestContent)) {
$imageId = (int) $id;
if (($requestPayload['entityId'] || $requestPayload['entityUuid']) && !empty($requestPayload['entityType']) && $imageId > 0) {
if ($this->checkEntityEditAccess($requestPayload['entityId'], $requestPayload['entityType'])) {
$imageEntity = $this->entityTypeManager()->getStorage('file')->load($imageId);
if ($this->checkImageAttachedToEntity($imageEntity, $requestPayload['entityId'], $requestPayload['entityUuid'])) {
if ($imageEntity) {
$imageEntity->delete();
}
$setkaEditorImages = $this->sessionStore->get('setka_editor_images') ?? [];
if (!empty($setkaEditorImages[$requestPayload['entityUuid']])) {
$imageKey = array_search($imageId, $setkaEditorImages[$requestPayload['entityUuid']]);
unset($setkaEditorImages[$requestPayload['entityUuid']][$imageKey]);
$this->sessionStore->set('setka_editor_images', $setkaEditorImages);
}
}
return new JsonResponse();
}
}
}
$res = new Response();
$res->setStatusCode(400, $this->t('Unknown error.'));
return $res;
}
/**
* Checks if user has access to edit/create entity.
*
* @param int|null $entityId
* Entity id.
* @param string $entityType
* Entity type.
*
* @return bool|\Drupal\Core\Access\AccessResultInterface
* Check access result.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function checkEntityEditAccess($entityId, $entityType) {
$account = $this->currentUser();
if ($entityId > 0) {
$entity = $this->entityTypeManager()->getStorage($entityType)->load($entityId);
return $entity->access('edit', $account);
}
else {
return $account->hasPermission('create ' . $entityType . ' content');
}
}
/**
* Checks if image attached to entity or not.
*
* @param \Drupal\file\Entity\File $imageFile
* Image file.
* @param int $entityId
* Entity id.
* @param string $entityUuid
* Entity uuid.
*
* @return bool
* Image attach status.
*/
protected function checkImageAttachedToEntity(File $imageFile, $entityId, $entityUuid) {
if (!$imageFile) {
return FALSE;
}
if ($entityId) {
$listUsage = $this->fileUsage->listUsage($imageFile);
if (!empty($listUsage['setka_editor'])) {
foreach ($listUsage['setka_editor'] as $entityTypeUsage) {
if ($entityTypeUsage[$entityId]) {
return TRUE;
}
}
}
return FALSE;
}
elseif ($entityUuid) {
$setkaEditorImages = $this->sessionStore->get('setka_editor_images') ?? [];
if (!empty($setkaEditorImages[$entityUuid])) {
return in_array($imageFile->id(), $setkaEditorImages[$entityUuid]);
}
}
return FALSE;
}
}
