monster_menus-9.0.x-dev/src/Controller/DefaultController.php
src/Controller/DefaultController.php
<?php /**
* @file
* Contains \Drupal\monster_menus\Controller\DefaultController.
*/
namespace Drupal\monster_menus\Controller;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Database\Query\TableSortExtender;
use Drupal\Core\Database\Query\PagerSelectExtender;
use Drupal\Component\Utility\Html;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Render\Element\Form;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\monster_menus\Constants;
use Drupal\monster_menus\Entity\MMTree;
use Drupal\monster_menus\Form\CopyMoveContentForm;
use Drupal\monster_menus\Form\DeleteContentConfirmForm;
use Drupal\monster_menus\Form\DeleteNodeConfirmForm;
use Drupal\monster_menus\Form\EditContentForm;
use Drupal\monster_menus\PermissionsSolver;
use Drupal\monster_menus\Form\RestoreContentConfirmForm;
use Drupal\monster_menus\Form\SearchReplaceForm;
use Drupal\monster_menus\GetTreeIterator\SitemapDumpIter;
use Drupal\monster_menus\PathProcessor\InboundPathProcessor;
use Drupal\monster_menus\Plugin\MMTreeBrowserDisplay\Fallback;
use Drupal\monster_menus\Plugin\MMTreeBrowserDisplay\Groups;
use Drupal\monster_menus\Plugin\MMTreeBrowserDisplay\Users;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\node\NodeInterface;
use Drupal\user\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Default controller for the monster_menus module.
*/
class DefaultController extends ControllerBase {
/**
* The entity type manager.
*
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The database connection.
*
* @var Connection
*/
protected $database;
/**
* The service container.
*
* @var ContainerInterface
*/
protected $container;
/**
* Constructs a DefaultController object.
*
* @param ContainerInterface $container
* The service container.
* @param Connection $database
* The database connection.
* @param EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(ContainerInterface $container, Connection $database, EntityTypeManagerInterface $entity_type_manager) {
$this->container = $container;
$this->database = $database;
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container,
$container->get('database'),
$container->get('entity_type.manager')
);
}
public static function menuAccessCreateHomepage(AccountInterface $account) {
$user = \Drupal::currentUser();
return AccessResult::allowedIf(mm_get_setting('user_homepages.enable') &&
$account->isAuthenticated() &&
($user->hasPermission('administer all users') || $account->id() == $user->id()));
}
public static function getNodeTypeLabel($type) {
static $cache;
if (!isset($cache[$type])) {
$loaded = NodeType::load($type);
$cache[$type] = $loaded ? $loaded->label() : '';
}
return $cache[$type];
}
/**
* Create a user's home directory in the MM tree.
*
* @param User $user
* The user object describing the account being added
* @return RedirectResponse
*/
public function createHomepage(User $user) {
return mm_content_create_homepage($user);
}
public static function menuAccessListUserHomepages() {
return AccessResult::allowedIf(mm_get_setting('user_homepages.virtual'));
}
/**
* List user homepages starting with a certain letter.
*
* @param int $mmtid
* The pseudo-MMTID (integer < 0) of the page to show
* @param Request $request
* Request object
* @return array
* Render array for the page
*/
public function listUserHomepages($mmtid, Request $request) {
/** @var MMTree $mm_tree */
$mm_tree = MMTree::create(['mmtid' => $mmtid]);
$view = MMTreeViewController::create($this->container);
return $view->view($mm_tree, $request);
}
/**
* Retrieve a string of autocomplete suggestions for existing users
*/
public function autocomplete(Request $request, $want_username, $misc) {
$string = $request->query->get('q');
$limit = 15;
$min_string = 2;
$matches = [];
$too_short = [['value' => '', 'label' => $this->t('Please type some more characters')]];
$string = trim($string);
if (!empty($string)) {
$result = NULL;
$hook = mm_module_implements('mm_autocomplete_alter');
if ($hook) {
// Only the first hook gets called.
$function = reset($hook);
$result = $function($string, $limit, $min_string, $misc);
if (empty($result)) {
$matches = $too_short;
}
}
elseif (mb_strlen($string) >= $min_string) {
// Consider Anonymous and Administrator first
$startswith = $contains = '';
for ($i = 0; $i <= 1; $i++) {
$name = mm_content_uid2name($i);
if (($pos = stristr($name, $string)) !== FALSE) {
$stmt = "(SELECT $i AS uid, '' AS name, '' AS pref_fml, '' AS pref_lfm, '$name' AS lastname, '' AS firstname, '' AS middlename) UNION ";
if (!$pos) {
$startswith .= $stmt;
}
else {
$contains .= $stmt;
}
}
}
$status_limit = $this->currentUser()->hasPermission('administer all users') ? '' : ' status = 1 AND';
$result = $this->database->query('SELECT * FROM (' . $startswith . $contains . "(SELECT uid, name, '' AS pref_fml, '' AS pref_lfm, '' AS lastname, '' AS firstname, '' AS middlename " . "FROM {users_field_data} WHERE$status_limit uid > 1 AND name = :name_exact " . 'ORDER BY name) UNION ' . "(SELECT uid, name, '', '', '', '', '' " . "FROM {users_field_data} WHERE$status_limit uid > 1 AND name LIKE :name_start " . 'ORDER BY name) UNION ' . "(SELECT uid, name, '', '', '', '', '' " . "FROM {users_field_data} WHERE$status_limit uid > 1 AND name LIKE :name_any " . 'ORDER BY name)) x ' . 'LIMIT ' . intval($limit + 1), [
':name_exact' => $string,
':name_start' => $string . '%',
':name_any' => '%_' . $string . '%',
]);
}
else {
$matches = $too_short;
}
if (!empty($result)) {
foreach ($result as $usr) {
if (count($matches) == $limit) {
$matches[] = ['value' => '', 'label' => '...'];
break;
}
else {
$name = Html::escape(mm_content_uid2name($usr->uid, 'lfmu'));
if (!$want_username) {
$matches[] = ['value' => $usr->uid . '-' . $name, 'label' => $name];
}
elseif ($usr->name) {
$matches[] = ['value' => $usr->name, 'label' => $name];
}
}
}
}
}
return new JsonResponse($matches);
}
public function menuAccessSitemap() {
return AccessResult::allowedIf(mm_get_setting('sitemap.max_level') >= 0);
}
public function saveSitemap() {
$max_level = mm_get_setting('sitemap.max_level');
if ($max_level >= 0) {
$iter = new SitemapDumpIter($max_level);
// Use the anonymous user, so permissions tests are valid.
$params = [
Constants::MM_GET_TREE_FILTER_NORMAL => TRUE,
Constants::MM_GET_TREE_FILTER_USERS => TRUE,
Constants::MM_GET_TREE_ITERATOR => $iter,
Constants::MM_GET_TREE_RETURN_BLOCK => TRUE,
Constants::MM_GET_TREE_RETURN_PERMS => TRUE,
Constants::MM_GET_TREE_USER => User::getAnonymousUser(),
];
mm_content_get_tree(1, $params);
$iter->finish();
}
}
public function showSitemap() {
return new BinaryFileResponse('public://sitemap.xml', 200, ['Content-Type' => 'text/xml']);
}
static public function menuAccessShowGroup(MMTree $mm_tree, AccountInterface $account) {
$perms = mm_content_user_can($mm_tree->id(), NULL, $account);
return AccessResult::allowedIf($account->isAuthenticated() && $perms[Constants::MM_PERMS_READ] && $perms[Constants::MM_PERMS_IS_GROUP]);
}
public function showGroup(MMTree $mm_tree) {
$headers = _mm_ui_userlist_get_headers();
array_pop($headers);
foreach ($headers as $key => $value) {
$headers[$key] = [
'data' => $value,
'attributes' => empty($value) ? ['class' => ['no-sort']] : [],
];
}
// Adding ->toString(TRUE) adds cacheability metadata, to avoid an error in
// EarlyRenderingControllerWrapperSubscriber.
$link_location = Url::fromRoute('monster_menus.large_group_get_users_json', ['mm_tree' => $mm_tree->id(), 'element' => 'mm-user-datatable-members-display'])
->toString(TRUE)
->getGeneratedUrl();
$body = [[
'#type' => 'table',
'#attributes' => [
'class' => ['tablesorter'],
'data-link-location' => $link_location,
'data-col-def' => Json::encode(array_fill(0, count($headers), NULL)),
],
'#id' => 'mm-user-datatable-members-display',
'#header' => $headers,
[
'#attributes' => ['class' => ['dataTables_empty']],
['#wrapper_attributes' => ['colspan' => count($headers)], '#markup' => $this->t('Loading data from server')],
],
'#cache' => ['max-age' => -1],
]];
mm_add_library($body, 'show_group');
mm_add_library($body, 'dataTables');
return mm_page_wrapper($this->t('Group Members'), $body, ['class' => ['mm-dialog']]);
}
static public function menuAccessUserCan(MMTree $mm_tree, $mode = '', AccountInterface $account = NULL) {
$result = AccessResult::allowedIf(mm_content_user_can($mm_tree->id(), $mode, $account))->cachePerUser();
$result->addCacheableDependency($mm_tree);
return $result;
}
public function renderNodesOnPage(MMTree $mm_tree, $per_page) {
$item = mm_content_get($mm_tree->id(), Constants::MM_GET_ARCHIVE);
$perms = mm_content_user_can($mm_tree->id());
$no_read = $ok = 0;
$output = [];
// set $_GET['page'] to control the page number
if (_mm_render_nodes_on_page($item, $perms, (int) $per_page, [], FALSE, $output, $ok, $no_read, $pager_elem, $archive_tree, $archive_date_int, $rss_link)) {
return $output;
}
return [];
}
static public function menuAccessAdd(AccountInterface $account, MMTree $mm_tree = NULL) {
if ($mm_tree) {
$perms = mm_content_user_can($mm_tree->id(), '', $account);
if ($perms[Constants::MM_PERMS_APPLY] && !$perms[Constants::MM_PERMS_IS_GROUP] && !$perms[Constants::MM_PERMS_IS_RECYCLED] && !mm_content_is_archive($mm_tree->id())) {
// Make sure the user can create at least one type of content here
$allowed_node_types = mm_content_resolve_cascaded_setting('allowed_node_types', $mm_tree->id(), $types_at, $types_parent);
$types = NodeType::loadMultiple();
if (!$types) {
throw new \InvalidArgumentException('No content types have been defined.');
}
if (!$account->hasPermission('administer all menus')) {
$types = array_intersect_key($types, array_flip($allowed_node_types));
}
/** @var NodeType $type */
foreach ($types as $type) {
if (mm_node_access_create($type->id(), $account)) {
return AccessResult::allowed();
}
}
}
}
return AccessResult::forbidden();
}
public function addNode(MMTree $mm_tree) {
return $this->nodeAdd($mm_tree->id(), '');
}
public function addNodeWithType(MMTree $mm_tree, $node_type) {
return $this->nodeAdd($mm_tree->id(), $node_type);
}
public function addNodeWithTypeGetTitle($node_type) {
return $this->t('Add %type Content', ['%type' => static::getNodeTypeLabel($node_type)]);
}
/**
* Present a node submission form or a set of links to such forms.
*
* @param int $mmtid
* MM Tree ID to add the node to
* @param string $type
* Type of node to create(optional)
* @return string|array
* The HTML code for the results
*/
private function nodeAdd($mmtid, $type = '') {
$user = $this->currentUser();
if (!mm_content_user_can($mmtid, Constants::MM_PERMS_APPLY)) {
return t('You are not allowed to assign the page %cat to content.', ['%cat' => mm_content_get_name($mmtid)]);
}
$allowed_node_types = mm_content_resolve_cascaded_setting('allowed_node_types', $mmtid, $types_at, $types_parent);
// If a node type has been specified, validate its existence.
/** @var NodeType[] $types */
$types = NodeType::loadMultiple();
$type = isset($type) ? str_replace('-', '_', $type) : NULL;
if (isset($types[$type]) && mm_node_access_create($type) && ($this->currentUser()->hasPermission('administer all menus') || in_array($type, $allowed_node_types))) {
$node = Node::create(['type' => $type])
->setOwnerId($user->id());
return $this->entityFormBuilder()->getForm($node);
}
else {
// If no (valid) node type has been provided, display a node type overview.
$hidden_types = mm_get_node_info(Constants::MM_NODE_INFO_ADD_HIDDEN);
$admin_only_item = $item = [];
foreach ($types as $type) {
$type_url_str = $type->id();
$direct_link = Url::fromRoute('monster_menus.add_node_with_type', ['mm_tree' => $mmtid, 'node_type' => $type_url_str], ['absolute' => TRUE]);
if (mm_node_access_create($type_url_str) && !in_array($type_url_str, $hidden_types) && in_array($type_url_str, $allowed_node_types)) {
$item[$type->label()] = $type;
$sole_direct_link = $direct_link;
}
elseif ($this->currentUser()->hasPermission('administer all menus') && !in_array($type_url_str, $allowed_node_types)) {
$admin_only_item[$type->label()] = $type;
}
}
if ($item || $admin_only_item) {
if (count($item) == 1 && !$this->currentUser()->hasPermission('administer all menus')) {
return new RedirectResponse($sole_direct_link->toString());
}
uksort($item, 'strnatcasecmp');
uksort($admin_only_item, 'strnatcasecmp');
$output = '';
if ($mmtid && mm_get_setting('pages.hide_empty_pages_in_menu')) {
$or = $this->database->condition('OR');
$select = $this->database->select('mm_node2tree', 't');
$select->join('node', 'n', 't.nid = n.nid');
$select->condition('t.mmtid', $mmtid)
->condition($or
->condition('n.status', 1)
->condition('n.uid', $user->id())
);
$count = $select->countQuery()->execute()->fetchField();
if (!$count) {
$this->messenger()->addWarning(t('Until you have added some content to this page, it will not appear in the menus for anyone who does not also have the ability to add content.'));
}
}
$admin_ok = $this->currentUser()->hasPermission('administer all menus') && $admin_only_item;
$output = $admin_ok && !$item ? [] : [
['#markup' => $output . t('Choose the type of content to create using this page:')],
[
'#theme' => 'node_add_list',
'#content' => $item,
],
];
if ($admin_ok) {
$output[] = [['#markup' => '<br /><div class="mm-admin-types">' . t('The following content type(s) will only be displayed to admin users:')], ['#theme' => 'node_add_list', '#content' => $admin_only_item], ['#markup' => '</div>']];
}
}
else {
$output = [['#markup' => t('You are not allowed to create content here.')]];
}
$output['#title'] = t('Add content');
return $output;
}
}
static public function menuAccessPageSettings(MMTree $mm_tree, AccountInterface $user) {
$perms = mm_content_user_can($mm_tree->id(), '', $user);
return AccessResult::allowedIf($perms[Constants::MM_PERMS_WRITE] || ($perms[Constants::MM_PERMS_SUB] || $perms[Constants::MM_PERMS_APPLY] && (!isset($user->user_portal_mmtid) || $mm_tree->id() != $user->user_portal_mmtid) && (!$perms[Constants::MM_PERMS_IS_GROUP] || $perms[Constants::MM_PERMS_READ]) && !$perms[Constants::MM_PERMS_IS_RECYCLED]))->cachePerUser();
}
/**
* Perform an operation on a tree entry.
*
* @param MMTree $mm_tree
* MMTree of entry to modify.
* @param string $op
* Operation to perform
* @throws NotFoundHttpException|AccessDeniedHttpException
* NotFoundHttpException if the entry does not exist or if the entry is a
* bin that cannot be emptied by the current user; AccessDeniedHttpException
* if the current user does not have permission to perform the requested
* action.
* @return string|array
* The HTML code for the results.
*/
public function handlePageSettings(MMTree $mm_tree, $op = '') {
$user = $this->currentUser();
$perms = mm_content_user_can($mm_tree->id());
if (empty($op)) {
if ($perms[Constants::MM_PERMS_IS_RECYCLE_BIN]) {
$op = $this->currentUser()->hasPermission('delete permanently') ? 'empty' : 'search';
}
elseif ($perms[Constants::MM_PERMS_WRITE]) {
$op = 'edit';
}
else {
$this->messenger()->addStatus($this->t('You do not have permission to change the settings. Please choose another option from the menu.'));
return [];
}
if ($op != 'edit') {
return mm_goto(Url::fromRoute("monster_menus.page_settings_$op", ['mm_tree' => $mm_tree->id()]));
}
}
$params = [
Constants::MM_GET_TREE_DEPTH => $op == 'delete' || $op == 'empty' ? -1 : 0,
Constants::MM_GET_TREE_RETURN_FLAGS => TRUE,
Constants::MM_GET_TREE_RETURN_PERMS => TRUE,
Constants::MM_GET_TREE_RETURN_MTIME => TRUE,
];
$tree = mm_content_get_tree($mm_tree->id(), $params);
if (!$tree) {
throw new NotFoundHttpException();
}
if (!$tree[0]->perms[Constants::MM_PERMS_WRITE] && !$tree[0]->perms[Constants::MM_PERMS_SUB] && !$tree[0]->perms[Constants::MM_PERMS_APPLY]) {
throw new AccessDeniedHttpException();
}
$x = mm_ui_strings($is_group = $tree[0]->is_group);
$x['%name'] = mm_content_get_name($tree[0]);
switch ($op) {
case 'edit':
_mm_ui_is_user_home($tree[0]);
$form = \Drupal::formBuilder()->getForm(EditContentForm::class, $tree[0], $mm_tree->id(), $is_group);
$form['#title'] = $this->t('Settings for %name', $x);
return $form;
case 'copymove':
_mm_ui_is_user_home($tree[0]);
$form = \Drupal::formBuilder()->getForm(CopyMoveContentForm::class, $tree[0]);
$form['#title'] = $this->t('Copy/move %name', $x);
return $form;
case 'restore':
$next_par = FALSE;
foreach (array_reverse(mm_content_get_parents($tree[0]->mmtid)) as $t) {
if ($t < 0) {
// virtual user dir
continue;
}
elseif ($next_par) {
$par = mm_content_get($t);
$pperms = mm_content_user_can($t);
break;
}
elseif (mm_content_user_can($t, Constants::MM_PERMS_IS_RECYCLE_BIN)) {
$next_par = TRUE;
}
}
if (!empty($par) && !$pperms[Constants::MM_PERMS_SUB]) {
$x['@name'] = mm_content_get_name($par);
$x[':link'] = mm_content_get_mmtid_url($par->mmtid);
return $this->t('You cannot restore this @thing because you do not have permission to add to the parent @thing, <a href=":link">@name</a>. You may be able to copy or move this @thing to another location, however.', $x);
}
elseif (!$pperms[Constants::MM_PERMS_SUB]) {
return $this->t('You cannot restore this @thing because you do not have permission to add to the parent @thing. You may be able to copy or move this @thing to another location, however.', $x);
}
$x['%name'] = mm_content_get_name($par);
return \Drupal::formBuilder()->getForm(RestoreContentConfirmForm::class, $tree[0], $par->mmtid, $x);
case 'empty':
if (!mm_content_user_can_recycle($mm_tree->id(), Constants::MM_PERMS_IS_EMPTYABLE)) {
throw new NotFoundHttpException();
}
// intentionally fall through to 'delete'
case 'delete':
return $this->contentDelete($tree, $x, $is_group);
case 'sub':
$sub = clone $tree[0];
$sub->name = $is_group ? $this->t('(new group)') : ($mm_tree->id() != 1 ? $this->t('(new page)') : $this->t('(new site)'));
$sub->alias = '';
$sub->uid = $user->id();
$sub->theme = '';
$sub->hidden = FALSE;
$sub->flags = [];
foreach (\Drupal::moduleHandler()->invokeAll('mm_tree_flags') as $flag => $elem) {
if (isset($elem['#flag_inherit']) && $elem['#flag_inherit'] === TRUE && isset($tree[0]->flags[$flag])) {
$sub->flags[$flag] = $tree[0]->flags[$flag];
}
}
$form = \Drupal::formBuilder()->getForm(EditContentForm::class, $sub, $mm_tree->id(), $is_group, TRUE);
$form['#title'] = $mm_tree->id() != 1 ? $this->t('Create a new @subthing of %name', $x) : $this->t('Create a new site', $x);
return $form;
case 'search':
return SearchReplaceForm::getForm($mm_tree->id());
default:
throw new NotFoundHttpException();
}
}
private function contentDelete($tree, $x, $is_group) {
if (isset($tree[0]->flags['limit_delete']) && !$this->currentUser()->hasPermission('administer all menus')) {
throw new AccessDeniedHttpException();
}
if ($tree[0]->mmtid == mm_home_mmtid()) {
return ['#markup' => $this->t('The @thing %name cannot be deleted.', $x)];
}
$del_perm = !mm_content_recycle_enabled() ||
$tree[0]->perms[Constants::MM_PERMS_IS_RECYCLED] || $tree[0]->perms[Constants::MM_PERMS_IS_GROUP];
$mmtids = [];
$kids = 0;
$x['%sub'] = '';
foreach ($tree as $t) {
if (!$t->perms[Constants::MM_PERMS_IS_RECYCLE_BIN]) {
if ($t != $tree[0]) $kids++;
if ($t->level == 1 && $x['%sub'] == '') $x['%sub'] = mm_content_get_name($t);
}
if ($del_perm && !$t->perms[Constants::MM_PERMS_WRITE] && !$t->perms[Constants::MM_PERMS_IS_RECYCLE_BIN] || isset($t->flags['limit_delete']) && !$this->currentUser()->hasPermission('administer all menus')) {
$x['%name'] = mm_content_get_name($t);
return ['#markup' => $this->t('You cannot delete this @thing because you do not have permission to delete the @subthing %name', $x)];
}
$mmtids[] = $t->mmtid;
}
$x['@num'] = $kids;
$nodes = $excl_nodes = [];
if (!$is_group) {
$nodes = mm_content_get_nids_by_mmtid($mmtids);
$excl_nodes = mm_content_get_nids_by_mmtid($mmtids, 0, TRUE);
}
return \Drupal::formBuilder()->getForm(DeleteContentConfirmForm::class, $tree, $x, $is_group, $mmtids, $del_perm, $kids, $nodes, $excl_nodes);
}
static public function menuAccessEmptyBin(MMTree $mm_tree, AccountInterface $account) {
$perms = mm_content_user_can($mm_tree->id(), NULL, $account);
return AccessResult::allowedIf($perms[Constants::MM_PERMS_IS_RECYCLE_BIN] && mm_content_user_can_recycle($mm_tree->id(), Constants::MM_PERMS_IS_EMPTYABLE));
}
static public function menuAccessEdit(MMTree $mm_tree, AccountInterface $account) {
$perms = mm_content_user_can($mm_tree->id(), NULL, $account);
return AccessResult::allowedIf(!$perms[Constants::MM_PERMS_IS_RECYCLE_BIN] && $perms[Constants::MM_PERMS_WRITE]);
}
static public function menuAccessCopy(MMTree $mm_tree, AccountInterface $account) {
$perms = mm_content_user_can($mm_tree->id(), NULL, $account);
return AccessResult::allowedIf(!$perms[Constants::MM_PERMS_IS_RECYCLE_BIN] && ($perms[Constants::MM_PERMS_WRITE] || $perms[Constants::MM_PERMS_SUB] || $perms[Constants::MM_PERMS_APPLY]) && !mm_content_is_archive($mm_tree->id()) && \Drupal::currentUser()->hasPermission('use tree browser'));
}
static public function menuAccessRestore(MMTree $mm_tree, AccountInterface $account) {
$parent = mm_content_get_parent($mm_tree->id());
$perms = mm_content_user_can($mm_tree->id(), NULL, $account);
return AccessResult::allowedIf($perms[Constants::MM_PERMS_WRITE] && $perms[Constants::MM_PERMS_IS_RECYCLED] && mm_content_user_can($parent, Constants::MM_PERMS_IS_RECYCLE_BIN, $account));
}
static public function menuGetTitleSettingsDelete(MMTree $mm_tree) {
$perms = mm_content_user_can($mm_tree->id());
return $perms[Constants::MM_PERMS_IS_RECYCLED] || $perms[Constants::MM_PERMS_IS_GROUP] ? 'Delete permanently' : 'Delete';
}
static public function menuAccessDelete(MMTree $mm_tree, AccountInterface $account) {
$tree = mm_content_get_tree($mm_tree->id(), [
Constants::MM_GET_TREE_RETURN_PERMS => TRUE,
Constants::MM_GET_TREE_RETURN_FLAGS => TRUE,
Constants::MM_GET_TREE_DEPTH => 0,
]);
return AccessResult::allowedIf(isset($tree[0]) && $tree[0]->perms[Constants::MM_PERMS_WRITE] &&
($account->hasPermission('administer all menus') || !isset($tree[0]->flags['limit_delete'])) &&
!$tree[0]->perms[Constants::MM_PERMS_IS_RECYCLE_BIN] &&
(!$tree[0]->perms[Constants::MM_PERMS_IS_RECYCLED] || $account->hasPermission('delete permanently')));
}
static public function menuGetTitleSettingsSub(MMTree $mm_tree) {
return t('Add @subthing', mm_ui_strings(mm_content_is_group($mm_tree->id())));
}
static public function menuAccessSub(MMTree $mm_tree, AccountInterface $account) {
$perms = mm_content_user_can($mm_tree->id(), NULL, $account);
return AccessResult::allowedIf($perms[Constants::MM_PERMS_SUB] && !mm_content_is_node_content_block($mm_tree->id()) && $perms[Constants::MM_PERMS_SUB] && !$perms[Constants::MM_PERMS_IS_RECYCLED]);
}
public function showRevisions(MMTree $mm_tree) {
$access_modes = [
Constants::MM_PERMS_WRITE => $this->t('delete/edit'),
Constants::MM_PERMS_APPLY => $this->t('add content'),
Constants::MM_PERMS_SUB => $this->t('add sub-pages'),
Constants::MM_PERMS_READ => $this->t('read'),
];
$is_group = mm_content_is_group($mm_tree->id());
if ($is_group) {
$access_modes[Constants::MM_PERMS_APPLY] = $this->t('apply');
$access_modes[Constants::MM_PERMS_SUB] = $this->t('add sub-groups');
$access_modes[Constants::MM_PERMS_READ] = $this->t('see members');
}
$yesno = fn($data) => empty($data) ? $this->t('no') : $this->t('yes');
$from_list = function ($modes, $data) {
if (is_null($data) || $data === '') {
return '';
}
return $modes[$data] ?? $this->t('Unknown value "@value"', ['@value' => $data]);
};
$header = [
[
'data' => $this->t('Date'),
'#field' => 'mtime',
'#fmt' => fn($data) => $data ? mm_format_date($data, 'short') : t('(Unknown)'),
'field' => 'mtime',
'sort' => 'desc',
],
[
'data' => $this->t('Modified By'),
'#field' => 'muid',
'#fmt' => 'mm_content_uid2name',
],
[
'data' => $is_group ? $this->t('Group Name') : $this->t('Page Name'),
'#field' => 'name',
'#fmt' => Xss::filter(...),
],
[
'data' => $this->t('Page URL'),
'#field' => 'alias',
'#fmt' => Xss::filter(...),
],
[
'data' => $this->t('Parent'),
'#field' => 'parent',
'#fmt' => fn($data) => $data ? Link::fromTextAndUrl(mm_content_get_name($data), Url::fromRoute('entity.mm_tree.version_history', ['mm_tree' => $data]))->toString() : '',
],
[
'data' => $this->t('All Users'),
'#field' => 'default_mode',
'#fmt' => function ($data) use ($from_list, $access_modes) {
$out = [];
$data = explode(',', $data);
sort($data);
foreach ($data as $mode) {
$out[] = $from_list($access_modes, $mode);
}
return empty($out) ? $this->t('(none)') : implode(', ', $out);
},
],
[
'data' => $this->t('Owner'),
'#field' => 'uid',
'#fmt' => 'mm_content_uid2name',
],
[
'data' => $this->t('Hidden'),
'#field' => 'hidden',
'#fmt' => $yesno,
],
['data' => $this->t('Theme'), '#field' => 'theme', '#fmt' => Xss::filter(...)],
[
'data' => $this->t('Attributions'),
'#field' => 'node_info',
'#fmt' => function ($data) use ($from_list) {
static $modes;
if (!$modes) {
$dummy = [];
$modes = _mm_ui_node_info_values($dummy);
}
return $from_list($modes, $data);
},
],
[
'data' => $this->t('Summaries'),
'#field' => 'previews',
'#fmt' => $yesno,
],
[
'data' => $this->t('Comments'),
'#field' => 'comment',
'#fmt' => function ($data) use ($from_list) {
static $modes;
if (!$modes) {
$modes = _mm_ui_comment_write_setting_values();
}
return $from_list($modes, $data);
},
],
[
'data' => $this->t('RSS'),
'#field' => 'rss',
'#fmt' => $yesno,
],
['data' => $this->t('Hover'), '#field' => 'hover', '#fmt' => Xss::filter(...)],
];
if ($is_group) {
$header = array_intersect_key($header, [0 => 0, 1 => 0, 2 => 0, 4 => 0, 5 => 0, 6 => 0, 13 => 0]);
}
$rows = [];
$saved = [];
$query = $this->database->select('mm_tree_revision', 'r')
->fields('r')
->condition('mmtid', $mm_tree->id())
->extend(TableSortExtender::class)
->orderByHeader($header)
->extend(PagerSelectExtender::class)->limit(20)
->execute();
foreach ($query as $result) {
$row = [];
foreach ($header as $key => &$col) {
if (isset($saved[$key])) {
[$_field, $func] = $saved[$key];
}
else {
$_field = $col['#field'];
$func = $col['#fmt'];
$saved[$key] = [$_field, $func];
unset($col['#field'], $col['#fmt']);
}
$data = $result->{$_field} = $func($result->{$_field} ?? '');
$class = 'mm-revisions-changed';
if (!isset($col['field']) && !empty($last)) {
$old = $last->{$_field};
if ($old === $data) {
$class = 'mm-revisions-same';
}
else {
if (is_null($data) || $data === '') {
$class = 'mm-revisions-deleted';
$data = $old;
}
else {
if (is_null($old) || $old === '') {
$class = 'mm-revisions-added';
}
}
}
}
$row[] = ['data' => $data, 'class' => [$class]];
}
$last = $result;
$rows[] = $row;
}
$build['revisions'] = [
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#attached' => ['library' => ['monster_menus/mm_css']],
];
$build['pager'] = ['#type' => 'pager'];
$build['#cache']['tags'] = $mm_tree->getCacheTags();
return $build;
}
static public function menuAccessSolver(MMTree $mm_tree, AccountInterface $account = NULL) {
return AccessResult::allowedIf(static::menuAccessSolverByMMTID($mm_tree->id(), $account));
}
static public function menuAccessSolverByMMTID($mmtid, AccountInterface $account = NULL) {
if (empty($account)) {
$account = \Drupal::currentUser();
}
return $mmtid && $account->hasPermission('use permissions solver') && $account->hasPermission('access user profiles') && mm_content_user_can($mmtid, Constants::MM_PERMS_APPLY, $account);
}
public function showSolver(MMTree $mm_tree, AccountInterface $user = NULL) {
$result = ['#markup' => $this->t('<p>Unknown user</p>')];
if ($user) {
$result = PermissionsSolver::getSolverTable($mm_tree->id(), $user);
}
return new AjaxResponse((string) \Drupal::service('renderer')->renderRoot($result));
}
public function exportGroupAsCSV(MMTree $mm_tree) {
$headers = [
'Content-type' => 'text/csv',
'Content-Disposition' => 'attachment; filename=mm_group_' . $mm_tree->id() . '.csv',
'Pragma' => 'no-cache',
'Expires' => '0',
];
$output = fopen('php://output', 'w');
ob_start();
foreach (mm_content_get_users_in_group($mm_tree->id(), NULL, FALSE, 0, FALSE) as $uid => $name) {
$query = $this->database->select('users_field_data', 'u')->fields('u', ['name']);
$username = $query->condition('uid', $uid)->execute()->fetchField();
fputcsv($output, [$username, $name]);
}
fclose($output);
$response = ob_get_clean();
return new Response($response, 200, $headers);
}
private function getGroupToken($_get = NULL) {
if (empty($_get)) {
$_get = \Drupal::request()->query;
}
if (empty($form_token = $_get->get('token'))) {
throw new AccessDeniedHttpException();
}
return $form_token;
}
/**
* Get a list of users in a group, as JSON.
*
* @param MMTree $mm_tree
* The MMTree object of the group that is being queried
* @param string $element
* The name of the triggering form element
* @return JsonResponse
*/
public function getGroupUsersJson(MMTree $mm_tree, $element) {
$clean_element = str_replace('-', '_', $element);
$is_temp = $clean_element != 'mm_user_datatable_members_display';
$_get = \Drupal::request()->query;
$form_token = $is_temp ? $this->getGroupToken($_get) : '';
$mmtid = $mm_tree->id();
$users_array = mm_module_invoke_all_array('mm_large_group_get_users', [
'mmtid' => $mmtid,
'element' => $clean_element,
'form_token' => $form_token,
]);
if (empty($users_array)) {
$query = $this->database->select('users_field_data', 'u')
->fields('u', ['uid', 'name']);
if ($is_temp) {
$query->join('mm_group_temp', 'm', 'u.uid = m.uid');
$query->condition('m.gid', $mmtid)
->condition('m.sessionid', session_id())
->condition('m.token', $form_token);
}
else {
$query_virtual_group = $this->database->select('mm_group', 'm')
->fields('m', ['vgid']);
$query_virtual_group->condition('m.gid', $mmtid)
->condition('m.vgid', 0, '<>');
$query_virtual_group->groupBy('m.gid');
$query_virtual_group->groupBy('m.vgid');
$results = $query_virtual_group->execute();
if (($vgid = $results->fetchField()) > 0) {
$query->join('mm_virtual_group', 'm', 'u.uid = m.uid');
$query->condition('m.vgid', $vgid);
}
else {
$query->join('mm_group', 'm', 'u.uid = m.uid');
$query->condition('m.gid', $mmtid);
}
}
$unfiltered_query = clone $query;
try {
$asearch = $_get->all('search');
$aorder = $_get->all('order');
}
catch (BadRequestException) {
}
if (isset($asearch, $asearch['value'])) {
$query->condition('u.name', '%' . $asearch['value'] . '%', 'LIKE');
}
if (isset($aorder, $aorder[0]['column'], $aorder[0]['dir']) && $aorder[0]['column'] == '0') {
$direction = $aorder[0]['dir'] === 'desc' ? 'DESC' : 'ASC';
$query->orderBy('name', $direction);
}
$total_unfiltered_rows = $unfiltered_query->countQuery()->execute()->fetchField();
$total_rows = $query->countQuery()->execute()->fetchField();
if ($length = $_get->getInt('length')) {
$query->range($_get->getInt('start'), $length);
}
$results = $query->execute();
$users = [
'draw' => $_get->getInt('draw'),
'recordsTotal' => $total_unfiltered_rows,
'recordsFiltered' => $total_rows,
'data' => [],
];
foreach ($results as $item) {
if (!$is_temp) {
$users['data'][] = [$item->name];
}
elseif ($clean_element == 'members') {
$users['data'][] = [
$item->name,
'<a href="javascript:Drupal.mmGroupRemoveUser(' . $item->uid . ',\'' . $clean_element . '\')">' . $this->t('Delete') . '</a>',
];
}
}
}
else {
$users = $users_array;
}
return mm_json_response($users, ['Pragma' => 'no-cache']);
}
/**
* Delete a user from the editing form temporary table.
*
* @param MMTree $mm_tree
* The MMTree object of the group from which the user is being removed
* @param User $user
* User object of the user to be deleted
* @return JsonResponse
*/
public function deleteGroupUser(MMTree $mm_tree, User $user) {
$this->database->delete('mm_group_temp')
->condition('gid', $mm_tree->id())
->condition('uid', $user->id())
->condition('sessionid', session_id())
->condition('token', $this->getGroupToken())
->execute();
return mm_json_response([]);
}
/**
* Add one or more users to the editing form temporary table.
*
* @param MMTree $mm_tree
* The MMTree object of the group to which the user is being added
* @param string $uids
* A comma-separated list of uids to be added
* @return JsonResponse
*/
public function addGroupUser(MMTree $mm_tree, $uids) {
$form_token = $this->getGroupToken();
foreach (explode(',', $uids) as $uid) {
if (!empty($uid) && User::load($uid)) {
try {
$this->database->merge('mm_group_temp')
->keys([
'gid' => $mm_tree->id(),
'uid' => $uid,
'sessionid' => session_id(),
'token' => $form_token,
])
->fields([
'expire' => mm_request_time() + 24 * 60 * 60,
])
->execute();
}
catch (\Exception) {
}
}
}
return mm_json_response([]);
}
/**
* Allow the user to edit a node.
*
* @param NodeInterface $node
* Node to edit
* @return RedirectResponse|Form|array
* The form array or a RedirectResponse
*/
public function editNode(NodeInterface $node) {
return $this->entityFormBuilder()->getForm($node);
}
public function editNodeGetTitle(NodeInterface $node) {
return $this->t('Edit %title', ['%title' => $node->label()]);
}
static public function accessAnyAdmin(AccountInterface $account) {
return AccessResult::allowedIf(
$account->hasPermission('administer all groups') ||
$account->hasPermission('administer all users') ||
$account->hasPermission('administer all menus'));
}
/**
* Display a list of links to popup tree browsers
*
* @return array
* The HTML code for the links
*/
public function showAdminBrowseLinks() {
$list = [
(string) $this->t('Permission groups') => [
Groups::BROWSER_MODE_ADMIN_GROUP,
$this->currentUser()->hasPermission('administer all groups'),
mm_content_groups_mmtid(),
],
(string) $this->t('Top-level menus') => [
Fallback::BROWSER_MODE_ADMIN_PAGE,
$this->currentUser()->hasPermission('administer all menus'),
mm_home_mmtid(),
],
(string) $this->t('User menus') => [
Users::BROWSER_MODE_ADMIN_USER,
$this->currentUser()->hasPermission('administer all users'),
mm_content_users_mmtid(),
],
(string) $this->t('Entire tree') => [
Fallback::BROWSER_MODE_ADMIN_PAGE,
static::accessAllAdmin(),
1,
],
];
$links = [];
foreach ($list as $text => $item) {
if ($item[1]) {
$links[] = [
'attributes' => ['title' => $text],
'url' => Url::fromRoute('monster_menus.browser_load', [], ['query' => ['_path' => $item[2] . '-' . $item[0] . '-0--' . Constants::MM_PERMS_APPLY . '/' . $item[2]]]),
'title' => $text,
];
}
}
$arr = [
'#theme' => 'links',
'#links' => $links,
'#attributes' => [],
];
mm_static($arr, 'admin_browse');
return $arr;
}
static public function accessAllAdmin(AccountInterface $account = NULL) {
if (empty($account)) {
$account = \Drupal::currentUser();
}
return AccessResult::allowedIf(
$account->hasPermission('administer all groups') &&
$account->hasPermission('administer all users') &&
$account->hasPermission('administer all menus'));
}
/**
* Print a CSV dump of the entire MM tree
*/
function getTreeAsCSV($start = 1) {
\Drupal::service('monster_menus.dump_csv')->dump($start);
}
public function updateVgroupView($mmtid = NULL) {
mm_content_update_vgroup_view($mmtid);
return ['#markup' => $this->t('All virtual groups have been marked for update during the next cron run.')];
}
public function regenerateVgroup() {
mm_regenerate_vgroup();
return ['#markup' => $this->t('All virtual groups have been regenerated.')];
}
public function validateSortIndex($fix = FALSE) {
$result = \Drupal::service('monster_menus.validate_sort_index')->setOutputMode()->validate($fix);
return is_array($result) ? $result : ['#markup' => $result];
}
public function findOrphanNodes(Request $request) {
return \Drupal::service('monster_menus.check_orphan_nodes')->setOutputMode()->check($request->query->get('_fix'), 'table');
}
public function editSite() {
/** @var MMTree $mm_tree_root */
$mm_tree_root = MMTree::load(1);
return $this->handlePageSettings($mm_tree_root, 'sub');
}
/**
* Redirect to a specific page. This is used internally when the incoming URL
* looks like /NUMBER or /mm/NUMBER, so that the browser gets the full path of
* the page.
*
* @param Request $request
* The incoming request
* @param int $mmtid
* ID of the entry
* @return RedirectResponse
*/
public function redirectToMMTID(Request $request, $mmtid) {
$query = InboundPathProcessor::filterOargs($request->query->all());
return new RedirectResponse(mm_content_get_mmtid_url($mmtid, ['query' => $query, 'absolute' => TRUE])->toString());
}
public function menuAccessNode(NodeInterface $node, AccountInterface $account) {
return AccessResult::allowedIf($node->access('view', $account));
}
public function redirectToNode(Request $request, NodeInterface $node, $mode = NULL) {
$options = ['absolute' => TRUE];
if ($mmtids = mm_content_get_by_nid($node->id())) {
$params = ['mm_tree' => $mmtids[0], 'node' => $node->id()];
$options['query'] = $request->query->all();
}
switch ($mode) {
case 'edit':
if ($mmtids) {
return new RedirectResponse(Url::fromRoute('entity.node.edit_form', $params, $options)->toString());
}
return $this->editNode($node);
case 'delete':
if ($mmtids) {
return new RedirectResponse(Url::fromRoute('entity.node.delete_form', $params, $options)->toString());
}
return \Drupal::formBuilder()->getForm(DeleteNodeConfirmForm::class, NULL, $node);
default:
if ($mmtids) {
return new RedirectResponse(Url::fromRoute('entity.node.canonical', $params, $options)->toString());
}
return $this->entityTypeManager->getViewBuilder('node')->view($node);
}
}
}
