/**
  * {@inheritdoc}
  */
 public function render(array $js_assets)
 {
     $elements = array();
     // A dummy query-string is added to filenames, to gain control over
     // browser-caching. The string changes on every update or full cache
     // flush, forcing browsers to load a new copy of the files, as the
     // URL changed. Files that should not be cached (see _drupal_add_js())
     // get REQUEST_TIME as query-string instead, to enforce reload on every
     // page request.
     $default_query_string = $this->state->get('system.css_js_query_string') ?: '0';
     // For inline JavaScript to validate as XHTML, all JavaScript containing
     // XHTML needs to be wrapped in CDATA. To make that backwards compatible
     // with HTML 4, we need to comment out the CDATA-tag.
     $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
     $embed_suffix = "\n//--><!]]>\n";
     // Defaults for each SCRIPT element.
     $element_defaults = array('#type' => 'html_tag', '#tag' => 'script', '#value' => '');
     // Loop through all JS assets.
     foreach ($js_assets as $js_asset) {
         // Element properties that do not depend on JS asset type.
         $element = $element_defaults;
         $element['#browsers'] = $js_asset['browsers'];
         // Element properties that depend on item type.
         switch ($js_asset['type']) {
             case 'setting':
                 $element['#value_prefix'] = $embed_prefix;
                 $element['#value'] = 'var drupalSettings = ' . Json::encode(drupal_merge_js_settings($js_asset['data'])) . ";";
                 $element['#value_suffix'] = $embed_suffix;
                 break;
             case 'inline':
                 $element['#value_prefix'] = $embed_prefix;
                 $element['#value'] = $js_asset['data'];
                 $element['#value_suffix'] = $embed_suffix;
                 break;
             case 'file':
                 $query_string = empty($js_asset['version']) ? $default_query_string : 'v=' . $js_asset['version'];
                 $query_string_separator = strpos($js_asset['data'], '?') !== FALSE ? '&' : '?';
                 $element['#attributes']['src'] = file_create_url($js_asset['data']);
                 // Only add the cache-busting query string if this isn't an aggregate
                 // file.
                 if (!isset($js_asset['preprocessed'])) {
                     $element['#attributes']['src'] .= $query_string_separator . ($js_asset['cache'] ? $query_string : REQUEST_TIME);
                 }
                 break;
             case 'external':
                 $element['#attributes']['src'] = $js_asset['data'];
                 break;
             default:
                 throw new \Exception('Invalid JS asset type.');
         }
         // Attributes may only be set if this script is output independently.
         if (!empty($element['#attributes']['src']) && !empty($js_asset['attributes'])) {
             $element['#attributes'] += $js_asset['attributes'];
         }
         $elements[] = $element;
     }
     return $elements;
 }
 /**
  * Processes AJAX file uploads and deletions.
  *
  * @param \Symfony\Component\HttpFoundation\Request $request
  *   The current request object.
  *
  * @return \Drupal\Core\Ajax\AjaxResponse
  *   An AjaxResponse object.
  */
 public function upload(Request $request)
 {
     $form_parents = explode('/', $request->query->get('element_parents'));
     $form_build_id = $request->query->get('form_build_id');
     $request_form_build_id = $request->request->get('form_build_id');
     if (empty($request_form_build_id) || $form_build_id !== $request_form_build_id) {
         // Invalid request.
         drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error');
         $response = new AjaxResponse();
         $status_messages = array('#theme' => 'status_messages');
         return $response->addCommand(new ReplaceCommand(NULL, drupal_render($status_messages)));
     }
     try {
         /** @var $ajaxForm \Drupal\system\FileAjaxForm */
         $ajaxForm = $this->getForm($request);
         $form = $ajaxForm->getForm();
         $form_state = $ajaxForm->getFormState();
         $commands = $ajaxForm->getCommands();
     } catch (HttpExceptionInterface $e) {
         // Invalid form_build_id.
         drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error');
         $response = new AjaxResponse();
         $status_messages = array('#theme' => 'status_messages');
         return $response->addCommand(new ReplaceCommand(NULL, drupal_render($status_messages)));
     }
     // Get the current element and count the number of files.
     $current_element = NestedArray::getValue($form, $form_parents);
     $current_file_count = isset($current_element['#file_upload_delta']) ? $current_element['#file_upload_delta'] : 0;
     // Process user input. $form and $form_state are modified in the process.
     drupal_process_form($form['#form_id'], $form, $form_state);
     // Retrieve the element to be rendered.
     $form = NestedArray::getValue($form, $form_parents);
     // Add the special Ajax class if a new file was added.
     if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) {
         $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content';
     } else {
         $form['#suffix'] .= '<span class="ajax-new-content"></span>';
     }
     $status_messages = array('#theme' => 'status_messages');
     $form['#prefix'] .= drupal_render($status_messages);
     $output = drupal_render($form);
     drupal_process_attached($form);
     $js = _drupal_add_js();
     $settings = drupal_merge_js_settings($js['settings']['data']);
     $response = new AjaxResponse();
     foreach ($commands as $command) {
         $response->addCommand($command, TRUE);
     }
     return $response->addCommand(new ReplaceCommand(NULL, $output, $settings));
 }
Exemple #3
0
 /**
  * Processes an AJAX response into current content.
  *
  * This processes the AJAX response as ajax.js does. It uses the response's
  * JSON data, an array of commands, to update $this->content using equivalent
  * DOM manipulation as is used by ajax.js.
  * It does not apply custom AJAX commands though, because emulation is only
  * implemented for the AJAX commands that ship with Drupal core.
  *
  * @param string $content
  *   The current HTML content.
  * @param array $ajax_response
  *   An array of AJAX commands.
  * @param array $ajax_settings
  *   An array of AJAX settings which will be used to process the response.
  * @param array $drupal_settings
  *   An array of settings to update the value of drupalSettings for the
  *   currently-loaded page.
  *
  * @see drupalPostAjaxForm()
  * @see ajax.js
  */
 protected function drupalProcessAjaxResponse($content, array $ajax_response, array $ajax_settings, array $drupal_settings)
 {
     // ajax.js applies some defaults to the settings object, so do the same
     // for what's used by this function.
     $ajax_settings += array('method' => 'replaceWith');
     // DOM can load HTML soup. But, HTML soup can throw warnings, suppress
     // them.
     $dom = new \DOMDocument();
     @$dom->loadHTML($content);
     // XPath allows for finding wrapper nodes better than DOM does.
     $xpath = new \DOMXPath($dom);
     foreach ($ajax_response as $command) {
         // Error messages might be not commands.
         if (!is_array($command)) {
             continue;
         }
         switch ($command['command']) {
             case 'settings':
                 $drupal_settings = drupal_merge_js_settings(array($drupal_settings, $command['settings']));
                 break;
             case 'insert':
                 $wrapperNode = NULL;
                 // When a command doesn't specify a selector, use the
                 // #ajax['wrapper'] which is always an HTML ID.
                 if (!isset($command['selector'])) {
                     $wrapperNode = $xpath->query('//*[@id="' . $ajax_settings['wrapper'] . '"]')->item(0);
                 } elseif (in_array($command['selector'], array('head', 'body'))) {
                     $wrapperNode = $xpath->query('//' . $command['selector'])->item(0);
                 }
                 if ($wrapperNode) {
                     // ajax.js adds an enclosing DIV to work around a Safari bug.
                     $newDom = new \DOMDocument();
                     @$newDom->loadHTML('<div>' . $command['data'] . '</div>');
                     $newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
                     $method = isset($command['method']) ? $command['method'] : $ajax_settings['method'];
                     // The "method" is a jQuery DOM manipulation function. Emulate
                     // each one using PHP's DOMNode API.
                     switch ($method) {
                         case 'replaceWith':
                             $wrapperNode->parentNode->replaceChild($newNode, $wrapperNode);
                             break;
                         case 'append':
                             $wrapperNode->appendChild($newNode);
                             break;
                         case 'prepend':
                             // If no firstChild, insertBefore() falls back to
                             // appendChild().
                             $wrapperNode->insertBefore($newNode, $wrapperNode->firstChild);
                             break;
                         case 'before':
                             $wrapperNode->parentNode->insertBefore($newNode, $wrapperNode);
                             break;
                         case 'after':
                             // If no nextSibling, insertBefore() falls back to
                             // appendChild().
                             $wrapperNode->parentNode->insertBefore($newNode, $wrapperNode->nextSibling);
                             break;
                         case 'html':
                             foreach ($wrapperNode->childNodes as $childNode) {
                                 $wrapperNode->removeChild($childNode);
                             }
                             $wrapperNode->appendChild($newNode);
                             break;
                     }
                 }
                 break;
                 // @todo Add suitable implementations for these commands in order to
                 //   have full test coverage of what ajax.js can do.
             // @todo Add suitable implementations for these commands in order to
             //   have full test coverage of what ajax.js can do.
             case 'remove':
                 break;
             case 'changed':
                 break;
             case 'css':
                 break;
             case 'data':
                 break;
             case 'restripe':
                 break;
             case 'add_css':
                 break;
             case 'update_build_id':
                 $buildId = $xpath->query('//input[@name="form_build_id" and @value="' . $command['old'] . '"]')->item(0);
                 if ($buildId) {
                     $buildId->setAttribute('value', $command['new']);
                 }
                 break;
         }
     }
     $content = $dom->saveHTML();
     $this->setRawContent($content);
     $this->setDrupalSettings($drupal_settings);
 }
Exemple #4
0
 /**
  * Prepares the AJAX commands for sending back to the client.
  *
  * @param 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 ajaxRender(Request $request)
 {
     // Ajax responses aren't rendered with html.html.twig, so we have to call
     // drupal_get_css() and drupal_get_js() here, in order to have new files
     // added during this request to be loaded by the page. We only want to send
     // back files that the page hasn't already loaded, so we implement simple
     // diffing logic using array_diff_key().
     $ajax_page_state = $request->request->get('ajax_page_state');
     foreach (array('css', 'js') as $type) {
         // It is highly suspicious if
         // $request->request->get("ajax_page_state[$type]") is empty, since the
         // base page ought to have at least one JS file and one CSS file loaded.
         // It probably indicates an error, and rather than making the page reload
         // all of the files, instead we return no new files.
         if (empty($ajax_page_state[$type])) {
             $items[$type] = array();
         } else {
             $function = '_drupal_add_' . $type;
             $items[$type] = $function();
             \Drupal::moduleHandler()->alter($type, $items[$type]);
             // @todo Inline CSS and JS items are indexed numerically. These can't be
             //   reliably diffed with array_diff_key(), since the number can change
             //   due to factors unrelated to the inline content, so for now, we
             //   strip the inline items from Ajax responses, and can add support for
             //   them when _drupal_add_css() and _drupal_add_js() are changed to use
             //   a hash of the inline content as the array key.
             foreach ($items[$type] as $key => $item) {
                 if (is_numeric($key)) {
                     unset($items[$type][$key]);
                 }
             }
             // Ensure that the page doesn't reload what it already has.
             $items[$type] = array_diff_key($items[$type], $ajax_page_state[$type]);
         }
     }
     // Render the HTML to load these files, and add AJAX commands to insert this
     // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the
     // data from being altered again, as we already altered it above. Settings
     // are handled separately, afterwards.
     if (isset($items['js']['settings'])) {
         unset($items['js']['settings']);
     }
     $styles = drupal_get_css($items['css'], TRUE);
     $scripts_footer = drupal_get_js('footer', $items['js'], TRUE, TRUE);
     $scripts_header = drupal_get_js('header', $items['js'], TRUE, TRUE);
     // Prepend commands to add the resources, preserving their relative order.
     $resource_commands = array();
     if (!empty($styles)) {
         $resource_commands[] = new AddCssCommand($styles);
     }
     if (!empty($scripts_header)) {
         $resource_commands[] = new PrependCommand('head', $scripts_header);
     }
     if (!empty($scripts_footer)) {
         $resource_commands[] = new AppendCommand('body', $scripts_footer);
     }
     foreach (array_reverse($resource_commands) as $resource_command) {
         $this->addCommand($resource_command, TRUE);
     }
     // Prepend a command to merge changes and additions to drupalSettings.
     $scripts = _drupal_add_js();
     if (!empty($scripts['settings'])) {
         $settings = drupal_merge_js_settings($scripts['settings']['data']);
         // 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 for the keys below. An Ajax request
         // would update them with values for the Ajax request and incorrectly
         // override the page's values.
         // @see _drupal_add_js()
         foreach (array('basePath', 'currentPath', 'scriptPath', 'pathPrefix') as $item) {
             unset($settings[$item]);
         }
         $this->addCommand(new SettingsCommand($settings, TRUE), TRUE);
     }
     $commands = $this->commands;
     \Drupal::moduleHandler()->alter('ajax_render', $commands);
     return $commands;
 }