tra('List'), 'documentation' => 'PluginList', 'description' => tra('Search for, list, and filter all types of items and display custom-formatted results.'), 'prefs' => ['wikiplugin_list', 'feature_search'], 'body' => tra('List configuration information'), 'filter' => 'wikicontent', 'profile_reference' => 'search_plugin_content', 'iconname' => 'list', 'introduced' => 7, 'tags' => [ 'basic' ], 'params' => [ 'searchable_only' => [ 'required' => false, 'name' => tra('Searchable Only Results'), 'description' => tra('Only include results marked as searchable in the index.'), 'filter' => 'digits', 'default' => '1', 'options' => [ ['text' => tra(''), 'value' => ''], ['text' => tra('Yes'), 'value' => '1'], ['text' => tra('No'), 'value' => '0'], ], ], 'gui' => [ 'required' => false, 'name' => tra('Use List GUI'), 'description' => tra('Use the graphical user interface for editing this list plugin.'), 'filter' => 'digits', 'default' => '1', 'options' => [ ['text' => tra(''), 'value' => ''], ['text' => tra('Yes'), 'value' => '1'], ['text' => tra('No'), 'value' => '0'], ], ], 'cache' => [ 'required' => false, 'name' => tra('Cache Output'), 'description' => tra('Cache output of this list plugin.'), 'filter' => 'word', 'since' => '20.0', 'options' => [ ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'], ['text' => tra('Yes (even for admins)'), 'value' => 'a'], ] ], 'cacheexpiry' => [ 'required' => false, 'name' => tra('Cache Expiry Time'), 'description' => tra('Time before cache is expired in minutes.'), 'filter' => 'word', 'since' => '20.0', ], 'cachepurgerules' => [ 'required' => false, 'name' => tra('Cache Purge Rules'), 'description' => tra('Purge the cache when the type:id objects are updated. Set id=0 for any of that type. Or set type:withparam:x. Examples: trackeritem:20, trackeritem:trackerId:3, file:galleryId:5, forum post:forum_id:7, forum post:parent_id:8. Note that rule changes affect future caching, not past caches.'), 'separator' => ',', 'default' => '', 'filter' => 'text', 'since' => '20.0', ], 'multisearchid' => [ 'required' => false, 'name' => 'ID of MULTISEARCH block from which to render results', 'description' => tra('This is for much better performance by doing one search for multiple LIST plugins together. Render results from previous {MULTISEARCH(id-x)}...{MULTISEARCH} block by providing the ID used in that block.'), 'filter' => 'text', 'since' => '20.0', ], ], ]; } function wikiplugin_list($data, $params) { global $prefs; global $user; static $multisearchResults; static $originalQueries; static $i; $i++; $listId = 'wplist-' . $i; if (!empty($_REQUEST['download']) && $listId != $_REQUEST['listId'] ) { return; } if (! isset($params['cache'])) { if ($prefs['unified_list_cache_default_on'] == 'y') { $params['cache'] = 'y'; } else { $params['cache'] = 'n'; } } if ($params['cache'] == 'y') { // Exclude any type of admin from caching foreach (TikiLib::lib('user')->get_user_permissions($user) as $permission) { if (substr($permission, 0, 12) == 'tiki_p_admin') { $params['cache'] = 'n'; break; } } } // cache = a means cache even for admins if ($params['cache'] == 'a') { $params['cache'] = 'y'; } if (! isset($params['gui'])) { $params['gui'] = 1; } if ($prefs['wikiplugin_list_gui'] === 'y' && $params['gui']) { TikiLib::lib('header') ->add_jsfile('lib/jquery_tiki/pluginedit_list.js') ->add_jsfile('vendor_bundled/vendor/jquery-plugins/nestedsortable/jquery.ui.nestedSortable.js'); } $tosearch = []; if (isset($params['multisearchid']) && $params['multisearchid'] > '') { // If 'multisearchid' is provided as a parameter to the LIST plugin, it means the list plugin // is to render the results of that ID specified in the MULTISEARCH block of the "pre-searching" LIST plugin. $renderMultisearch = true; } else { $renderMultisearch = false; } $now = TikiLib::lib('tiki')->now; $cachelib = TikiLib::lib('cache'); $cacheType = 'listplugin'; if ($user) { $cacheName = md5($data); } else { $cacheName = md5($data . "loggedout"); } if (isset($params['cacheexpiry'])) { $cacheExpiry = $params['cacheexpiry']; } else { $cacheExpiry = $prefs['unified_list_cache_default_expiry']; } // First need to check for {MULTISEARCH()} blocks as then will need to do all queries at the same time $multisearch = false; $matches = WikiParser_PluginMatcher::match($data); $offset_arg = 'offset'; $argParser = new WikiParser_PluginArgumentParser(); foreach ($matches as $match) { if ($match->getName() == 'multisearch') { if ($prefs['unified_engine'] != 'elastic') { return tra("Error: {MULTISEARCH(id=x)} requires use of Elasticsearch as the engine."); } $args = $argParser->parse($match->getArguments()); if (! isset($args['id'])) { return tra("Error: {MULTISEARCH(id=x)} needs an ID to be specified."); } $tosearch[$args['id']] = $match->getBody(); $multisearch = true; } if ($match->getName() == 'list' || $match->getName() == 'pagination') { $args = $argParser->parse($match->getArguments()); if (! empty($args['offset_arg'])) { // Update cacheName by offset arg to have different cache for each page of paginated list $offset_arg = $args['offset_arg']; } } } if (! empty($_REQUEST[$offset_arg])) { $cacheName .= '_' . $args['offset_arg'] . '=' . $_REQUEST[$offset_arg]; } if (! $multisearch) { $tosearch = [ $data ]; } if ($params['cache'] == 'y') { // Clean rules setting $rules = array(); foreach ($params['cachepurgerules'] as $r) { $parts = explode(':', $r, 2); $cleanrule['type'] = trim($parts[0]); $cleanrule['object'] = trim($parts[1]); $rules[] = $cleanrule; } // Need to check if existing rules have been changed and therefore have to be deleted first $oldrules = $cachelib->get_purge_rules_for_cache($cacheType, $cacheName); if ($oldrules != $rules) { $cachelib->clear_purge_rules_for_cache($cacheType, $cacheName); } // Now set rules foreach ($rules as $rule) { $cachelib->set_cache_purge_rule($rule['type'], $rule['object'], $cacheType, $cacheName); } // Now retrieve cache if any if ($cachelib->isCached($cacheName, $cacheType)) { list($date, $out) = $cachelib->getSerialized($cacheName, $cacheType); if ($date > $now - $cacheExpiry * 60) { if ($multisearch) { $multisearchResults = $out; } else { return $out; } } else { $cachelib->invalidate($cacheName, $cacheType); } } } $unifiedsearchlib = TikiLib::lib('unifiedsearch'); if (! $index = $unifiedsearchlib->getIndex()) { return ''; } if ($renderMultisearch && isset($originalQueries[$params['multisearchid']])) { // Skip searching if rendering already retrieved results. $query = $originalQueries[$params['multisearchid']]; $result = $query->search($index, '', $multisearchResults[$params['multisearchid']]); } else { // Perform searching foreach ($tosearch as $id => $body) { if ($renderMultisearch) { // when rendering and if not already in $originalQueries, then just need to get the one that matches. if ($params['multisearchid'] != $id) { continue; } } // Handle each query. If not multisearch will just be one. $query = new Search_Query(); if (! isset($params['searchable_only']) || $params['searchable_only'] == 1) { $query->filterIdentifier('y', 'searchable'); } $unifiedsearchlib->initQuery($query); $matches = WikiParser_PluginMatcher::match($body); $builder = new Search_Query_WikiBuilder($query); $builder->enableAggregate(); $builder->apply($matches); $tsret = $builder->applyTablesorter($matches); if (! empty($tsret['max']) || ! empty($_GET['numrows'])) { $max = ! empty($_GET['numrows']) ? $_GET['numrows'] : $tsret['max']; $builder->wpquery_pagination_max($query, $max); } $paginationArguments = $builder->getPaginationArguments(); if (! empty($_REQUEST[$paginationArguments['sort_arg']])) { $query->setOrder($_REQUEST[$paginationArguments['sort_arg']]); } PluginsLibUtil::handleDownload($query, $index, $matches); /* set up facets/aggregations */ $facetsBuilder = new Search_Query_FacetWikiBuilder(); $facetsBuilder->apply($matches); if ($facetsBuilder->getFacets()) { $facetsBuilder->build($query, $unifiedsearchlib->getFacetProvider()); } if ($multisearch) { $originalQueries[$id] = $query; $query->search($index, (string)$id); } elseif ($renderMultisearch) { $result = $query->search($index, '', $multisearchResults[$params['multisearchid']]); } else { $result = $query->search($index); } } // END: Foreach loop of queries if ($multisearch) { // Now that all the queries are in the stack, the actual search can be performed $multisearchResults = $index->triggerMultisearch(); if ($params['cache'] == 'y') { $cachelib->cacheItem($cacheName, serialize([$now, $multisearchResults]), $cacheType); } // No output is required when saving results of multisearch for later rendering on page by other LIST plugins return ''; } } // END: Perform searching $result->setId('wplist-' . $i); $resultBuilder = new Search_ResultSet_WikiBuilder($result); $resultBuilder->setPaginationArguments($paginationArguments); $resultBuilder->apply($matches); $builder = new Search_Formatter_Builder(); $builder->setPaginationArguments($paginationArguments); $builder->setId('wplist-' . $i); $builder->setCount($result->count()); $builder->setTsOn($tsret['tsOn']); $builder->apply($matches); $result->setTsSettings($builder->getTsSettings()); $formatter = $builder->getFormatter(); $result->setTsOn($tsret['tsOn']); if (! empty($params['resultCallback']) && is_callable($params['resultCallback'])) { return $params['resultCallback']($formatter->getPopulatedList($result), $formatter); } $out = $formatter->format($result); if ($params['cache'] == 'y') { $cachelib->cacheItem($cacheName, serialize([$now, $out]), $cacheType); } return $out; }