/** * Loops through and displays all form errors. * * @param array $form * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ protected function displayErrorMessages(array $form, FormStateInterface $form_state) { $error_links = []; $errors = $form_state->getErrors(); // Loop through all form errors and check if we need to display a link. foreach ($errors as $name => $error) { $form_element = FormElementHelper::getElementByName($name, $form); $title = FormElementHelper::getElementTitle($form_element); // Only show links to erroneous elements that are visible. $is_visible_element = Element::isVisibleElement($form_element); // Only show links for elements that have a title themselves or have // children with a title. $has_title = !empty($title); // Only show links for elements with an ID. $has_id = !empty($form_element['#id']); // Do not show links to elements with suppressed messages. Most often // their parent element is used for inline errors. if (!empty($form_element['#error_no_message'])) { unset($errors[$name]); } elseif ($is_visible_element && $has_title && $has_id) { $error_links[] = $this->l($title, Url::fromRoute('<none>', [], ['fragment' => $form_element['#id'], 'external' => TRUE])); unset($errors[$name]); } } // Set normal error messages for all remaining errors. foreach ($errors as $error) { $this->drupalSetMessage($error, 'error'); } if (!empty($error_links)) { $render_array = [['#markup' => $this->formatPlural(count($error_links), '1 error has been found: ', '@count errors have been found: ')], ['#theme' => 'item_list', '#items' => $error_links, '#context' => ['list_style' => 'comma-list']]]; $message = $this->renderer->renderPlain($render_array); $this->drupalSetMessage($message, 'error'); } }
/** * {@inheritdoc} */ public function execute($entity = NULL) { if (empty($this->configuration['node'])) { $this->configuration['node'] = $entity; } $message = $this->token->replace($this->configuration['message'], $this->configuration); $build = ['#markup' => $message]; // @todo Fix in https://www.drupal.org/node/2577827 drupal_set_message($this->renderer->renderPlain($build)); }
/** * {@inheritdoc} */ public function execute($comment = NULL) { $build = $this->viewBuilder->view($comment); $text = $this->renderer->renderPlain($build); foreach ($this->configuration['keywords'] as $keyword) { if (strpos($text, $keyword) !== FALSE) { $comment->setPublished(FALSE); $comment->save(); break; } } }
/** * Generates a response object after handing the un/flag request. * * Depending on the wrapper format of the request, it will either redirect * or return an ajax response. * * @param \Drupal\flag\FlagInterface $flag * The flag entity. * @param \Drupal\Core\Entity\EntityInterface $entity * The entity object. * @param \Symfony\Component\HttpFoundation\Request $request * The request. * * @return \Drupal\Core\Ajax\AjaxResponse|\Symfony\Component\HttpFoundation\RedirectResponse * The response object. */ protected function generateResponse(FlagInterface $flag, EntityInterface $entity, Request $request) { if ($request->get(MainContentViewSubscriber::WRAPPER_FORMAT) == 'drupal_ajax') { // Create a new AJAX response. $response = new AjaxResponse(); // Get the link type plugin. $link_type = $flag->getLinkTypePlugin(); // Generate the link render array and get the link CSS ID. $link = $link_type->getLink($flag, $entity); $link_id = '#' . $link['link']['#attributes']['id']; // Create a new JQuery Replace command to update the link display. $replace = new ReplaceCommand($link_id, $this->renderer->renderPlain($link)); $response->addCommand($replace); return $response; } else { // Redirect back to the entity. A passed in destination query parameter // will automatically override this. $url_info = $entity->toUrl(); return $this->redirect($url_info->getRouteName(), $url_info->getRouteParameters()); } }
/** * Generates themed output early in a page request. * * @see \Drupal\system\Tests\Theme\ThemeEarlyInitializationTest::testRequestListener() */ public function onRequest(GetResponseEvent $event) { if ($this->currentRouteMatch->getRouteName() === 'theme_test.request_listener') { // First, force the theme registry to be rebuilt on this page request. // This allows us to test a full initialization of the theme system in // the code below. drupal_theme_rebuild(); // Next, initialize the theme system by storing themed text in a global // variable. We will use this later in // theme_test_request_listener_page_callback() to test that even when the // theme system is initialized this early, it is still capable of // returning output and theming the page as a whole. $more_link = array('#type' => 'more_link', '#url' => Url::fromRoute('user.page'), '#attributes' => array('title' => 'Themed output generated in a KernelEvents::REQUEST listener')); $GLOBALS['theme_test_output'] = $this->renderer->renderPlain($more_link); } }
/** * Generates an overview table of older revisions of a node. * * @param \Drupal\node\NodeInterface $node * A node object. * * @return array * An array as expected by drupal_render(). */ public function revisionOverview(NodeInterface $node) { $account = $this->currentUser(); $langcode = $node->language()->getId(); $langname = $node->language()->getName(); $languages = $node->getTranslationLanguages(); $has_translations = count($languages) > 1; $node_storage = $this->entityManager()->getStorage('node'); $type = $node->getType(); $build['#title'] = $has_translations ? $this->t('@langname revisions for %title', ['@langname' => $langname, '%title' => $node->label()]) : $this->t('Revisions for %title', ['%title' => $node->label()]); $header = array($this->t('Revision'), $this->t('Operations')); $revert_permission = ($account->hasPermission("revert {$type} revisions") || $account->hasPermission('revert all revisions') || $account->hasPermission('administer nodes')) && $node->access('update'); $delete_permission = ($account->hasPermission("delete {$type} revisions") || $account->hasPermission('delete all revisions') || $account->hasPermission('administer nodes')) && $node->access('delete'); $rows = array(); $vids = $node_storage->revisionIds($node); $latest_revision = TRUE; foreach (array_reverse($vids) as $vid) { /** @var \Drupal\node\NodeInterface $revision */ $revision = $node_storage->loadRevision($vid); // Only show revisions that are affected by the language that is being // displayed. if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) { $username = ['#theme' => 'username', '#account' => $revision->getRevisionAuthor()]; // Use revision link to link to revisions that are not active. $date = $this->dateFormatter->format($revision->revision_timestamp->value, 'short'); if ($vid != $node->getRevisionId()) { $link = $this->l($date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid])); } else { $link = $node->link($date); } $row = []; $column = ['data' => ['#type' => 'inline_template', '#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}', '#context' => ['date' => $link, 'username' => $this->renderer->renderPlain($username), 'message' => ['#markup' => $revision->revision_log->value, '#allowed_tags' => Xss::getHtmlTagList()]]]]; // @todo Simplify once https://www.drupal.org/node/2334319 lands. $this->renderer->addCacheableDependency($column['data'], $username); $row[] = $column; if ($latest_revision) { $row[] = ['data' => ['#prefix' => '<em>', '#markup' => $this->t('Current revision'), '#suffix' => '</em>']]; foreach ($row as &$current) { $current['class'] = ['revision-current']; } $latest_revision = FALSE; } else { $links = []; if ($revert_permission) { $links['revert'] = ['title' => $this->t('Revert'), 'url' => $has_translations ? Url::fromRoute('node.revision_revert_translation_confirm', ['node' => $node->id(), 'node_revision' => $vid, 'langcode' => $langcode]) : Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid])]; } if ($delete_permission) { $links['delete'] = ['title' => $this->t('Delete'), 'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid])]; } $row[] = ['data' => ['#type' => 'operations', '#links' => $links]]; } $rows[] = $row; } } $build['node_revisions_table'] = array('#theme' => 'table', '#rows' => $rows, '#header' => $header, '#attached' => array('library' => array('node/drupal.node.admin'))); return $build; }
/** * Renders HTML response attachment placeholders. * * This is the last step where all of the attachments are placed into the * response object's contents. * * @param \Drupal\Core\Render\HtmlResponse $response * The HTML response to update. * @param array $placeholders * An array of placeholders, keyed by type with the placeholders * present in the content of the response as values. * @param array $variables * The variables to render and replace, keyed by type with renderable * arrays as values. */ protected function renderHtmlResponseAttachmentPlaceholders(HtmlResponse $response, array $placeholders, array $variables) { $content = $response->getContent(); foreach ($placeholders as $type => $placeholder) { if (isset($variables[$type])) { $content = str_replace($placeholder, $this->renderer->renderPlain($variables[$type]), $content); } } $response->setContent($content); }
/** * Prepares the AJAX commands to attach assets. * * @param \Drupal\Core\Ajax\AjaxResponse $response * The AJAX response to update. * @param \Symfony\Component\HttpFoundation\Request $request * The request object that the AJAX is responding to. * * @return array * An array of commands ready to be returned as JSON. */ protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) { $ajax_page_state = $request->request->get('ajax_page_state'); // Aggregate CSS/JS if necessary, but only during normal site operation. $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess'); $optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess'); $attachments = $response->getAttachments(); // Resolve the attached libraries into asset collections. $assets = new AttachedAssets(); $assets->setLibraries(isset($attachments['library']) ? $attachments['library'] : [])->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])->setSettings(isset($attachments['drupalSettings']) ? $attachments['drupalSettings'] : []); $css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css); list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js); // Render the HTML to load these files, and add AJAX commands to insert this // HTML in the page. Settings are handled separately, afterwards. $settings = []; if (isset($js_assets_header['drupalSettings'])) { $settings = $js_assets_header['drupalSettings']['data']; unset($js_assets_header['drupalSettings']); } if (isset($js_assets_footer['drupalSettings'])) { $settings = $js_assets_footer['drupalSettings']['data']; unset($js_assets_footer['drupalSettings']); } // Prepend commands to add the assets, preserving their relative order. $resource_commands = array(); if ($css_assets) { $css_render_array = $this->cssCollectionRenderer->render($css_assets); $resource_commands[] = new AddCssCommand((string) $this->renderer->renderPlain($css_render_array)); } if ($js_assets_header) { $js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header); $resource_commands[] = new PrependCommand('head', (string) $this->renderer->renderPlain($js_header_render_array)); } if ($js_assets_footer) { $js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer); $resource_commands[] = new AppendCommand('body', (string) $this->renderer->renderPlain($js_footer_render_array)); } foreach (array_reverse($resource_commands) as $resource_command) { $response->addCommand($resource_command, TRUE); } // Prepend a command to merge changes and additions to drupalSettings. if (!empty($settings)) { // During Ajax requests basic path-specific settings are excluded from // new drupalSettings values. The original page where this request comes // from already has the right values. An Ajax request would update them // with values for the Ajax request and incorrectly override the page's // values. // @see system_js_settings_alter() unset($settings['path']); $response->addCommand(new SettingsCommand($settings, TRUE), TRUE); } $commands = $response->getCommands(); $this->moduleHandler->alter('ajax_render', $commands); return $commands; }
/** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => $this->t('Run tests'), '#tableselect' => TRUE, '#button_type' => 'primary'); $form['clean'] = array('#type' => 'fieldset', '#title' => $this->t('Clean test environment'), '#description' => $this->t('Remove tables with the prefix "simpletest" and temporary directories that are left over from tests that crashed. This is intended for developers when creating tests.'), '#weight' => 200); $form['clean']['op'] = array('#type' => 'submit', '#value' => $this->t('Clean environment'), '#submit' => array('simpletest_clean_environment')); // Do not needlessly re-execute a full test discovery if the user input // already contains an explicit list of test classes to run. $user_input = $form_state->getUserInput(); if (!empty($user_input['tests'])) { return $form; } // JavaScript-only table filters. $form['filters'] = array('#type' => 'container', '#attributes' => array('class' => array('table-filter', 'js-show'))); $form['filters']['text'] = array('#type' => 'search', '#title' => $this->t('Search'), '#size' => 30, '#placeholder' => $this->t('Enter test name…'), '#attributes' => array('class' => array('table-filter-text'), 'data-table' => '#simpletest-test-form', 'autocomplete' => 'off', 'title' => $this->t('Enter at least 3 characters of the test name or description to filter by.'))); $form['tests'] = array('#type' => 'table', '#id' => 'simpletest-form-table', '#tableselect' => TRUE, '#header' => array(array('data' => $this->t('Test'), 'class' => array('simpletest-test-label')), array('data' => $this->t('Description'), 'class' => array('simpletest-test-description'))), '#empty' => $this->t('No tests to display.'), '#attached' => array('library' => array('simpletest/drupal.simpletest'))); // Define the images used to expand/collapse the test groups. $image_collapsed = array('#theme' => 'image', '#uri' => 'core/misc/menu-collapsed.png', '#width' => '7', '#height' => '7', '#alt' => $this->t('Expand'), '#title' => $this->t('Expand'), '#suffix' => '<a href="#" class="simpletest-collapse">(' . $this->t('Expand') . ')</a>'); $image_extended = array('#theme' => 'image', '#uri' => 'core/misc/menu-expanded.png', '#width' => '7', '#height' => '7', '#alt' => $this->t('Collapse'), '#title' => $this->t('Collapse'), '#suffix' => '<a href="#" class="simpletest-collapse">(' . $this->t('Collapse') . ')</a>'); $form['tests']['#attached']['drupalSettings']['simpleTest']['images'] = [(string) $this->renderer->renderPlain($image_collapsed), (string) $this->renderer->renderPlain($image_extended)]; // Generate the list of tests arranged by group. $groups = simpletest_test_get_all(); foreach ($groups as $group => $tests) { $form['tests'][$group] = array('#attributes' => array('class' => array('simpletest-group'))); // Make the class name safe for output on the page by replacing all // non-word/decimal characters with a dash (-). $group_class = 'module-' . strtolower(trim(preg_replace("/[^\\w\\d]/", "-", $group))); // Override tableselect column with custom selector for this group. // This group-select-all checkbox is injected via JavaScript. $form['tests'][$group]['select'] = array('#wrapper_attributes' => array('id' => $group_class, 'class' => array('simpletest-group-select-all'))); $form['tests'][$group]['title'] = array('#prefix' => '<div class="simpletest-image" id="simpletest-test-group-' . $group_class . '"></div>', '#markup' => '<label for="' . $group_class . '-group-select-all">' . $group . '</label>', '#wrapper_attributes' => array('class' => array('simpletest-group-label'))); $form['tests'][$group]['description'] = array('#markup' => ' ', '#wrapper_attributes' => array('class' => array('simpletest-group-description'))); // Cycle through each test within the current group. foreach ($tests as $class => $info) { $form['tests'][$class] = array('#attributes' => array('class' => array($group_class . '-test', 'js-hide'))); $form['tests'][$class]['title'] = array('#type' => 'label', '#title' => '\\' . $info['name'], '#wrapper_attributes' => array('class' => array('simpletest-test-label', 'table-filter-text-source'))); $form['tests'][$class]['description'] = array('#prefix' => '<div class="description">', '#plain_text' => $info['description'], '#suffix' => '</div>', '#wrapper_attributes' => array('class' => array('simpletest-test-description', 'table-filter-text-source'))); } } return $form; }
/** * Tests JavaScript files that have querystrings attached get added right. */ function testAddJsFileWithQueryString() { $build['#attached']['library'][] = 'common_test/querystring'; $assets = AttachedAssets::createFromRenderArray($build); $css = $this->assetResolver->getCssAssets($assets, FALSE); $js = $this->assetResolver->getJsAssets($assets, FALSE)[1]; $this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2', $css), 'CSS file with query string is correctly added.'); $this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2', $js), 'JavaScript file with query string is correctly added.'); $css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css); $rendered_css = $this->renderer->renderPlain($css_render_array); $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js); $rendered_js = $this->renderer->renderPlain($js_render_array); $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0'; $this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="' . str_replace('&', '&', file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2'))) . '&' . $query_string . '" media="all" />'), FALSE, 'CSS file with query string gets version query string correctly appended..'); $this->assertNotIdentical(strpos($rendered_js, '<script src="' . str_replace('&', '&', file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2'))) . '&' . $query_string . '"></script>'), FALSE, 'JavaScript file with query string gets version query string correctly appended.'); }
/** * Validation handler for the credentials form. * * @param array $form * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ public function validateCredentialForm(array &$form, FormStateInterface $form_state) { // Retrieve the database driver from the form, use reflection to get the // namespace, and then construct a valid database array the same as in // settings.php. $driver = $form_state->getValue('driver'); $drivers = $this->getDatabaseTypes(); $reflection = new \ReflectionClass($drivers[$driver]); $install_namespace = $reflection->getNamespaceName(); $database = $form_state->getValue($driver); // Cut the trailing \Install from namespace. $database['namespace'] = substr($install_namespace, 0, strrpos($install_namespace, '\\')); $database['driver'] = $driver; // Validate the driver settings and just end here if we have any issues. if ($errors = $drivers[$driver]->validateDatabaseSettings($database)) { foreach ($errors as $name => $message) { $form_state->setErrorByName($name, $message); } return; } try { $connection = $this->getConnection($database); $version = $this->getLegacyDrupalVersion($connection); if (!$version) { $form_state->setErrorByName($database['driver'] . '][0', $this->t('Source database does not contain a recognizable Drupal version.')); } else { $this->createDatabaseStateSettings($database, $version); $migrations = $this->getMigrations('migrate_drupal_' . $version, $version); // Get the system data from source database. $system_data = $this->getSystemData($connection); // Convert the migration object into array // so that it can be stored in form storage. $migration_array = []; foreach ($migrations as $migration) { $migration_array[$migration->id()] = $migration->label(); } // Store the retrieved migration IDs in form storage. $form_state->set('migrations', $migration_array); $form_state->set('source_base_path', $form_state->getValue('source_base_path')); // Store the retrived system data in form storage. $form_state->set('system_data', $system_data); } } catch (\Exception $e) { $error_message = ['#type' => 'inline_template', '#template' => '{% trans %}Resolve the issue below to continue the upgrade.{% endtrans%}{{ errors }}', '#context' => ['errors' => ['#theme' => 'item_list', '#items' => [$e->getMessage()]]]]; $form_state->setErrorByName($database['driver'] . '][0', $this->renderer->renderPlain($error_message)); } }
/** * Generates an overview table of older revisions of a node. * * @param \Drupal\node\NodeInterface $node * A node object. * * @return array * An array as expected by drupal_render(). */ public function revisionOverview(NodeInterface $node) { $account = $this->currentUser(); $node_storage = $this->entityManager()->getStorage('node'); $type = $node->getType(); $build = array(); $build['#title'] = $this->t('Revisions for %title', array('%title' => $node->label())); $header = array($this->t('Revision'), $this->t('Operations')); $revert_permission = ($account->hasPermission("revert {$type} revisions") || $account->hasPermission('revert all revisions') || $account->hasPermission('administer nodes')) && $node->access('update'); $delete_permission = ($account->hasPermission("delete {$type} revisions") || $account->hasPermission('delete all revisions') || $account->hasPermission('administer nodes')) && $node->access('delete'); $rows = array(); $vids = $node_storage->revisionIds($node); foreach (array_reverse($vids) as $vid) { $revision = $node_storage->loadRevision($vid); $username = ['#theme' => 'username', '#account' => $revision->uid->entity]; // Use revision link to link to revisions that are not active. $date = $this->dateFormatter->format($revision->revision_timestamp->value, 'short'); if ($vid != $node->getRevisionId()) { $link = $this->l($date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid])); } else { $link = $node->link($date); } $row = []; $column = ['data' => ['#type' => 'inline_template', '#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}', '#context' => ['date' => $link, 'username' => $this->renderer->renderPlain($username), 'message' => Xss::filter($revision->revision_log->value)]]]; // @todo Simplify once https://www.drupal.org/node/2334319 lands. $this->renderer->addCacheableDependency($column['data'], $username); $row[] = $column; if ($vid == $node->getRevisionId()) { $row[0]['class'] = ['revision-current']; $row[] = ['data' => SafeMarkup::placeholder($this->t('current revision')), 'class' => ['revision-current']]; } else { $links = []; if ($revert_permission) { $links['revert'] = ['title' => $this->t('Revert'), 'url' => Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid])]; } if ($delete_permission) { $links['delete'] = ['title' => $this->t('Delete'), 'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid])]; } $row[] = ['data' => ['#type' => 'operations', '#links' => $links]]; } $rows[] = $row; } $build['node_revisions_table'] = array('#theme' => 'table', '#rows' => $rows, '#header' => $header, '#attached' => array('library' => array('node/drupal.node.admin'))); return $build; }
/** * Get any database errors and links them to a form element. * * @param array $database * An array of database settings. * @param string $settings_file * The settings file that contains the database settings. * * @return array * An array of form errors keyed by the element name and parents. */ protected function getDatabaseErrors(array $database, $settings_file) { $errors = install_database_errors($database, $settings_file); $form_errors = array_filter($errors, function ($value) { // Errors keyed by something other than an integer already are linked to // form elements. return is_int($value); }); // Find the generic errors. $errors = array_diff_key($errors, $form_errors); if (count($errors)) { $error_message = ['#type' => 'inline_template', '#template' => '{% trans %}Resolve all issues below to continue the installation. For help configuring your database server, see the <a href="https://www.drupal.org/getting-started/install">installation handbook</a>, or contact your hosting provider.{% endtrans%}{{ errors }}', '#context' => ['errors' => ['#theme' => 'item_list', '#items' => $errors]]]; // These are generic errors, so we do not have any specific key of the // database connection array to attach them to; therefore, we just put // them in the error array with standard numeric keys. $form_errors[$database['driver'] . '][0'] = $this->renderer->renderPlain($error_message); } return $form_errors; }
/** * Indexes a single node. * * @param \Drupal\node\NodeInterface $node * The node to index. */ protected function indexNode(NodeInterface $node) { $languages = $node->getTranslationLanguages(); $node_render = $this->entityManager->getViewBuilder('node'); foreach ($languages as $language) { $node = $node->getTranslation($language->getId()); // Render the node. $build = $node_render->view($node, 'search_index', $language->getId()); unset($build['#theme']); $rendered = $this->renderer->renderPlain($build); $text = '<h1>' . SafeMarkup::checkPlain($node->label($language->getId())) . '</h1>' . $rendered; // Fetch extra data normally not visible. $extra = $this->moduleHandler->invokeAll('node_update_index', array($node, $language->getId())); foreach ($extra as $t) { $text .= $t; } // Update index, using search index "type" equal to the plugin ID. search_index($this->getPluginId(), $node->id(), $language->getId(), $text); } }
/** * {@inheritdoc} */ public function settingsForm(array $form, FormStateInterface $form_state, EditorEntity $editor) { $settings = $editor->getSettings(); $ckeditor_settings_toolbar = array('#theme' => 'ckeditor_settings_toolbar', '#editor' => $editor, '#plugins' => $this->ckeditorPluginManager->getButtons()); $form['toolbar'] = array('#type' => 'container', '#attached' => array('library' => array('ckeditor/drupal.ckeditor.admin'), 'drupalSettings' => ['ckeditor' => ['toolbarAdmin' => (string) $this->renderer->renderPlain($ckeditor_settings_toolbar)]]), '#attributes' => array('class' => array('ckeditor-toolbar-configuration'))); $form['toolbar']['button_groups'] = array('#type' => 'textarea', '#title' => t('Toolbar buttons'), '#default_value' => json_encode($settings['toolbar']['rows']), '#attributes' => array('class' => array('ckeditor-toolbar-textarea'))); // CKEditor plugin settings, if any. $form['plugin_settings'] = array('#type' => 'vertical_tabs', '#title' => t('CKEditor plugin settings'), '#attributes' => array('id' => 'ckeditor-plugin-settings')); $this->ckeditorPluginManager->injectPluginSettingsForm($form, $form_state, $editor); if (count(Element::children($form['plugins'])) === 0) { unset($form['plugins']); unset($form['plugin_settings']); } // Hidden CKEditor instance. We need a hidden CKEditor instance with all // plugins enabled, so we can retrieve CKEditor's per-feature metadata (on // which tags, attributes, styles and classes are enabled). This metadata is // necessary for certain filters' (e.g. the html_filter filter) settings to // be updated accordingly. // Get a list of all external plugins and their corresponding files. $plugins = array_keys($this->ckeditorPluginManager->getDefinitions()); $all_external_plugins = array(); foreach ($plugins as $plugin_id) { $plugin = $this->ckeditorPluginManager->createInstance($plugin_id); if (!$plugin->isInternal()) { $all_external_plugins[$plugin_id] = $plugin->getFile(); } } // Get a list of all buttons that are provided by all plugins. $all_buttons = array_reduce($this->ckeditorPluginManager->getButtons(), function ($result, $item) { return array_merge($result, array_keys($item)); }, array()); // Build a fake Editor object, which we'll use to generate JavaScript // settings for this fake Editor instance. $fake_editor = entity_create('editor', array('format' => $editor->id(), 'editor' => 'ckeditor', 'settings' => array('toolbar' => array('rows' => array(0 => array(0 => array('name' => 'All existing buttons', 'items' => $all_buttons)))), 'plugins' => $settings['plugins']))); $config = $this->getJSSettings($fake_editor); // Remove the ACF configuration that is generated based on filter settings, // because otherwise we cannot retrieve per-feature metadata. unset($config['allowedContent']); $form['hidden_ckeditor'] = array('#markup' => '<div id="ckeditor-hidden" class="hidden"></div>', '#attached' => array('drupalSettings' => ['ckeditor' => ['hiddenCKEditorConfig' => $config]])); return $form; }
/** * Indexes a single node. * * @param \Drupal\node\NodeInterface $node * The node to index. */ protected function indexNode(NodeInterface $node) { $languages = $node->getTranslationLanguages(); $node_render = $this->entityManager->getViewBuilder('node'); foreach ($languages as $language) { $node = $node->getTranslation($language->getId()); // Render the node. $build = $node_render->view($node, 'search_index', $language->getId()); unset($build['#theme']); // Add the title to text so it is searchable. $build['search_title'] = ['#prefix' => '<h1>', '#plain_text' => $node->label(), '#suffix' => '</h1>', '#weight' => -1000]; $text = $this->renderer->renderPlain($build); // Fetch extra data normally not visible. $extra = $this->moduleHandler->invokeAll('node_update_index', [$node]); foreach ($extra as $t) { $text .= $t; } // Update index, using search index "type" equal to the plugin ID. search_index($this->getPluginId(), $node->id(), $language->getId(), $text); } }
/** * {@inheritdoc} */ public function saveFile($uri, $destination, $extensions, AccountProxyInterface $user, $validators = []) { // Create the file entity. $file = $this->fileEntityFromUri($uri, $user); // Replace tokens. As the tokens might contain HTML we convert it to plain // text. $destination = PlainTextOutput::renderFromHtml($this->token->replace($destination)); // Handle potentialy dangerous extensions. $renamed = $this->renameExecutableExtensions($file); // The .txt extension may not be in the allowed list of extensions. We have // to add it here or else the file upload will fail. if ($renamed && !empty($extensions)) { $extensions .= ' txt'; drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', ['%filename' => $file->getFilename()])); } // Validate the file. $errors = $this->validateFile($file, $extensions, $validators); if (!empty($errors)) { $message = ['error' => ['#markup' => t('The specified file %name could not be uploaded.', ['%name' => $file->getFilename()])], 'item_list' => ['#theme' => 'item_list', '#items' => $errors]]; drupal_set_message($this->renderer->renderPlain($message), 'error'); return FALSE; } // Prepare destination. if (!$this->prepareDestination($file, $destination)) { drupal_set_message(t('The file could not be uploaded because the destination %destination is invalid.', ['%destination' => $destination]), 'error'); return FALSE; } // Move uploaded files from PHP's upload_tmp_dir to destination. $move_result = file_unmanaged_move($uri, $file->getFileUri()); if (!$move_result) { drupal_set_message(t('File upload error. Could not move uploaded file.'), 'error'); $this->logger->notice('Upload error. Could not move uploaded file %file to destination %destination.', ['%file' => $file->getFilename(), '%destination' => $file->getFileUri()]); return FALSE; } // Set the permissions on the new file. $this->fileSystem->chmod($file->getFileUri()); // If we made it this far it's safe to record this file in the database. $file->save(); return $file; }
/** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state, $node = NULL) { $account = $this->currentUser; $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(); $langname = $this->languageManager->getLanguageName($langcode); $languages = $node->getTranslationLanguages(); $has_translations = count($languages) > 1; $node_storage = $this->entityManager->getStorage('node'); $type = $node->getType(); $vids = array_reverse($node_storage->revisionIds($node)); $revision_count = count($vids); $build['#title'] = $has_translations ? $this->t('@langname revisions for %title', ['@langname' => $langname, '%title' => $node->label()]) : $this->t('Revisions for %title', ['%title' => $node->label()]); $build['nid'] = array('#type' => 'hidden', '#value' => $node->id()); $table_header = array('revision' => $this->t('Revision'), 'operations' => $this->t('Operations')); // Allow comparisons only if there are 2 or more revisions. if ($revision_count > 1) { $table_header += array('select_column_one' => '', 'select_column_two' => ''); } $rev_revert_perm = $account->hasPermission("revert {$type} revisions") || $account->hasPermission('revert all revisions') || $account->hasPermission('administer nodes'); $rev_delete_perm = $account->hasPermission("delete {$type} revisions") || $account->hasPermission('delete all revisions') || $account->hasPermission('administer nodes'); $revert_permission = $rev_revert_perm && $node->access('update'); $delete_permission = $rev_delete_perm && $node->access('delete'); // Contains the table listing the revisions. $build['node_revisions_table'] = array('#type' => 'table', '#header' => $table_header, '#attributes' => array('class' => array('diff-revisions'))); $build['node_revisions_table']['#attached']['library'][] = 'diff/diff.general'; $build['node_revisions_table']['#attached']['drupalSettings']['diffRevisionRadios'] = $this->config->get('general_settings.radio_behavior'); $latest_revision = TRUE; // Add rows to the table. foreach ($vids as $vid) { if ($revision = $node_storage->loadRevision($vid)) { if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) { $username = array('#theme' => 'username', '#account' => $revision->getRevisionAuthor()); $revision_date = $this->date->format($revision->getRevisionCreationTime(), 'short'); // Use revision link to link to revisions that are not active. if ($vid != $node->getRevisionId()) { $link = $this->l($revision_date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid])); } else { $link = $node->link($revision_date); } // Default revision. if ($latest_revision) { $row = array('revision' => array('#type' => 'inline_template', '#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}', '#context' => ['date' => $link, 'username' => $this->renderer->renderPlain($username), 'message' => ['#markup' => $revision->revision_log->value, '#allowed_tags' => Xss::getHtmlTagList()]])); // Allow comparisons only if there are 2 or more revisions. if ($revision_count > 1) { $row += array('select_column_one' => array('#type' => 'radio', '#title_display' => 'invisible', '#name' => 'radios_left', '#return_value' => $vid, '#default_value' => FALSE), 'select_column_two' => array('#type' => 'radio', '#title_display' => 'invisible', '#name' => 'radios_right', '#default_value' => $vid, '#return_value' => $vid)); } $row['operations'] = array('#prefix' => '<em>', '#markup' => $this->t('Current revision'), '#suffix' => '</em>', '#attributes' => array('class' => array('revision-current'))); $latest_revision = FALSE; } else { $route_params = array('node' => $node->id(), 'node_revision' => $vid, 'langcode' => $langcode); $links = array(); if ($revert_permission) { $links['revert'] = ['title' => $this->t('Revert'), 'url' => $has_translations ? Url::fromRoute('node.revision_revert_translation_confirm', ['node' => $node->id(), 'node_revision' => $vid, 'langcode' => $langcode]) : Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid])]; } if ($delete_permission) { $links['delete'] = array('title' => $this->t('Delete'), 'url' => Url::fromRoute('node.revision_delete_confirm', $route_params)); } // Here we don't have to deal with 'only one revision' case because // if there's only one revision it will also be the default one, // entering on the first branch of this if else statement. $row = array('revision' => array('#type' => 'inline_template', '#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}', '#context' => ['date' => $link, 'username' => $this->renderer->renderPlain($username), 'message' => ['#markup' => $revision->revision_log->value, '#allowed_tags' => Xss::getHtmlTagList()]]), 'select_column_one' => array('#type' => 'radio', '#title_display' => 'invisible', '#name' => 'radios_left', '#return_value' => $vid, '#default_value' => isset($vids[1]) ? $vids[1] : FALSE), 'select_column_two' => array('#type' => 'radio', '#title_display' => 'invisible', '#name' => 'radios_right', '#return_value' => $vid, '#default_value' => FALSE), 'operations' => array('#type' => 'operations', '#links' => $links)); } // Add the row to the table. $build['node_revisions_table'][] = $row; } } } // Allow comparisons only if there are 2 or more revisions. if ($revision_count > 1) { $build['submit'] = array('#type' => 'submit', '#value' => t('Compare'), '#attributes' => array('class' => array('diff-button'))); } return $build; }
/** * Language translations overview page for a configuration name. * * @param \Symfony\Component\HttpFoundation\Request $request * Page request object. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The route match. * @param string $plugin_id * The plugin ID of the mapper. * * @return array * Page render array. */ public function itemPage(Request $request, RouteMatchInterface $route_match, $plugin_id) { /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($plugin_id); $mapper->populateFromRouteMatch($route_match); $page = array(); $page['#title'] = $this->t('Translations for %label', array('%label' => $mapper->getTitle())); $languages = $this->languageManager->getLanguages(); if (count($languages) == 1) { drupal_set_message($this->t('In order to translate configuration, the website must have at least two <a href=":url">languages</a>.', array(':url' => $this->url('entity.configurable_language.collection'))), 'warning'); } try { $original_langcode = $mapper->getLangcode(); $operations_access = TRUE; } catch (ConfigMapperLanguageException $exception) { $items = []; foreach ($mapper->getConfigNames() as $config_name) { $langcode = $mapper->getLangcodeFromConfig($config_name); $items[] = $this->t('@name: @langcode', ['@name' => $config_name, '@langcode' => $langcode]); } $message = ['message' => ['#markup' => $this->t('The configuration objects have different language codes so they cannot be translated:')], 'items' => ['#theme' => 'item_list', '#items' => $items]]; drupal_set_message($this->renderer->renderPlain($message), 'warning'); $original_langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED; $operations_access = FALSE; } if (!isset($languages[$original_langcode])) { // If the language is not configured on the site, create a dummy language // object for this listing only to ensure the user gets useful info. $language_name = $this->languageManager->getLanguageName($original_langcode); $languages[$original_langcode] = new Language(array('id' => $original_langcode, 'name' => $language_name)); } // We create a fake request object to pass into // ConfigMapperInterface::populateFromRouteMatch() for the different languages. // Creating a separate request for each language and route is neither easily // possible nor performant. $fake_request = $request->duplicate(); $page['languages'] = array('#type' => 'table', '#header' => array($this->t('Language'), $this->t('Operations'))); foreach ($languages as $language) { $langcode = $language->getId(); // This is needed because // ConfigMapperInterface::getAddRouteParameters(), for example, // needs to return the correct language code for each table row. $fake_route_match = RouteMatch::createFromRequest($fake_request); $mapper->populateFromRouteMatch($fake_route_match); $mapper->setLangcode($langcode); // Prepare the language name and the operations depending on whether this // is the original language or not. if ($langcode == $original_langcode) { $language_name = '<strong>' . $this->t('@language (original)', array('@language' => $language->getName())) . '</strong>'; // Check access for the path/route for editing, so we can decide to // include a link to edit or not. $edit_access = $this->accessManager->checkNamedRoute($mapper->getBaseRouteName(), $route_match->getRawParameters()->all(), $this->account); // Build list of operations. $operations = array(); if ($edit_access) { $operations['edit'] = array('title' => $this->t('Edit'), 'url' => Url::fromRoute($mapper->getBaseRouteName(), $mapper->getBaseRouteParameters(), ['query' => ['destination' => $mapper->getOverviewPath()]])); } } else { $language_name = $language->getName(); $operations = array(); // If no translation exists for this language, link to add one. if (!$mapper->hasTranslation($language)) { $operations['add'] = array('title' => $this->t('Add'), 'url' => Url::fromRoute($mapper->getAddRouteName(), $mapper->getAddRouteParameters())); } else { // Otherwise, link to edit the existing translation. $operations['edit'] = array('title' => $this->t('Edit'), 'url' => Url::fromRoute($mapper->getEditRouteName(), $mapper->getEditRouteParameters())); $operations['delete'] = array('title' => $this->t('Delete'), 'url' => Url::fromRoute($mapper->getDeleteRouteName(), $mapper->getDeleteRouteParameters())); } } $page['languages'][$langcode]['language'] = array('#markup' => $language_name); $page['languages'][$langcode]['operations'] = array('#type' => 'operations', '#links' => $operations, '#access' => $langcode == $original_langcode || $operations_access); } return $page; }
/** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => $this->t('Import all')); $source_list = $this->syncStorage->listAll(); $storage_comparer = new StorageComparer($this->syncStorage, $this->activeStorage, $this->configManager); if (empty($source_list) || !$storage_comparer->createChangelist()->hasChanges()) { $form['no_changes'] = array('#type' => 'table', '#header' => array($this->t('Name'), $this->t('Operations')), '#rows' => array(), '#empty' => $this->t('There are no configuration changes to import.')); $form['actions']['#access'] = FALSE; return $form; } elseif (!$storage_comparer->validateSiteUuid()) { drupal_set_message($this->t('The staged configuration cannot be imported, because it originates from a different site than this site. You can only synchronize configuration between cloned instances of this site.'), 'error'); $form['actions']['#access'] = FALSE; return $form; } // A list of changes will be displayed, so check if the user should be // warned of potential losses to configuration. if ($this->snapshotStorage->exists('core.extension')) { $snapshot_comparer = new StorageComparer($this->activeStorage, $this->snapshotStorage, $this->configManager); if (!$form_state->getUserInput() && $snapshot_comparer->createChangelist()->hasChanges()) { $change_list = array(); foreach ($snapshot_comparer->getAllCollectionNames() as $collection) { foreach ($snapshot_comparer->getChangelist(NULL, $collection) as $config_names) { if (empty($config_names)) { continue; } foreach ($config_names as $config_name) { $change_list[] = $config_name; } } } sort($change_list); $message = [['#markup' => $this->t('The following items in your active configuration have changes since the last import that may be lost on the next import.')], ['#theme' => 'item_list', '#items' => $change_list]]; drupal_set_message($this->renderer->renderPlain($message), 'warning'); } } // Store the comparer for use in the submit. $form_state->set('storage_comparer', $storage_comparer); // Add the AJAX library to the form for dialog support. $form['#attached']['library'][] = 'core/drupal.ajax'; foreach ($storage_comparer->getAllCollectionNames() as $collection) { if ($collection != StorageInterface::DEFAULT_COLLECTION) { $form[$collection]['collection_heading'] = array('#type' => 'html_tag', '#tag' => 'h2', '#value' => $this->t('@collection configuration collection', array('@collection' => $collection))); } foreach ($storage_comparer->getChangelist(NULL, $collection) as $config_change_type => $config_names) { if (empty($config_names)) { continue; } // @todo A table caption would be more appropriate, but does not have the // visual importance of a heading. $form[$collection][$config_change_type]['heading'] = array('#type' => 'html_tag', '#tag' => 'h3'); switch ($config_change_type) { case 'create': $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count new', '@count new'); break; case 'update': $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count changed', '@count changed'); break; case 'delete': $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count removed', '@count removed'); break; case 'rename': $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count renamed', '@count renamed'); break; } $form[$collection][$config_change_type]['list'] = array('#type' => 'table', '#header' => array($this->t('Name'), $this->t('Operations'))); foreach ($config_names as $config_name) { if ($config_change_type == 'rename') { $names = $storage_comparer->extractRenameNames($config_name); $route_options = array('source_name' => $names['old_name'], 'target_name' => $names['new_name']); $config_name = $this->t('@source_name to @target_name', array('@source_name' => $names['old_name'], '@target_name' => $names['new_name'])); } else { $route_options = array('source_name' => $config_name); } if ($collection != StorageInterface::DEFAULT_COLLECTION) { $route_name = 'config.diff_collection'; $route_options['collection'] = $collection; } else { $route_name = 'config.diff'; } $links['view_diff'] = array('title' => $this->t('View differences'), 'url' => Url::fromRoute($route_name, $route_options), 'attributes' => array('class' => array('use-ajax'), 'data-dialog-type' => 'modal', 'data-dialog-options' => json_encode(array('width' => 700)))); $form[$collection][$config_change_type]['list']['#rows'][] = array('name' => $config_name, 'operations' => array('data' => array('#type' => 'operations', '#links' => $links))); } } } return $form; }
/** * Validation handler for the credentials form. * * @param array $form * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ public function validateCredentialForm(array &$form, FormStateInterface $form_state) { // Skip if rollback was chosen. if ($form_state->getValue('upgrade_option') == static::MIGRATE_UPGRADE_ROLLBACK) { return; } // Retrieve the database driver from the form, use reflection to get the // namespace and then construct a valid database array the same as in // settings.php. if ($driver = $form_state->getValue('driver')) { $drivers = $this->getDatabaseTypes(); $reflection = new \ReflectionClass($drivers[$driver]); $install_namespace = $reflection->getNamespaceName(); $database = $form_state->getValue($driver); // Cut the trailing \Install from namespace. $database['namespace'] = substr($install_namespace, 0, strrpos($install_namespace, '\\')); $database['driver'] = $driver; // Validate the driver settings and just end here if we have any issues. if ($errors = $drivers[$driver]->validateDatabaseSettings($database)) { foreach ($errors as $name => $message) { $form_state->setErrorByName($name, $message); } return; } } else { $database = []; // Find a migration which has database credentials and use those. $query = $this->entityStorage->getQuery('OR'); $ids = $query->execute(); foreach ($ids as $id) { /** @var \Drupal\migrate\Entity\MigrationInterface $migration */ $migration = Migration::load($id); $is_drupal_migration = FALSE; foreach ($migration->get('migration_tags') as $migration_tag) { if (substr($migration_tag, 0, 7) === 'Drupal ') { $is_drupal_migration = TRUE; break; } } if ($is_drupal_migration) { $source = $migration->get('source'); if ($database = $this->state->get($source['database_state_key'])['database']) { break; } } } } try { // Get the template for migration. $migration_template = $this->getMigrationTemplates($database, $form_state->getValue('source_base_path')); // Get a copy of all the relevant migrations so we run them in next step. $migrations = $this->getMigrations($migration_template); // Get the system data from source database. $system_data = $this->getSystemData($database); // Convert the migration object into array // so that it can be stored in form storage. $migration_array = []; foreach ($migrations as $migration) { $migration_array[] = $migration->toArray(); } // Store the retrieved migration templates in form storage. $form_state->set('migration_template', $migration_template); // Store the retrieved migration ids in form storage. $form_state->set('migration', $migration_array); // Store the retrived system data in from storage. $form_state->set('system_data', $system_data); } catch (\Exception $e) { $error_message = ['#type' => 'inline_template', '#template' => '{% trans %}Resolve the issue below to continue the upgrade.{% endtrans%}{{ errors }}', '#context' => ['errors' => ['#theme' => 'item_list', '#items' => [$e->getMessage()]]]]; $form_state->setErrorByName($database['driver'] . '][0', $this->renderer->renderPlain($error_message)); } }