monster_menus-9.0.x-dev/modules/mm_media/src/Plugin/MMTreeBrowserDisplay/Media.php
modules/mm_media/src/Plugin/MMTreeBrowserDisplay/Media.php
<?php
namespace Drupal\mm_media\Plugin\MMTreeBrowserDisplay;
use Drupal\Core\Database\Query\PagerSelectExtender;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Database\Database;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\mm_media\Plugin\EntityBrowser\Widget\MMTree;
use Drupal\monster_menus\Constants;
use Drupal\monster_menus\MMTreeBrowserDisplay\MMTreeBrowserDisplayInterface;
use Drupal\monster_menus\Plugin\MMTreeBrowserDisplay\Fallback;
use Drupal\paragraphs\Entity\Paragraph;
/**
* Provides the MM Tree display generator for pages containing Media entities.
*
* @MMTreeBrowserDisplay(
* id = "mm_tree_browser_display_media",
* admin_label = @Translation("MM Tree media display"),
* )
*/
class Media extends Fallback implements MMTreeBrowserDisplayInterface {
final public const BROWSER_MODE_MEDIA = 'med';
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* Entity field manager service.
*
* @var \Drupal\Core\Entity\EntityFieldManager
*/
protected $entityFieldManager;
/**
* Entity field manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManager
*/
protected $entityTypeManager;
/**
* The cache.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* The HTTP query.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $httpQuery;
/**
* If set, allow the implicit creation of Media entities from File entities.
*
* @var bool
*/
protected $allow_file2media = FALSE;
/**
* Constructor.
*/
public function __construct() {
$this->entityFieldManager = \Drupal::service('entity_field.manager');
$this->entityTypeManager = \Drupal::service('entity_type.manager');
$this->cache = \Drupal::service('cache.default');
$this->httpQuery = \Drupal::request()->query;
$this->database = Database::getConnection();
}
public static function supportedModes() {
return [self::BROWSER_MODE_MEDIA];
}
public function label($mode) {
return t('Select a file upload');
}
/**
* {@inheritdoc}
*/
public function alterLeftQuery($mode, $query, &$params) {
$segments = [];
foreach ($this->getFields() as $alias => $fields_list) {
foreach ($fields_list as $fields) {
$join = [];
$last_alias = '';
foreach ($fields as $field) {
$join[] = '{' . $field[0] . '} ' . $field[1] . ' ON ' . $field[2];
$last_alias = $field[1];
}
$joined = join(' INNER JOIN ', $join);
if (str_starts_with($alias, 'file_managed')) {
// Negate fids in order to count them separately from mids.
$concat_alias = "-$last_alias.fid";
}
else {
$concat_alias = "$alias.mid";
}
$segments[] = "SELECT GROUP_CONCAT($concat_alias) FROM {node_field_data} n INNER JOIN $joined INNER JOIN {mm_node2tree} n2 ON n2.nid = n.nid WHERE n.status = 1 AND n2.mmtid = o.container";
}
}
// Ideally, we would use a UNION to get all the distinct mids, but can't
// because then MySQL doesn't let us use o.container from the outer query.
// Instead, get a concatenated list of all the mids in nodecount and
// squash the duplicates in PHP to get the count.
// Start by making sure we can get all the results.
$this->database->query('SET @@group_concat_max_len = 1000000')->execute();
$params[Constants::MM_GET_TREE_ADD_SELECT] = "CONCAT_WS(',', (" . join('), (', $segments) . ')) AS fid_list';
$params[Constants::MM_GET_TREE_FILTER_NORMAL] = $params[Constants::MM_GET_TREE_FILTER_USERS] = TRUE;
}
/**
* @return array
*/
private function getFields() {
$file_types = $this->getFileTypes();
$cid = 'mm_media.get_fields:' . implode(':', $file_types) . ':' . $this->allow_file2media;
if ($cache = $this->cache->get($cid)) {
return $cache->data;
}
$output = [];
$paragraph_data = [];
$cache_tags = ['mm_media_get_fields'];
foreach ($this->entityFieldManager->getFieldStorageDefinitions('paragraph') as $field_name => $field_def) {
$type = '.entity.bundle';
if ($field_def->getType() == 'entity_reference' && $field_def->getSetting('target_type') == 'media' ||
$this->allow_file2media && in_array($field_def->getType(), $file_types) && !$field_def->hasCustomStorage() && ($type = '.entity')) {
foreach ($this->entityFieldManager->getFieldMap()['paragraph'][$field_name]['bundles'] ?? [] as $paragraph) {
$ids = $this->entityTypeManager->getStorage('paragraph')->getQuery()
->accessCheck(FALSE)
->condition('type', $paragraph)
->execute();
if ($loaded = Paragraph::load(reset($ids))) {
if ($loaded->get('parent_type')->value == 'node') {
$key = $loaded->get('parent_field_name')->value;
$cache_tags = Cache::mergeTags($field_def->getCacheTags(), $cache_tags);
$paragraph_data[$key][] = $field_name . $type;
}
}
}
}
}
foreach ($this->entityFieldManager->getFieldStorageDefinitions('node') as $field_name => $field_def) {
// Perform a dummy entity query, in order to have it map the table
// relationships.
if ($field_def->getType() == 'entity_reference' && $field_def->getSetting('target_type') == 'media') {
$this->getTablesForField("$field_name.entity.bundle", $field_def, $cache_tags, $output);
}
elseif ($this->allow_file2media && in_array($field_def->getType(), $file_types) && !$field_def->hasCustomStorage()) {
$this->getTablesForField("$field_name.entity", $field_def, $cache_tags, $output);
}
elseif (isset($paragraph_data[$field_name])) {
foreach ($paragraph_data[$field_name] as $paragraph_field) {
$this->getTablesForField("$field_name.entity:paragraph." . $paragraph_field, $field_def, $cache_tags, $output);
}
}
}
$this->cache->set($cid, $output, Cache::PERMANENT, $cache_tags);
return $output;
}
private function getTablesForField($field, FieldStorageDefinitionInterface $field_def, &$cache_tags, &$output) {
$query = \Drupal::entityQuery('node')
->accessCheck(FALSE);
$query->condition($field, 1);
$cache_tags = Cache::mergeTags($field_def->getCacheTags(), $cache_tags);
// Sadly, entityQuery doesn't have public methods for most things, so hack
// it.
// Start with $query->prepare().
$prepare = new \ReflectionMethod($query::class, 'prepare');
$prepare->setAccessible(TRUE);
$prepare->invoke($query);
// $query->compile().
$compile = new \ReflectionMethod($query::class, 'compile');
$compile->setAccessible(TRUE);
$compile->invoke($query);
// Get $query->sqlQuery.
$rp = new \ReflectionProperty($query::class, 'sqlQuery');
$rp->setAccessible(TRUE);
$sqlQuery = $rp->getValue($query);
$join = [];
$last_alias = '';
foreach ($sqlQuery->getTables() as $table_name => $table_def) {
if ($table_name != 'base_table' && $table_name != 'media_field_data') {
if ($join && preg_match('{ \[?base_table\]?\.}', $table_def['condition'])) {
$output[$last_alias][] = $join;
$join = [];
}
$last_alias = $table_def['alias'];
$join[] = [
$table_def['table'],
$table_def['alias'],
preg_replace('{ \[?base_table\]?\.}', ' n.', $table_def['condition']),
];
}
}
$output[$last_alias][] = $join;
}
/**
* Sanitize and expand the browserFileTypes query parameter.
*
* @return array
*/
private function getFileTypes() {
if ($types = $this->httpQuery->get('browserFileTypes')) {
$types = array_map(fn($mime) => preg_replace('/[^A-Za-z0-9_.]+/', '', $mime), explode(',', $types));
if (($i = array_search(MMTree::FILE2MEDIA_TYPE, $types)) !== FALSE) {
unset($types[$i]);
$this->allow_file2media = TRUE;
$types[] = 'file';
}
return $types;
}
return [];
}
public function showReservedEntries($mode) {
return FALSE;
}
public function alterRightButtons($mode, $query, $item, $permissions, &$actions, &$dialogs) {
$this->addSearchAction($query, $actions, t('Filter by filename/ALT text'));
parent::alterRightButtons($mode, $query, $item, $permissions, $actions, $dialogs);
}
/**
* Get a list of file upload thumbnails for the right hand column.
*
* {@inheritdoc}
*/
public function viewRight($mode, $query, $perms, $item, $database) {
$mmtid = $item->mmtid;
if (!$perms[Constants::MM_PERMS_APPLY]) {
$out = '';
if ($mmtid > 0) {
$out = '<div id="mmtree-browse-thumbnails"><br />' . t('You do not have permission to use the file uploads on this page.') . '</div>';
}
$json = [
'title' => mm_content_get_name($mmtid),
'body' => $out,
];
return mm_json_response($json);
}
foreach ($this->getFields() as $fields_list) {
foreach ($fields_list as $fields) {
$segment = $this->database->select('node_field_data', 'n');
foreach ($fields as $i => $field) {
if (!$i) {
$segment->addTag(__FUNCTION__ . '__' . $field[0] . '.' . $field[1]);
}
$segment->join($field[0], $field[1], $field[2]);
if ($field[0] == 'media') {
$segment->addExpression($field[1] . '.mid', 'mid');
$segment->addExpression(-1, 'fid');
}
elseif ($field[0] == 'file_managed') {
$segment->addExpression(-1, 'mid');
$segment->addExpression($field[1] . '.fid', 'fid');
}
}
$segment->addExpression('n.changed', 'changed');
$segment->join('mm_node2tree', 'n2', 'n2.nid = n.nid');
$segment->where('n.status = 1')
->where('n2.mmtid = ' . intval($mmtid));
if (empty($qquery)) {
$qquery = $segment;
}
else {
$qquery->union($segment);
}
}
}
$content = [];
if (!empty($qquery)) {
$qquery = $this->database->select($qquery, 'subquery');
$qquery->addTag(__FUNCTION__);
if ($mode == self::BROWSER_MODE_MEDIA) {
$qquery->addField('subquery', 'mid');
}
$qquery->leftJoin('file_managed', 'fm', 'fm.fid = subquery.fid');
$qquery->fields('fm');
if ($search_terms = $this->getSearchTerms($query)) {
$qquery->leftJoin('media_field_data', 'fd', 'fd.mid = subquery.mid');
foreach ($search_terms as $term) {
// $term is already sanitized in ::getSearchTerms().
$qquery->where("(fd.name REGEXP '" . $term . "' OR fd.thumbnail__alt REGEXP '" . $term . "' OR fm.filename REGEXP '" . $term . "')");
}
}
$result = $qquery->extend(PagerSelectExtender::class)
->orderBy('subquery.changed', 'DESC')
->limit(mm_get_setting('nodes.nodelist_pager_limit'))
->execute();
$min_wh[0] = $this->httpQuery->getInt('browserMinW', 0);
$min_wh[1] = $this->httpQuery->getInt('browserMinH', 0);
foreach ($result as $file) {
$content['icons'][] = [
'#theme' => 'mm_browser_thumbnail',
'#file' => $file,
'#style_name' => 'thumbnail',
'#mode' => $mode,
'#mmtid' => $mmtid,
'#min_wh' => $min_wh,
];
}
}
if (!$content) {
$msg = !empty($search_terms) ? t('There is no selectable content on this page which matches the filter.') : t('There is no selectable content on this page.');
$content = [['#prefix' => '<p>', '#markup' => $msg, '#suffix' => '</p>']];
$this->removeSearchAction();
}
else {
$content['pager'] = [
'#type' => 'pager',
'#route_name' => 'monster_menus.browser_getright',
];
}
return ['#prefix' => '<div id="mmtree-browse-thumbnails">', $content, '#suffix' => '</div>'];
}
}
