Files
idpan.poznan.pl/components/com_jce/editor/extensions/search/link.php
2026-02-08 21:16:11 +01:00

460 lines
14 KiB
PHP

<?php
/**
* @copyright Copyright (c) 2009-2022 Ryan Demmer. All rights reserved
* @license GNU/GPL 2 or later - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* JCE is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses
*/
defined('JPATH_PLATFORM') or die;
use Joomla\String\StringHelper;
class WFLinkSearchExtension extends WFSearchExtension
{
private $enabled = array();
protected function loadDefaultAdapter($plugin)
{
$app = JFactory::getApplication();
// create component name from plugin - special case for "contacts"
$component = ($plugin == 'contacts') ? 'com_contact' : 'com_' . $plugin;
// check for associated component
if (!JComponentHelper::isEnabled($component)) {
return;
}
$adapter = __DIR__ . '/adapter/' . $plugin . '/' . $plugin . '.php';
if (!is_file($adapter)) {
return;
}
require_once $adapter;
// create classname, eg: PlgSearchContent
$className = 'PlgWfSearch' . ucfirst($plugin);
if (!class_exists($className)) {
return;
}
// simple plugin config
$config = array(
'name' => $plugin,
'type' => 'search',
'params' => array(
'search_limit' => 10
),
);
// Joomla 4
if (method_exists($app, 'getDispatcher')) {
$dispatcher = $app->getDispatcher();
$instance = new $className($dispatcher, (array) $config);
$instance->registerListeners();
} else {
$dispatcher = JEventDispatcher::getInstance();
$instance = new $className($dispatcher, (array) $config);
}
$this->enabled[] = $plugin;
}
/**
* Constructor activating the default information of the class.
*/
public function __construct()
{
parent::__construct();
$request = WFRequest::getInstance();
$request->setRequest(array($this, 'doSearch'));
$request->setRequest(array($this, 'getAreas'));
$wf = WFEditorPlugin::getInstance();
// get plugins
$plugins = $wf->getParam('search.link.plugins', array());
// set defaults if empty
if (empty($plugins)) {
$plugins = array('categories', 'contacts', 'content', 'tags');
}
// list core adapters
$adapters = array('categories', 'contacts', 'content', 'tags', 'weblinks');
// check and load external search plugins
foreach ($plugins as $plugin) {
// process core search plugins
if (in_array($plugin, $adapters)) {
$this->loadDefaultAdapter($plugin);
continue;
}
// plugin must be enabled
if (!JPluginHelper::isEnabled('search', $plugin)) {
continue;
}
// check plugin imports correctly - plugin may have a db entry, but is missing files
if (JPluginHelper::importPlugin('search', $plugin)) {
$this->enabled[] = $plugin;
}
}
}
public function display()
{
parent::display();
$document = WFDocument::getInstance();
$document->addScript(array('link'), 'extensions.search.js');
$document->addStylesheet(array('link'), 'extensions.search.css');
}
public function isEnabled()
{
$wf = WFEditorPlugin::getInstance();
return (bool) $wf->getParam('search.link.enable', 1) && !empty($this->enabled);
}
/**
* Method to get the search areas.
*/
public function getAreas()
{
$app = JFactory::getApplication('site');
$areas = array();
$results = array();
$searchareas = $app->triggerEvent('onContentSearchAreas');
foreach ($searchareas as $area) {
if (is_array($area)) {
$areas = array_merge($areas, $area);
}
}
foreach ($areas as $k => $v) {
$results[$k] = JText::_($v);
}
return $results;
}
/*
* Truncate search text
* This method uses portions of components/com_finder/views/search/tmpl/default_result.php
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
*/
private function truncateText($text, $searchword)
{
// Calculate number of characters to display around the result
$term_length = StringHelper::strlen($searchword);
$lang = JFactory::getLanguage();
$desc_length = $lang->getSearchDisplayedCharactersNumber();
$pad_length = $term_length < $desc_length ? (int) floor(($desc_length - $term_length) / 2) : 0;
// Find the position of the search term
$pos = $term_length ? StringHelper::strpos(StringHelper::strtolower($text), StringHelper::strtolower($searchword)) : false;
// Find a potential start point
$start = ($pos && $pos > $pad_length) ? $pos - $pad_length : 0;
// Find a space between $start and $pos, start right after it.
$space = StringHelper::strpos($text, ' ', $start > 0 ? $start - 1 : 0);
$start = ($space && $space < $pos) ? $space + 1 : $start;
$text = JHtml::_('string.truncate', StringHelper::substr($text, $start), $desc_length, false);
return $text;
}
/*
* Prepare search content by clean and truncating
* This method uses portions of SearchHelper::prepareSearchContent from administrator/components/com_search/helpers/search.php
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
*/
public function prepareSearchContent($text, $searchword)
{
// Replace line breaking tags with whitespace.
$text = preg_replace("'<(br[^/>]*?/|hr[^/>]*?/|/(div|h[1-6]|li|p|td))>'si", ' ', $text);
// clean text
$text = htmlspecialchars(strip_tags($text));
// remove shortcode
$text = preg_replace('#{.+?}#', '', $text);
// truncate text based around searchword
$text = $this->truncateText($text, $searchword);
// highlight searchword
$text = preg_replace('#\b(' . preg_quote($searchword, '#') . ')\b#i', '<mark>$1</mark>', $text);
return $text;
}
/*
* Render Search fields
* This method uses portions of SearchViewSearch::display from components/com_search/views/search/view.html.php
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
*/
public function render()
{
if (!$this->isEnabled()) {
return '';
}
// built select lists
$orders = array();
$orders[] = JHtml::_('select.option', 'newest', JText::_('WF_SEARCH_NEWEST_FIRST'));
$orders[] = JHtml::_('select.option', 'oldest', JText::_('WF_SEARCH_OLDEST_FIRST'));
$orders[] = JHtml::_('select.option', 'popular', JText::_('WF_SEARCH_MOST_POPULAR'));
$orders[] = JHtml::_('select.option', 'alpha', JText::_('WF_SEARCH_ALPHABETICAL'));
$orders[] = JHtml::_('select.option', 'category', JText::_('WF_CATEGORY'));
$lists = array();
$lists['ordering'] = JHtml::_('select.genericlist', $orders, 'ordering', 'class="inputbox"', 'value', 'text');
$searchphrases = array();
$searchphrases[] = JHtml::_('select.option', 'all', JText::_('WF_SEARCH_ALL_WORDS'));
$searchphrases[] = JHtml::_('select.option', 'any', JText::_('WF_SEARCH_ANY_WORDS'));
$searchphrases[] = JHtml::_('select.option', 'exact', JText::_('WF_SEARCH_EXACT_PHRASE'));
$lists['searchphrase'] = JHtml::_('select.radiolist', $searchphrases, 'searchphrase', '', 'value', 'text', 'all');
$view = $this->getView(array('name' => 'search', 'layout' => 'search'));
$view->searchareas = self::getAreas();
$view->lists = $lists;
$view->display();
}
private static function getSearchAreaFromUrl($url)
{
$query = parse_url($url, PHP_URL_QUERY);
if (empty($query)) {
return "";
}
parse_str($query, $values);
if (!array_key_exists('option', $values)) {
return "";
}
$language = JFactory::getLanguage();
$option = $values['option'];
// load system language file
$language->load($option . '.sys', JPATH_ADMINISTRATOR);
$language->load($option, JPATH_ADMINISTRATOR);
return JText::_($option);
}
/**
* Process search.
*
* @param type $query Search query
* @return array Search Results
*
* This method uses portions of SearchController::search from components/com_search/controller.php
*
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved
*/
public function doSearch($query)
{
$wf = WFEditorPlugin::getInstance();
$results = array();
if (empty($query)) {
return $results;
}
// search area
$area = null;
// available search areas
$areas = $this->getAreas();
// query using a specific plugin
if (strpos($query, ':') !== false) {
preg_match('#^(' . implode('|', $areas) . ')\:(.+)#', $query, $matches);
if ($matches) {
$area = array($matches[1]);
$query = $matches[2];
}
}
if (!class_exists('JSite')) {
// Load JSite class
JLoader::register('JSite', JPATH_SITE . '/includes/application.php');
}
$app = JFactory::getApplication('site');
$filter = JFilterInput::getInstance();
$router = $app::getRouter('site');
// get router mode
$sef = (int) $wf->getParam('search.link.sef_url', 0);
$limit = (int) $wf->getParam('search.link.limit', 50);
// set router off so a raw url is returned by the Search plugin
if ($router) {
//$router->setMode(0);
}
// slashes cause errors, <> get stripped anyway later on. # causes problems.
$searchword = trim(str_replace(array('#', '>', '<', '\\'), '', $filter->clean($query)));
$ordering = null;
$searchphrase = 'all';
// if searchword enclosed in double quotes, strip quotes and do exact match
if (substr($searchword, 0, 1) == '"' && substr($searchword, -1) == '"') {
$searchword = substr($searchword, 1, -1);
$searchphrase = 'exact';
}
$searchphrase = $app->input->post->getWord('searchphrase', $searchphrase);
// get passed through ordering
$ordering = $app->input->post->getWord('ordering', $ordering);
// get passed through area
$area = $app->input->post->getCmd('areas', (array) $area);
if (empty($area)) {
$area = null;
}
// trigger search on loaded plugins
$searches = $app->triggerEvent('onContentSearch', array(
$searchword,
$searchphrase,
$ordering,
$area,
));
$rows = array();
foreach ($searches as $search) {
$rows = array_merge((array) $rows, (array) $search);
}
// get first 10
$rows = array_slice($rows, 0, $limit);
$areas = array();
for ($i = 0, $count = count($rows); $i < $count; ++$i) {
$row = &$rows[$i];
if (empty($row->href) || empty($row->title)) {
continue;
}
$area = isset($row->section) ? $row->section : self::getSearchAreaFromUrl($row->href);
if (!isset($areas[$area])) {
$areas[$area] = array();
}
$result = new StdClass;
if ($searchphrase == 'exact') {
$searchwords = array($searchword);
$needle = $searchword;
} else {
$searchworda = preg_replace('#\xE3\x80\x80#s', ' ', $searchword);
$searchwords = preg_split("/\s+/u", $searchworda);
$needle = $searchwords[0];
}
// get anchors if any...
$row->anchors = self::getAnchors($row->text);
// prepare and truncate search text
$row->text = $this->prepareSearchContent($row->text, $needle);
// remove base url
if (JURI::base(true) && strpos($row->href, JURI::base(true)) !== false) {
$row->href = substr_replace($row->href, '', 0, strlen(JURI::base(true)) + 1);
}
// remove the alias or ItemId from a link
$row->href = self::route($row->href);
$result->title = $row->title;
$result->text = $row->text;
$result->link = $row->href;
if (!empty($row->anchors)) {
$result->anchors = $row->anchors;
}
$areas[$area][] = $result;
}
if (!empty($areas)) {
$results[] = $areas;
}
return $results;
}
private static function route($url)
{
$wf = WFEditorPlugin::getInstance();
if ((bool) $wf->getParam('search.link.remove_alias', 0)) {
$url = WFLinkHelper::route($url);
}
// remove Itemid if "home"
$url = WFLinkHelper::removeHomeItemId($url);
// remove Itemid if set
if ((bool) $wf->getParam('search.link.itemid', 1) === false) {
$url = WFLinkHelper::removeItemId($url);
}
return $url;
}
private static function getAnchors($content)
{
preg_match_all('#<a([^>]+)(name|id)="([a-z]+[\w\-\:\.]*)"([^>]*)>#i', $content, $matches, PREG_SET_ORDER);
$anchors = array();
if (!empty($matches)) {
foreach ($matches as $match) {
if (strpos($match[0], 'href') === false) {
$anchors[] = $match[3];
}
}
}
return $anchors;
}
}