diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5f81a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +sites/default/files/* +sites/default/settings.php \ No newline at end of file diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index 2d85f6a..d29da83 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -99,7 +99,9 @@ public static function registerTwig(ContainerBuilder $container) { // This is saved / loaded via drupal_php_storage(). // All files can be refreshed by clearing caches. // @todo ensure garbage collection of expired files. - 'cache' => settings()->get('twig_cache', TRUE), + // When in the installer, twig_cache must be FALSE until we know the + // files folder is writable. + 'cache' => drupal_installation_attempted() ? FALSE : settings()->get('twig_cache', TRUE), 'base_template_class' => 'Drupal\Core\Template\TwigTemplate', // @todo Remove in followup issue // @see http://drupal.org/node/1712444. diff --git a/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearchExecute.php b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearchExecute.php new file mode 100644 index 0000000..667c185 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearchExecute.php @@ -0,0 +1,95 @@ +keywords = $keywords; + } + + public function isSearchExecutable() { + return (bool) $this->keywords; + } + + public function execute() { + $results = array(); + if (!$this->isSearchExecutable()) { + return $results; + } + $keys = $this->keywords; + // Build matching conditions + $query = db_select('search_index', 'i', array('target' => 'slave')) + ->extend('Drupal\search\SearchQuery') + ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); + $query->join('node', 'n', 'n.nid = i.sid'); + $query + ->condition('n.status', 1) + ->addTag('node_access') + ->searchExpression($keys, 'node'); + + // Insert special keywords. + $query->setOption('type', 'n.type'); + $query->setOption('langcode', 'n.langcode'); + if ($query->setOption('term', 'ti.tid')) { + $query->join('taxonomy_index', 'ti', 'n.nid = ti.nid'); + } + // Only continue if the first pass query matches. + if (!$query->executeFirstPass()) { + return array(); + } + + // Add the ranking expressions. + _node_rankings($query); + + // Load results. + $find = $query + // Add the language code of the indexed item to the result of the query, + // since the node will be rendered using the respective language. + ->fields('i', array('langcode')) + ->limit(10) + ->execute(); + foreach ($find as $item) { + // Render the node. + $node = node_load($item->sid); + $build = node_view($node, 'search_result', $item->langcode); + unset($build['#theme']); + $node->rendered = drupal_render($build); + + // Fetch comments for snippet. + $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node, $item->langcode); + + $extra = module_invoke_all('node_search_result', $node, $item->langcode); + + $language = language_load($item->langcode); + $uri = $node->uri(); + $results[] = array( + 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE, 'language' => $language))), + 'type' => check_plain(node_get_type_label($node)), + 'title' => $node->label($item->langcode), + 'user' => theme('username', array('account' => $node)), + 'date' => $node->changed, + 'node' => $node, + 'extra' => $extra, + 'score' => $item->calculated_score, + 'snippet' => search_excerpt($keys, $node->rendered, $item->langcode), + 'langcode' => $node->langcode, + ); + } + return $results; + } +} \ No newline at end of file diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 1e1084e..215d5a1 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -1256,16 +1256,6 @@ function _node_rankings(SelectExtender $query) { } /** - * Implements hook_search_info(). - */ -function node_search_info() { - return array( - 'title' => 'Content', - 'path' => 'node', - ); -} - -/** * Implements hook_search_access(). */ function node_search_access() { @@ -1319,72 +1309,6 @@ function node_search_admin() { } /** - * Implements hook_search_execute(). - */ -function node_search_execute($keys = NULL, $conditions = NULL) { - // Build matching conditions - $query = db_select('search_index', 'i', array('target' => 'slave')) - ->extend('Drupal\search\SearchQuery') - ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); - $query->join('node', 'n', 'n.nid = i.sid'); - $query - ->condition('n.status', 1) - ->addTag('node_access') - ->searchExpression($keys, 'node'); - - // Insert special keywords. - $query->setOption('type', 'n.type'); - $query->setOption('langcode', 'n.langcode'); - if ($query->setOption('term', 'ti.tid')) { - $query->join('taxonomy_index', 'ti', 'n.nid = ti.nid'); - } - // Only continue if the first pass query matches. - if (!$query->executeFirstPass()) { - return array(); - } - - // Add the ranking expressions. - _node_rankings($query); - - // Load results. - $find = $query - // Add the language code of the indexed item to the result of the query, - // since the node will be rendered using the respective language. - ->fields('i', array('langcode')) - ->limit(10) - ->execute(); - $results = array(); - foreach ($find as $item) { - // Render the node. - $node = node_load($item->sid); - $build = node_view($node, 'search_result', $item->langcode); - unset($build['#theme']); - $node->rendered = drupal_render($build); - - // Fetch comments for snippet. - $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node, $item->langcode); - - $extra = module_invoke_all('node_search_result', $node, $item->langcode); - - $language = language_load($item->langcode); - $uri = $node->uri(); - $results[] = array( - 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE, 'language' => $language))), - 'type' => check_plain(node_get_type_label($node)), - 'title' => $node->label($item->langcode), - 'user' => theme('username', array('account' => $node)), - 'date' => $node->changed, - 'node' => $node, - 'extra' => $extra, - 'score' => $item->calculated_score, - 'snippet' => search_excerpt($keys, $node->rendered, $item->langcode), - 'langcode' => $node->langcode, - ); - } - return $results; -} - -/** * Implements hook_ranking(). */ function node_ranking() { diff --git a/core/modules/picture/picture.module b/core/modules/picture/picture.module index 60d0f5b..7dbbaee 100644 --- a/core/modules/picture/picture.module +++ b/core/modules/picture/picture.module @@ -268,7 +268,7 @@ function theme_picture($variables) { ); } else { - // Mutliple images, use srcset. + // Multiple images, use srcset. $srcset = array(); foreach ($new_sources as $new_source) { $srcset[] = image_style_url($new_source['style_name'], $new_source['uri']) . ' ' . $new_source['#multiplier']; diff --git a/core/modules/search/lib/Drupal/search/Annotation/SearchPagePlugin.php b/core/modules/search/lib/Drupal/search/Annotation/SearchPagePlugin.php new file mode 100644 index 0000000..53fff9a --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Annotation/SearchPagePlugin.php @@ -0,0 +1,45 @@ + $namespaces['Drupal\search']); + $this->discovery = new AnnotatedClassDiscovery('Search', $namespaces, $annotation_namespaces, 'Drupal\search\Annotation\SearchPagePlugin'); + $this->discovery = new AlterDecorator($this->discovery, 'search_info'); + $this->discovery = new CacheDecorator($this->discovery, 'search'); + + $this->factory = new DefaultFactory($this->discovery); + } + + /** + * Overrides \Drupal\Component\Plugin\PluginManagerBase::createInstance(). + */ + public function createInstance($plugin_id, array $configuration = array()) { + $plugin_definition = $this->discovery->getDefinition($plugin_id); + $plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition); + + // Normalize the data + $configuration + array( + 'keywords' => '', + 'query_parameters' => array(), + 'request_attributes' => array(), + ); + return new $plugin_class($configuration['keywords'], $configuration['query_parameters'], $configuration['request_attributes']); + } + + /** + * Implements \Drupal\Core\PluginManagerInterface::getInstance(). + * + * Finds an instance based on the module that owns the plugin + */ + public function getInstance(array $options = array()) { + $module = $options['module']; + foreach ($this->getDefinitions() as $plugin_id => $definition) { + if ($definition['module'] == $module && !empty($options['configuration'])) { + // Return the instance of the searchExecutor where the annotation + // has mentioned it belongs to a certain module. Eg.: node + return $this->createInstance($plugin_id, $options['configuration']); + } + } + } +} \ No newline at end of file diff --git a/core/modules/search/search.module b/core/modules/search/search.module index efaf031..f566e93 100644 --- a/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -151,6 +151,7 @@ function search_menu() { $items['search'] = array( 'title' => 'Search', 'page callback' => 'search_view', + 'page arguments' => array(NULL, ''), 'access callback' => 'search_is_active', 'type' => MENU_SUGGESTED_ITEM, 'file' => 'search.pages.inc', @@ -177,28 +178,28 @@ function search_menu() { // system appears to be having two sets of tabs. See discussion on issue // http://drupal.org/node/245103 for details. - drupal_static_reset('search_get_info'); $default_info = search_get_default_module_info(); if ($default_info) { - foreach (search_get_info() as $module => $search_info) { + $search_definitions = Drupal::service('plugin.manager.search.page')->getDefinitions(); + foreach ($search_definitions as $plugin_id => $search_info) { $path = 'search/' . $search_info['path']; $items[$path] = array( 'title' => $search_info['title'], 'page callback' => 'search_view', - 'page arguments' => array($module, ''), + 'page arguments' => array($plugin_id, ''), 'access callback' => '_search_menu_access', - 'access arguments' => array($module), + 'access arguments' => array($search_info['module']), 'type' => MENU_LOCAL_TASK, 'file' => 'search.pages.inc', - 'weight' => $module == $default_info['module'] ? -10 : 0, + 'weight' => $search_info['module'] == $default_info['module'] ? -10 : 0, ); $items["$path/%menu_tail"] = array( 'title' => $search_info['title'], 'load arguments' => array('%map', '%index'), 'page callback' => 'search_view', - 'page arguments' => array($module, 2), + 'page arguments' => array($plugin_id, 2), 'access callback' => '_search_menu_access', - 'access arguments' => array($module), + 'access arguments' => array($search_info['module']), // The default local task points to its parent, but this item points to // where it should so it should not be changed. 'type' => MENU_LOCAL_TASK, @@ -219,7 +220,7 @@ function search_menu() { */ function search_is_active() { // This path cannot be accessed if there are no active modules. - return user_access('search content') && search_get_info(); + return user_access('search content') && Drupal::service('plugin.manager.search.page')->getDefinitions();; } /** @@ -231,30 +232,32 @@ function search_is_active() { * have been set to active on the search settings page will be returned. * * @return - * Array of hook_search_info() return values, keyed by module name. The + * Array of plugin.manager.search.page definitions, keyed by module name. The * 'title' and 'path' array elements will be set to defaults for each module * if not supplied by hook_search_info(), and an additional array element of * 'module' will be added (set to the module name). */ function search_get_info($all = FALSE) { - $search_hooks = &drupal_static(__FUNCTION__); + $search_info = &drupal_static(__FUNCTION__); - if (!isset($search_hooks)) { - foreach (module_implements('search_info') as $module) { - $search_hooks[$module] = call_user_func($module . '_search_info'); + if (!isset($search_plugins)) { + $search_info = array(); + $search_definitions = Drupal::service('plugin.manager.search.page')->getDefinitions(); + foreach ($search_definitions as $plugin_id => $plugin) { + $module = $plugin['module']; // Use module name as the default value. - $search_hooks[$module] += array('title' => $module, 'path' => $module); + $search_info[$module] = array('title' => $module, 'path' => $module); // Include the module name itself in the array. - $search_hooks[$module]['module'] = $module; + $search_info[$module]['module'] = $module; } } if ($all) { - return $search_hooks; + return $search_info; } // Return only modules that are set to active in search settings. - return array_intersect_key($search_hooks, array_flip(config('search.settings')->get('active_modules'))); + return array_intersect_key($search_info, array_flip(config('search.settings')->get('active_modules'))); } /** @@ -1071,9 +1074,8 @@ function search_box_form_submit($form, &$form_state) { * Renderable array of search results. No return value if $keys are not * supplied or if the given search module is not active. */ -function search_data($keys, $module, $conditions = NULL) { - if (module_hook($module, 'search_execute')) { - $results = module_invoke($module, 'search_execute', $keys, $conditions); +function search_data(SearchExecuteInterface $plugin, $module) { + $results = $plugin->execute(); if (module_hook($module, 'search_page')) { return module_invoke($module, 'search_page', $results); } @@ -1084,7 +1086,6 @@ function search_data($keys, $module, $conditions = NULL) { '#module' => $module, ); } - } } /** diff --git a/core/modules/search/search.pages.inc b/core/modules/search/search.pages.inc index eaae71a..3047f1f 100644 --- a/core/modules/search/search.pages.inc +++ b/core/modules/search/search.pages.inc @@ -13,7 +13,7 @@ * @param $keys * Keywords to use for the search. */ -function search_view($module = NULL, $keys = '') { +function search_view($plugin_id = NULL, $keys = '') { $info = FALSE; $keys = trim($keys); // Also try to pull search keywords out of the $_REQUEST variable to @@ -22,14 +22,7 @@ function search_view($module = NULL, $keys = '') { $keys = trim($_REQUEST['keys']); } - if (!empty($module)) { - $active_module_info = search_get_info(); - if (isset($active_module_info[$module])) { - $info = $active_module_info[$module]; - } - } - - if (empty($info)) { + if (empty($plugin_id)) { // No path or invalid path: find the default module. Note that if there // are no enabled search modules, this function should never be called, // since hook_menu() would not have defined any search paths. @@ -41,7 +34,13 @@ function search_view($module = NULL, $keys = '') { } drupal_goto($path); } - + $request = Drupal::request(); + $config = array( + 'keywords' => $keys, + 'query_parameters' => $request->query->all(), + 'request_attributes' => $request->attributes->all(), + ); + $plugin = Drupal::service('plugin.manager.search.page')->createInstance($plugin_id, $config); // Default results output is an empty string. $results = array('#markup' => ''); // Process the search form. Note that if there is $_POST data, @@ -50,18 +49,13 @@ function search_view($module = NULL, $keys = '') { // form submits with POST but redirects to GET. This way we can keep // the search query URL clean as a whistle. if (empty($_POST['form_id']) || $_POST['form_id'] != 'search_form') { - $conditions = NULL; - if (isset($info['conditions_callback'])) { - // Build an optional array of more search conditions. - $conditions = call_user_func($info['conditions_callback'], $keys); - } // Only search if there are keywords or non-empty conditions. - if ($keys || !empty($conditions)) { + if ($plugin->isSearchExecutable()) { // Log the search keys. watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys)); // Collect the search results. - $results = search_data($keys, $info['module'], $conditions); + $results = search_data($plugin, $info['module']); } } // The form may be altered based on whether the search was run. diff --git a/core/modules/search/search.services.yml b/core/modules/search/search.services.yml new file mode 100644 index 0000000..c80f7c7 --- /dev/null +++ b/core/modules/search/search.services.yml @@ -0,0 +1,4 @@ +services: + plugin.manager.search.page: + class: Drupal\search\SearchPagePluginManager + arguments: ['@container.namespaces'] \ No newline at end of file diff --git a/core/modules/system/system.module b/core/modules/system/system.module index cbf9f44..ce1b39b 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1243,6 +1243,7 @@ function system_library_info() { array('system', 'jquery'), array('system', 'drupal'), array('system', 'drupalSettings'), + array('system', 'drupal.ajax'), array('system', 'drupal.progress'), array('system', 'jquery.once'), ), diff --git a/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearchExecute.php b/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearchExecute.php new file mode 100644 index 0000000..d39fd75 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearchExecute.php @@ -0,0 +1,74 @@ +keywords = $keywords; + } + + public function isSearchExecutable() { + return (bool) $this->keywords; + } + + public function execute() { + $results = array(); + if (!$this->isSearchExecutable()) { + return $results; + } + $keys = $this->keywords; + $find = array(); + // Replace wildcards with MySQL/PostgreSQL wildcards. + $keys = preg_replace('!\*+!', '%', $keys); + $query = db_select('users') + ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); + $query->fields('users', array('uid')); + if (user_access('administer users')) { + // Administrators can also search in the otherwise private email field, and + // they don't need to be restricted to only active users. + $query->fields('users', array('mail')); + $query->condition(db_or()-> + condition('name', '%' . db_like($keys) . '%', 'LIKE')-> + condition('mail', '%' . db_like($keys) . '%', 'LIKE')); + } + else { + // Regular users can only search via usernames, and we do not show them + // blocked accounts. + $query->condition('name', '%' . db_like($keys) . '%', 'LIKE') + ->condition('status', 1); + } + $uids = $query + ->limit(15) + ->execute() + ->fetchCol(); + $accounts = user_load_multiple($uids); + + foreach ($accounts as $account) { + $result = array( + 'title' => user_format_name($account), + 'link' => url('user/' . $account->uid, array('absolute' => TRUE)), + ); + if (user_access('administer users')) { + $result['title'] .= ' (' . $account->mail . ')'; + } + $results[] = $result; + } + + return $results; + } +} \ No newline at end of file diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 0b884fa..81e7668 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -538,15 +538,6 @@ function user_permission() { } /** - * Implements hook_search_info(). - */ -function user_search_info() { - return array( - 'title' => 'Users', - ); -} - -/** * Implements hook_search_access(). */ function user_search_access() { @@ -554,51 +545,6 @@ function user_search_access() { } /** - * Implements hook_search_execute(). - */ -function user_search_execute($keys = NULL, $conditions = NULL) { - $find = array(); - // Replace wildcards with MySQL/PostgreSQL wildcards. - $keys = preg_replace('!\*+!', '%', $keys); - $query = db_select('users') - ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); - $query->fields('users', array('uid')); - if (user_access('administer users')) { - // Administrators can also search in the otherwise private email field, and - // they don't need to be restricted to only active users. - $query->fields('users', array('mail')); - $query->condition(db_or()-> - condition('name', '%' . db_like($keys) . '%', 'LIKE')-> - condition('mail', '%' . db_like($keys) . '%', 'LIKE')); - } - else { - // Regular users can only search via usernames, and we do not show them - // blocked accounts. - $query->condition('name', '%' . db_like($keys) . '%', 'LIKE') - ->condition('status', 1); - } - $uids = $query - ->limit(15) - ->execute() - ->fetchCol(); - $accounts = user_load_multiple($uids); - - $results = array(); - foreach ($accounts as $account) { - $result = array( - 'title' => user_format_name($account), - 'link' => url('user/' . $account->uid, array('absolute' => TRUE)), - ); - if (user_access('administer users')) { - $result['title'] .= ' (' . $account->mail . ')'; - } - $results[] = $result; - } - - return $results; -} - -/** * Implements hook_user_view(). */ function user_user_view(User $account, EntityDisplay $display) {