[BUG]: RouteHelper not producing correct urls #1208

Closed
opened 2025-02-22 18:04:08 +00:00 by ConfidantCommunications · 2 comments

What Happened?

I attempt to create a listing similar to the Looks listing in the demo. Instead of the site view named Looking I have Worldview.
So in my RouteHelper, I have a getWorldviewRoute method instead of a getLookingRoute method.

I attempt to generate my urls list in the same way, calling RouteHelper::getWorldviewRoute($item->slug);
It is generating an error Illegal offset type in isset or empty(JROOT/libraries/src/Menu/AbstractMenu.php:164 )

It seems that this problem is caused from using view=worldview in the link instead of task=worldview. To confirm this I can see a proper page using this url with task instead of view:

http://localhost:8080/index.php?option=com_threedbug&task=worldview&id=1

The other issue is that even when changing view to task in the RouteHelper, the URLs are still not correct once the Route class converts them. They output as one of the following, depending on if I have url rewriting enabled:
http://localhost:8080/worlds?task=worldview&id=1:one1
http://localhost:8080/index.php/worlds?task=worldview&id=1:one1
http://localhost:8080/index.php?option=com_threedbug&task=worldview&id=1:one1&Itemid=1118

Steps to reproduce the Bug

Set up a new component using similar site & admin views to Look, Looks, Looking.
Ensure the site view has a similar listing method using RouteHelper.
Create a menu item to the Looks equivalent.

Which Joomla version are you compiling in?

5.2.4

Which PHP version are you compiling in?

8.2.23

Which Joomla versions are you targeting?

5

Which PHP version are you targeting?

8.2.23

Which Web server is JCB running on?

Joomla Docker image. Apache 2

Which Relational Database is JCB running on?

mysqlnd 8.2.23

Which OS is JCB running on?

Alpine linux

Which JCB version are you using?

5.1.0

Where in JCB did this issue occur?

No response

On which browsers did you encounter the issue?

No response

Additional Comments

No response

### What Happened? I attempt to create a listing similar to the Looks listing in the demo. Instead of the site view named Looking I have Worldview. So in my RouteHelper, I have a getWorldviewRoute method instead of a getLookingRoute method. I attempt to generate my urls list in the same way, calling RouteHelper::getWorldviewRoute($item->slug); It is generating an error Illegal offset type in isset or empty(JROOT/libraries/src/Menu/AbstractMenu.php:164 ) It seems that this problem is caused from using view=worldview in the link instead of task=worldview. To confirm this I can see a proper page using this url with task instead of view: http://localhost:8080/index.php?option=com_threedbug&task=worldview&id=1 The other issue is that even when changing view to task in the RouteHelper, the URLs are still not correct once the Route class converts them. They output as one of the following, depending on if I have url rewriting enabled: http://localhost:8080/worlds?task=worldview&id=1:one1 http://localhost:8080/index.php/worlds?task=worldview&id=1:one1 http://localhost:8080/index.php?option=com_threedbug&task=worldview&id=1:one1&Itemid=1118 ### Steps to reproduce the Bug Set up a new component using similar site & admin views to Look, Looks, Looking. Ensure the site view has a similar listing method using RouteHelper. Create a menu item to the Looks equivalent. ### Which Joomla version are you compiling in? 5.2.4 ### Which PHP version are you compiling in? 8.2.23 ### Which Joomla versions are you targeting? 5 ### Which PHP version are you targeting? 8.2.23 ### Which Web server is JCB running on? Joomla Docker image. Apache 2 ### Which Relational Database is JCB running on? mysqlnd 8.2.23 ### Which OS is JCB running on? Alpine linux ### Which JCB version are you using? 5.1.0 ### Where in JCB did this issue occur? _No response_ ### On which browsers did you encounter the issue? _No response_ ### Additional Comments _No response_
ConfidantCommunications added the
Bug
label 2025-02-22 18:04:08 +00:00
Owner

Open the new router builder area:
image.png

Choose the code options, and write your own implementation... it can be as complex as:

Constructor Before Parent

Super___640b5352_fb09_425f_a26e_cd44eda03f15___Power::setOption('com_[[[component]]]');
$this->defaultTranslation = $this->params->get('default_translation', 'kjv');

// Define views and register them
$appView = new RouterViewConfiguration('app');
$this->registerView($appView);

$searchView = new RouterViewConfiguration('search');
$this->registerView($searchView);

$tagView = new RouterViewConfiguration('tag');
$this->registerView($tagView);

$openaiView = new RouterViewConfiguration('openai');
$this->registerView($openaiView);

Methods

/**
 * The default translation
 *
 * @var   string|null
 * @since   3.3
 */
private ?string $defaultTranslation = null;

/**
 * Build the route for the com_[[[component]]] component
 *
 * @param   array  &$query  An array of URL arguments
 *
 * @return  array  The URL arguments to use to assemble the subsequent URL.
 * @since   3.3
 */
public function build(&$query)
{
	$segments = [];
	$view = $query['view'] ?? 'app';

	if ($view === 'search')
	{
		$segments[0] = 'search';
		$segments[1] = $query['t'] ?? $query['version'] ?? $query['translation'] ?? $this->defaultTranslation;

		$criteria = $query['criteria'] ?? null;
		if ($criteria === null)
		{
			$word = $query['words'] ?? 1;
			$match = $query['match'] ?? 2;
			$case = $query['case'] ?? 1;
			$target = $query['target'] ?? 1000;

			$criteria = "{$word}-{$match}-{$case}-{$target}";
		}

		if (strpos($criteria, '-') !== false)
		{
			$word = $this->criteriaString(
				$criteria, 0,
				[
					'allwords' => 'allwords',
					'anywords' => 'anywords',
					'exactwords' => 'exactwords',
					1 => 'allwords',
					2 => 'anywords',
					3 => 'exactwords'
				], 'allwords');

			$match = $this->criteriaString(
				$criteria, 1,
				[
					'exactmatch' => 'exactmatch',
					'partialmatch' => 'partialmatch',
					1 => 'exactmatch',
					2 => 'partialmatch'
				], 'partialmatch');

			$case = $this->criteriaString(
				$criteria, 2,
				[
					'caseinsensitive' => 'caseinsensitive',
					'casesensitive' => 'casesensitive',
					1 => 'caseinsensitive',
					2 => 'casesensitive'
				], 'caseinsensitive');

			$target = $this->criteriaString(
				$criteria, 3,
				[
					'allbooks' => 'allbooks',
					'oldtestament' => 'oldtestament',
					'newtestament' => 'newtestament',
					1000 => 'allbooks',
					2000 => 'oldtestament',
					3000 => 'newtestament',
					4000 => 'booknames'
				], 'allbooks');

			if ($target === 'booknames')
			{
				$target = $this->criteriaBook($criteria, 3);
			}

			$segments[2] = "{$word}-{$match}-{$case}-{$target}";
		}
		else
		{
			$segments[2] = 'allwords-partialmatch-caseinsensitive-allbooks';
		}

		$segments[3] = $query['search'] ?? $query['s'] ?? '';
	}
	elseif ($view === 'openai')
	{
		$segments[0] = 'openai';
		$segments[1] = $query['guid'] ?? '';
		$segments[2] = $query['t'] ?? $query['version'] ?? $query['translation'] ?? $this->defaultTranslation;
		$segments[3] = $query['book'] ?? '';
		$segments[4] = $query['chapter'] ?? '';
		$segments[5] = $query['verse'] ?? '';
		$segments[6] = $query['words'] ?? '';

		// remove what is not there
		if (empty($segments[5]))
		{
			unset($segments[5]);
		}
		if (empty($segments[6]))
		{
			unset($segments[6]);
		}
	}
	elseif ($view === 'api')
	{
		$segments[0] = 'api';
		$segments[1] = $query['t'] ?? $query['version'] ?? $query['translation'] ?? $this->defaultTranslation;
		$segments[2] = $query['get'] ?? '';
	}
	elseif ($view === 'tag')
	{
		$segments[0] = 'tag';
		$segments[1] = $query['t'] ?? $query['version'] ?? $query['translation'] ?? $this->defaultTranslation;
		$segments[2] = $query['guid'] ?? '';
		if (!empty($query['guid']) && ($tag_name = Super___db87c339_5bb6_4291_a7ef_2c48ea1b06bc___Power::var('tag', $query['guid'], 'guid', 'name')) !== null)
		{
			$tag_name = preg_replace('/[^\p{L}\p{N}\s]/u', '', $tag_name);
			$segments[3] = urlencode($tag_name);
		}
	}
	else
	{
		$segments[0] = $query['t'] ?? $query['translation'] ?? $this->defaultTranslation;
		$book = $query['ref'] ?? $query['b'] ?? $query['book'] ?? '';
		if (is_numeric($book) && $book > 0)
		{
			$book = $this->getBookName((int) $book, $segments[0]);
		}
		$segments[1] = $book;

		$chapter = $query['chapter'] ?? $query['c'] ?? '';
		if (strlen($chapter) && is_numeric($chapter))
		{
			$segments[2] = $chapter;
		}

		$verse = $query['verse'] ?? $query['v'] ?? '';
		if (strlen($verse))
		{
			$segments[3] = $verse;
		}
	}

	// remove all values used
	unset($query['view']);
	unset($query['ref']);
	unset($query['t']);
	unset($query['version']);
	unset($query['translation']);
	unset($query['b']);
	unset($query['book']);
	unset($query['target_book']);
	unset($query['c']);
	unset($query['chapter']);
	unset($query['verse']);
	unset($query['v']);
	unset($query['criteria']);
	unset($query['words']);
	unset($query['match']);
	unset($query['case']);
	unset($query['target']);
	unset($query['search']);
	unset($query['guid']);

	return $segments;
}

/**
 * Parse the segments of a URL.
 *
 * @param   array  &$segments  The segments of the URL to parse.
 *
 * @return  array  The URL attributes to be used by the application.
 * @since   3.3
 */
public function parse(&$segments)
{
	$vars = [];
	$vars['view'] = 'app';

	$key = 0;
	$vars['t'] = $segments[$key] ?? '';

	// if first value is a valid translation, we are on the app page
	if ($this->validTranslation($vars['t']))
	{
		$key++;
		$this->setAppVars($vars, $key, $segments);
	}
	// if the first value is search we are on the search page
	elseif ($vars['t'] === 'search')
	{
		$key++;
		$this->setSearchVars($vars, $key, $segments);
	}
	// if the first api is search we are on the api page
	elseif ($vars['t'] === 'api')
	{
		$vars['view'] = 'api';

		// set the translation
		$key++;
		$this->setTranslation($vars, $key, $segments);

		$vars['ref'] = $segments[$key] ?? '';
	}
	// if the first api is search we are on the api page
	elseif ($vars['t'] === 'api')
	{
		$vars['view'] = 'api';

		// set the translation
		$key++;
		$this->setTranslation($vars, $key, $segments);

		$vars['ref'] = $segments[$key] ?? '';
	}
	// if the first openai is search we are on the openai page
	elseif ($vars['t'] === 'openai')
	{
		$vars['view'] = 'openai';

		// set the AI guid
		$key++;
		$vars['guid'] = $segments[$key] ?? '';

		// set the translation
		$key++;
		$this->setTranslation($vars, $key, $segments);

		// set the targets
		$vars['book'] = $segments[$key] ?? '';
		$key++;
		$vars['chapter'] = $segments[$key] ?? '';
		$key++;
		$vars['verse'] = $segments[$key] ?? '';
		$key++;
		$vars['words'] = $segments[$key] ?? '';
	}
	// if the first tag is search we are on the tag page
	elseif ($vars['t'] === 'tag')
	{
		$vars['view'] = 'tag';

		// set the translation
		$key++;
		$this->setTranslation($vars, $key, $segments);

		// set the Tag guid
		$vars['guid'] = $segments[$key] ?? '';
	}
	// if the first tag is none of the above, we are probably on the app page
	else
	{
		$vars['t'] = $this->defaultTranslation;
		$this->setAppVars($vars, $key, $segments);
	}

	// always clear the segment (crazy right)
	$segments = [];

	return $vars;
}

/**
 * Set the app variables
 *
 * @param   array  $vars     The active variables found
 * @param   int    $key      The active key state of the segment array pointer
 * @param   array  $segments The URL segments
 *
 * @return  void
 * @since   3.3
 */
protected function setAppVars(array &$vars, int &$key, array $segments): void
{
	$value = $segments[$key] ?? null;

	$book_number = 0;
	$book_name = $this->getBook($value, $book_number, $vars['t']);

	if ($book_name !== null && $book_number > 0)
	{
		$vars['ref'] = $book_name;
		$vars['book'] = $book_number;
		$key++;

		$chapter_number = $this->getChapter($vars, $key, $segments);
		$has_verses = $this->getVerses($vars, $key, $segments);
	}

	$this->checkAndRedirectApiCall(
		$vars,
		$key,
		$segments,
		$book_number ?? null,
		$chapter_number ?? null,
		$has_verses ?? false
	);
}

/**
 * Set the Search Variables
 *
 * @param   array  $vars     The active variables found
 * @param   int    $key      The active key state of the segment array pointer
 * @param   array  $segments The URL segments
 *
 * @return  void
 * @since   3.3
 */
protected function setSearchVars(array &$vars, int &$key, array $segments): void
{
	$vars['view'] = 'search';

	// set the translation
	$this->setTranslation($vars, $key, $segments);

	// set the criteria for the search
	$this->setSearchCriteria($vars, $key, $segments);

	// get the search value
	$vars['search'] = $segments[$key] ?? '';

	// check if this is an API call
	if (!empty($vars['search']))
	{
		$key++;

		$api_call = $segments[$key] ?? 'not_api_call';
		if ($api_call === 'get_bible.json')
		{
			echo '<pre>';
			var_dump('We have an API call!');
			var_dump($vars);
			exit;
		}
	}
}

/**
 * Retrieve book based on the value provided
 *
 * @param   mixed         $value
 * @param   int          &$bookNumber
 * @param   string|null   $translation    The book translation.
 *
 * @return  string|null
 * @since   3.3
 */
private function getBook($value, int &$bookNumber, ?string $translation = null): ?string
{
	if (is_numeric($value))
	{
		$bookNumber = $value;

		return $this->getBookName((int) $value, $translation);
	}
	elseif (!empty($value) && ($bookNumber = $this->getBookNumber($value)) !== null)
	{
		return $value;
	}

	return null;
}

/**
 * Retrieve chapter from the segments
 *
 * @param   array  &$vars
 * @param   int    &$key
 * @param   array  $segments
 *
 * @return  int|null
 * @since   3.3
 */
private function getChapter(array &$vars, int &$key, array $segments): ?int
{
	$value = $segments[$key] ?? null;
	if (!empty($value) && is_numeric($value) && $value > 0)
	{
		$vars['ref'] .= ' ' . $value;
		$vars['chapter'] = $value;
		$key++;

		return (int) $value;
	}

	return null;
}

/**
 * Retrieve verses from the segments
 *
 * @param   array  &$vars
 * @param   int    &$key
 * @param   array  $segments
 *
 * @return  bool
 * @since   3.3
 */
private function getVerses(array &$vars, int &$key, array $segments): bool
{
	$value = $segments[$key] ?? null;
	if (!empty($value) && (is_numeric($value) || strpos($value, '-') !== false))
	{
		$vars['ref'] .= ':' . $value;
		$vars['verse'] = $value;
		$key++;

		return true;
	}

	return false;
}

/**
 * Check if the request is an API call and redirect if necessary
 *
 * @param   array     $vars
 * @param   int       &$key
 * @param   array     $segments
 * @param   int|null  $bookNumber
 * @param   int|null  $chapterNumber
 * @param   bool      $hasVerses
 *
 * @return  void
 * @since   3.3
 */
private function checkAndRedirectApiCall(
	array $vars, int $key, array $segments,
	?int $bookNumber, ?int $chapterNumber, bool $hasVerses): void
{
	$apiCall = $segments[$key] ?? 'not_api_call';
	if ($apiCall === 'get_bible.json')
	{
		if ($hasVerses)
		{
			header("Location: https://query.getbible.net/v2/{$vars['t']}/{$vars['ref']}");
			exit;
		}
		elseif (!empty($bookNumber) && !empty($chapterNumber))
		{
			header("Location: https://api.getbible.net/v2/{$vars['t']}/$bookNumber/$chapterNumber.json");
			exit;
		}
		elseif (!empty($bookNumber))
		{
			header("Location: https://api.getbible.net/v2/{$vars['t']}/$bookNumber.json");
			exit;
		}
		else
		{
			header("Location: https://api.getbible.net/v2/{$vars['t']}.json");
			exit;
		}
	}
}

/**
 * Set The Translation
 *
 * @param   array  $vars     The active variables found
 * @param   int    $key      The active key state of the segment array pointer
 * @param   array  $segments The URL segments
 *
 * @return  void
 * @since   3.3
 */
private function setTranslation(array &$vars, int &$key, array $segments): void
{
	$vars['t'] = $segments[$key] ?? $this->defaultTranslation;

	if ($this->validTranslation($vars['t']))
	{
		$key++;
	}
	else
	{
		$vars['t'] = $this->defaultTranslation;
	}
}

/**
 * Set The Search Criteria
 *
 * @param   array  $vars     The active variables found
 * @param   int    $key      The active key state of the segment array pointer
 * @param   array  $segments The URL segments
 *
 * @return  void
 * @since   3.3
 */
private function setSearchCriteria(array &$vars, int &$key, array $segments): void
{
	// set the criteria values
	$criteria = $segments[$key] ?? null;
	if ($criteria === null || strpos($criteria , '-') === false)
	{
		// the default is greedy
		$criteria = 'allwords-partialmatch-caseinsensitive-allbooks';
	}
	else
	{
		$key++;
	}

	/**
	 * > words (0)
	 * 1 = allwords, 2 = anywords, 3 = exactwords
	 */
	$vars['words'] = $this->criteria(
		$criteria, 0,
		[
			'allwords' => 1,
			'anywords' => 2,
			'exactwords' => 3,
			1 => 1, 2 => 2, 3 => 3
		], 1);

	/**
	 * > match (1)
	 * 1 = exactmatch, 2 = partialmatch
	 */
	$vars['match'] = $this->criteria(
		$criteria, 1,
		[
			'exactmatch' => 1,
			'partialmatch' => 2,
			1 => 1, 2 => 2
		], 2);

	/**
	 * > case (2)
	 * 1 = caseinsensitive, 2 = casesensitive
	 */
	$vars['case'] = $this->criteria(
		$criteria, 2,
		[
			'caseinsensitive' => 1,
			'casesensitive' => 2,
			1 => 1, 2 => 2
		], 1);

	/**
	 * > target (3)
	 * 1000 = all, 2000 = old, 3000 = new, 4000 = book_name
	 */
	$vars['target'] = $this->criteria(
		$criteria, 3,
		[
			'allbooks' => 1000,
			'oldtestament' => 2000,
			'newtestament' => 3000,
			1000 => 1000, 2000 => 2000, 3000 => 3000
		], 4000);

	/**
	 * When we have 4000 we need to get the book name
	 */
	if ($vars['target'] == 4000)
	{
		$vars['target_book'] = $this->criteriaBook($criteria, 3);

		if ($vars['target_book'] === null)
		{
			$vars['target'] = 1000;
		}
	}
}

/**
 * Get the int value of the criteria string
 *
 * @param   string  $value     The criteria string.
 * @param   int     $position  The criteria position.
 * @param   array   $criteria  The criteria target.
 * @param   int     $default   The criteria default.
 *
 * @return  int  The int value of the targeted criteria.
 * @since   3.3
 */
private function criteria(string $value, int $position, array $criteria, int $default): int
{
	if (strpos($value, '-') !== false)
	{
		$array = explode('-', $value);
		if (isset($array[$position]) && isset($criteria[$array[$position]]))
		{
			return $criteria[$array[$position]];
		}
	}

	return $default;
}

/**
 * Get the book value from the criteria string
 *
 * @param   string  $value     The criteria string.
 * @param   int     $position  The criteria position.
 *
 * @return  mixed  the book value
 * @since   3.3
 */
private function criteriaBook(string $value, int $position)
{
	if (strpos($value, '-') !== false)
	{
		$array = explode('-', $value);
		if (isset($array[$position]))
		{
			return $array[$position];
		}
	}

	return null;
}

/**
 * Get the string value of the criteria int
 *
 * @param   string  $value     The criteria string.
 * @param   int     $position  The criteria position.
 * @param   array   $criteria  The criteria target.
 * @param   int     $default   The criteria default.
 *
 * @return  string  The string value of the targeted criteria.
 * @since   3.3
 */
private function criteriaString(string $value, int $position, array $criteria, string $default): string
{
	if (strpos($value, '-') !== false)
	{
		$array = explode('-', $value);
		if (isset($array[$position]) && isset($criteria[$array[$position]]))
		{
			return $criteria[$array[$position]];
		}
	}

	return $default;
}

/**
 * Get a Book number
 *
 * @param   string  $value   The book name.
 *
 * @return  int|null  The book number
 * @since   3.3
 */
private function getBookNumber(string $name): ?int
{
	if (($number = Super___db87c339_5bb6_4291_a7ef_2c48ea1b06bc___Power::var('book', $name, 'name', 'nr')) !== null
		&& $number > 0)
	{
		return $number;
	}

	return null;
}

/**
 * Get a Book name
 *
 * @param   int           $value          The book number.
 * @param   string|null   $translation    The book translation.
 *
 * @return  string|null  The book name
 * @since   3.3
 */
private function getBookName(int $value, ?string $translation = null): ?string
{
	if (!empty($translation) && is_numeric($value) && $value > 0)
	{
		// Create a new query object.
		$query = $this->db->getQuery(true);
		$query->select($this->db->quoteName('name'));
		$query->from($this->db->quoteName('#__getbible_book'));
		$query->where($this->db->quoteName('nr') . ' = '. (int) $value);
		$query->where($this->db->quoteName('abbreviation') . ' = ' . $this->db->quote((string) $translation));
		$this->db->setQuery($query);
		$this->db->execute();
		if ($this->db->getNumRows())
		{
			return $this->db->loadResult();
		}
	}

	if (($name = Super___db87c339_5bb6_4291_a7ef_2c48ea1b06bc___Power::var('book', $value, 'nr', 'name')) !== null)
	{
		return $name;
	}

	return null;
}

/**
 * Validate if this is a active translation
 *
 * @param   string  $value     The criteria string.
 *
 * @return  bool  True if its a valid translation
 * @since   3.3
 */
private function validTranslation(string $value): bool
{
	if (strlen($value) > 0)
	{
		if (($published = Super___db87c339_5bb6_4291_a7ef_2c48ea1b06bc___Power::var('translation', $value, 'abbreviation', 'published')) !== null
			&& $published == 1)
		{
			return true;
		}
	}

	return false;
}

We have added this new area, since this can be to many options to automate correctly.

Open the new router builder area: ![image.png](https://git.vdm.dev/attachments/4e3e7876-06cb-4ef5-9717-553e3ed0218b) Choose the code options, and write your own implementation... it can be as complex as: > Constructor Before Parent ``` Super___640b5352_fb09_425f_a26e_cd44eda03f15___Power::setOption('com_[[[component]]]'); $this->defaultTranslation = $this->params->get('default_translation', 'kjv'); // Define views and register them $appView = new RouterViewConfiguration('app'); $this->registerView($appView); $searchView = new RouterViewConfiguration('search'); $this->registerView($searchView); $tagView = new RouterViewConfiguration('tag'); $this->registerView($tagView); $openaiView = new RouterViewConfiguration('openai'); $this->registerView($openaiView); ``` > Methods ``` /** * The default translation * * @var string|null * @since 3.3 */ private ?string $defaultTranslation = null; /** * Build the route for the com_[[[component]]] component * * @param array &$query An array of URL arguments * * @return array The URL arguments to use to assemble the subsequent URL. * @since 3.3 */ public function build(&$query) { $segments = []; $view = $query['view'] ?? 'app'; if ($view === 'search') { $segments[0] = 'search'; $segments[1] = $query['t'] ?? $query['version'] ?? $query['translation'] ?? $this->defaultTranslation; $criteria = $query['criteria'] ?? null; if ($criteria === null) { $word = $query['words'] ?? 1; $match = $query['match'] ?? 2; $case = $query['case'] ?? 1; $target = $query['target'] ?? 1000; $criteria = "{$word}-{$match}-{$case}-{$target}"; } if (strpos($criteria, '-') !== false) { $word = $this->criteriaString( $criteria, 0, [ 'allwords' => 'allwords', 'anywords' => 'anywords', 'exactwords' => 'exactwords', 1 => 'allwords', 2 => 'anywords', 3 => 'exactwords' ], 'allwords'); $match = $this->criteriaString( $criteria, 1, [ 'exactmatch' => 'exactmatch', 'partialmatch' => 'partialmatch', 1 => 'exactmatch', 2 => 'partialmatch' ], 'partialmatch'); $case = $this->criteriaString( $criteria, 2, [ 'caseinsensitive' => 'caseinsensitive', 'casesensitive' => 'casesensitive', 1 => 'caseinsensitive', 2 => 'casesensitive' ], 'caseinsensitive'); $target = $this->criteriaString( $criteria, 3, [ 'allbooks' => 'allbooks', 'oldtestament' => 'oldtestament', 'newtestament' => 'newtestament', 1000 => 'allbooks', 2000 => 'oldtestament', 3000 => 'newtestament', 4000 => 'booknames' ], 'allbooks'); if ($target === 'booknames') { $target = $this->criteriaBook($criteria, 3); } $segments[2] = "{$word}-{$match}-{$case}-{$target}"; } else { $segments[2] = 'allwords-partialmatch-caseinsensitive-allbooks'; } $segments[3] = $query['search'] ?? $query['s'] ?? ''; } elseif ($view === 'openai') { $segments[0] = 'openai'; $segments[1] = $query['guid'] ?? ''; $segments[2] = $query['t'] ?? $query['version'] ?? $query['translation'] ?? $this->defaultTranslation; $segments[3] = $query['book'] ?? ''; $segments[4] = $query['chapter'] ?? ''; $segments[5] = $query['verse'] ?? ''; $segments[6] = $query['words'] ?? ''; // remove what is not there if (empty($segments[5])) { unset($segments[5]); } if (empty($segments[6])) { unset($segments[6]); } } elseif ($view === 'api') { $segments[0] = 'api'; $segments[1] = $query['t'] ?? $query['version'] ?? $query['translation'] ?? $this->defaultTranslation; $segments[2] = $query['get'] ?? ''; } elseif ($view === 'tag') { $segments[0] = 'tag'; $segments[1] = $query['t'] ?? $query['version'] ?? $query['translation'] ?? $this->defaultTranslation; $segments[2] = $query['guid'] ?? ''; if (!empty($query['guid']) && ($tag_name = Super___db87c339_5bb6_4291_a7ef_2c48ea1b06bc___Power::var('tag', $query['guid'], 'guid', 'name')) !== null) { $tag_name = preg_replace('/[^\p{L}\p{N}\s]/u', '', $tag_name); $segments[3] = urlencode($tag_name); } } else { $segments[0] = $query['t'] ?? $query['translation'] ?? $this->defaultTranslation; $book = $query['ref'] ?? $query['b'] ?? $query['book'] ?? ''; if (is_numeric($book) && $book > 0) { $book = $this->getBookName((int) $book, $segments[0]); } $segments[1] = $book; $chapter = $query['chapter'] ?? $query['c'] ?? ''; if (strlen($chapter) && is_numeric($chapter)) { $segments[2] = $chapter; } $verse = $query['verse'] ?? $query['v'] ?? ''; if (strlen($verse)) { $segments[3] = $verse; } } // remove all values used unset($query['view']); unset($query['ref']); unset($query['t']); unset($query['version']); unset($query['translation']); unset($query['b']); unset($query['book']); unset($query['target_book']); unset($query['c']); unset($query['chapter']); unset($query['verse']); unset($query['v']); unset($query['criteria']); unset($query['words']); unset($query['match']); unset($query['case']); unset($query['target']); unset($query['search']); unset($query['guid']); return $segments; } /** * Parse the segments of a URL. * * @param array &$segments The segments of the URL to parse. * * @return array The URL attributes to be used by the application. * @since 3.3 */ public function parse(&$segments) { $vars = []; $vars['view'] = 'app'; $key = 0; $vars['t'] = $segments[$key] ?? ''; // if first value is a valid translation, we are on the app page if ($this->validTranslation($vars['t'])) { $key++; $this->setAppVars($vars, $key, $segments); } // if the first value is search we are on the search page elseif ($vars['t'] === 'search') { $key++; $this->setSearchVars($vars, $key, $segments); } // if the first api is search we are on the api page elseif ($vars['t'] === 'api') { $vars['view'] = 'api'; // set the translation $key++; $this->setTranslation($vars, $key, $segments); $vars['ref'] = $segments[$key] ?? ''; } // if the first api is search we are on the api page elseif ($vars['t'] === 'api') { $vars['view'] = 'api'; // set the translation $key++; $this->setTranslation($vars, $key, $segments); $vars['ref'] = $segments[$key] ?? ''; } // if the first openai is search we are on the openai page elseif ($vars['t'] === 'openai') { $vars['view'] = 'openai'; // set the AI guid $key++; $vars['guid'] = $segments[$key] ?? ''; // set the translation $key++; $this->setTranslation($vars, $key, $segments); // set the targets $vars['book'] = $segments[$key] ?? ''; $key++; $vars['chapter'] = $segments[$key] ?? ''; $key++; $vars['verse'] = $segments[$key] ?? ''; $key++; $vars['words'] = $segments[$key] ?? ''; } // if the first tag is search we are on the tag page elseif ($vars['t'] === 'tag') { $vars['view'] = 'tag'; // set the translation $key++; $this->setTranslation($vars, $key, $segments); // set the Tag guid $vars['guid'] = $segments[$key] ?? ''; } // if the first tag is none of the above, we are probably on the app page else { $vars['t'] = $this->defaultTranslation; $this->setAppVars($vars, $key, $segments); } // always clear the segment (crazy right) $segments = []; return $vars; } /** * Set the app variables * * @param array $vars The active variables found * @param int $key The active key state of the segment array pointer * @param array $segments The URL segments * * @return void * @since 3.3 */ protected function setAppVars(array &$vars, int &$key, array $segments): void { $value = $segments[$key] ?? null; $book_number = 0; $book_name = $this->getBook($value, $book_number, $vars['t']); if ($book_name !== null && $book_number > 0) { $vars['ref'] = $book_name; $vars['book'] = $book_number; $key++; $chapter_number = $this->getChapter($vars, $key, $segments); $has_verses = $this->getVerses($vars, $key, $segments); } $this->checkAndRedirectApiCall( $vars, $key, $segments, $book_number ?? null, $chapter_number ?? null, $has_verses ?? false ); } /** * Set the Search Variables * * @param array $vars The active variables found * @param int $key The active key state of the segment array pointer * @param array $segments The URL segments * * @return void * @since 3.3 */ protected function setSearchVars(array &$vars, int &$key, array $segments): void { $vars['view'] = 'search'; // set the translation $this->setTranslation($vars, $key, $segments); // set the criteria for the search $this->setSearchCriteria($vars, $key, $segments); // get the search value $vars['search'] = $segments[$key] ?? ''; // check if this is an API call if (!empty($vars['search'])) { $key++; $api_call = $segments[$key] ?? 'not_api_call'; if ($api_call === 'get_bible.json') { echo '<pre>'; var_dump('We have an API call!'); var_dump($vars); exit; } } } /** * Retrieve book based on the value provided * * @param mixed $value * @param int &$bookNumber * @param string|null $translation The book translation. * * @return string|null * @since 3.3 */ private function getBook($value, int &$bookNumber, ?string $translation = null): ?string { if (is_numeric($value)) { $bookNumber = $value; return $this->getBookName((int) $value, $translation); } elseif (!empty($value) && ($bookNumber = $this->getBookNumber($value)) !== null) { return $value; } return null; } /** * Retrieve chapter from the segments * * @param array &$vars * @param int &$key * @param array $segments * * @return int|null * @since 3.3 */ private function getChapter(array &$vars, int &$key, array $segments): ?int { $value = $segments[$key] ?? null; if (!empty($value) && is_numeric($value) && $value > 0) { $vars['ref'] .= ' ' . $value; $vars['chapter'] = $value; $key++; return (int) $value; } return null; } /** * Retrieve verses from the segments * * @param array &$vars * @param int &$key * @param array $segments * * @return bool * @since 3.3 */ private function getVerses(array &$vars, int &$key, array $segments): bool { $value = $segments[$key] ?? null; if (!empty($value) && (is_numeric($value) || strpos($value, '-') !== false)) { $vars['ref'] .= ':' . $value; $vars['verse'] = $value; $key++; return true; } return false; } /** * Check if the request is an API call and redirect if necessary * * @param array $vars * @param int &$key * @param array $segments * @param int|null $bookNumber * @param int|null $chapterNumber * @param bool $hasVerses * * @return void * @since 3.3 */ private function checkAndRedirectApiCall( array $vars, int $key, array $segments, ?int $bookNumber, ?int $chapterNumber, bool $hasVerses): void { $apiCall = $segments[$key] ?? 'not_api_call'; if ($apiCall === 'get_bible.json') { if ($hasVerses) { header("Location: https://query.getbible.net/v2/{$vars['t']}/{$vars['ref']}"); exit; } elseif (!empty($bookNumber) && !empty($chapterNumber)) { header("Location: https://api.getbible.net/v2/{$vars['t']}/$bookNumber/$chapterNumber.json"); exit; } elseif (!empty($bookNumber)) { header("Location: https://api.getbible.net/v2/{$vars['t']}/$bookNumber.json"); exit; } else { header("Location: https://api.getbible.net/v2/{$vars['t']}.json"); exit; } } } /** * Set The Translation * * @param array $vars The active variables found * @param int $key The active key state of the segment array pointer * @param array $segments The URL segments * * @return void * @since 3.3 */ private function setTranslation(array &$vars, int &$key, array $segments): void { $vars['t'] = $segments[$key] ?? $this->defaultTranslation; if ($this->validTranslation($vars['t'])) { $key++; } else { $vars['t'] = $this->defaultTranslation; } } /** * Set The Search Criteria * * @param array $vars The active variables found * @param int $key The active key state of the segment array pointer * @param array $segments The URL segments * * @return void * @since 3.3 */ private function setSearchCriteria(array &$vars, int &$key, array $segments): void { // set the criteria values $criteria = $segments[$key] ?? null; if ($criteria === null || strpos($criteria , '-') === false) { // the default is greedy $criteria = 'allwords-partialmatch-caseinsensitive-allbooks'; } else { $key++; } /** * > words (0) * 1 = allwords, 2 = anywords, 3 = exactwords */ $vars['words'] = $this->criteria( $criteria, 0, [ 'allwords' => 1, 'anywords' => 2, 'exactwords' => 3, 1 => 1, 2 => 2, 3 => 3 ], 1); /** * > match (1) * 1 = exactmatch, 2 = partialmatch */ $vars['match'] = $this->criteria( $criteria, 1, [ 'exactmatch' => 1, 'partialmatch' => 2, 1 => 1, 2 => 2 ], 2); /** * > case (2) * 1 = caseinsensitive, 2 = casesensitive */ $vars['case'] = $this->criteria( $criteria, 2, [ 'caseinsensitive' => 1, 'casesensitive' => 2, 1 => 1, 2 => 2 ], 1); /** * > target (3) * 1000 = all, 2000 = old, 3000 = new, 4000 = book_name */ $vars['target'] = $this->criteria( $criteria, 3, [ 'allbooks' => 1000, 'oldtestament' => 2000, 'newtestament' => 3000, 1000 => 1000, 2000 => 2000, 3000 => 3000 ], 4000); /** * When we have 4000 we need to get the book name */ if ($vars['target'] == 4000) { $vars['target_book'] = $this->criteriaBook($criteria, 3); if ($vars['target_book'] === null) { $vars['target'] = 1000; } } } /** * Get the int value of the criteria string * * @param string $value The criteria string. * @param int $position The criteria position. * @param array $criteria The criteria target. * @param int $default The criteria default. * * @return int The int value of the targeted criteria. * @since 3.3 */ private function criteria(string $value, int $position, array $criteria, int $default): int { if (strpos($value, '-') !== false) { $array = explode('-', $value); if (isset($array[$position]) && isset($criteria[$array[$position]])) { return $criteria[$array[$position]]; } } return $default; } /** * Get the book value from the criteria string * * @param string $value The criteria string. * @param int $position The criteria position. * * @return mixed the book value * @since 3.3 */ private function criteriaBook(string $value, int $position) { if (strpos($value, '-') !== false) { $array = explode('-', $value); if (isset($array[$position])) { return $array[$position]; } } return null; } /** * Get the string value of the criteria int * * @param string $value The criteria string. * @param int $position The criteria position. * @param array $criteria The criteria target. * @param int $default The criteria default. * * @return string The string value of the targeted criteria. * @since 3.3 */ private function criteriaString(string $value, int $position, array $criteria, string $default): string { if (strpos($value, '-') !== false) { $array = explode('-', $value); if (isset($array[$position]) && isset($criteria[$array[$position]])) { return $criteria[$array[$position]]; } } return $default; } /** * Get a Book number * * @param string $value The book name. * * @return int|null The book number * @since 3.3 */ private function getBookNumber(string $name): ?int { if (($number = Super___db87c339_5bb6_4291_a7ef_2c48ea1b06bc___Power::var('book', $name, 'name', 'nr')) !== null && $number > 0) { return $number; } return null; } /** * Get a Book name * * @param int $value The book number. * @param string|null $translation The book translation. * * @return string|null The book name * @since 3.3 */ private function getBookName(int $value, ?string $translation = null): ?string { if (!empty($translation) && is_numeric($value) && $value > 0) { // Create a new query object. $query = $this->db->getQuery(true); $query->select($this->db->quoteName('name')); $query->from($this->db->quoteName('#__getbible_book')); $query->where($this->db->quoteName('nr') . ' = '. (int) $value); $query->where($this->db->quoteName('abbreviation') . ' = ' . $this->db->quote((string) $translation)); $this->db->setQuery($query); $this->db->execute(); if ($this->db->getNumRows()) { return $this->db->loadResult(); } } if (($name = Super___db87c339_5bb6_4291_a7ef_2c48ea1b06bc___Power::var('book', $value, 'nr', 'name')) !== null) { return $name; } return null; } /** * Validate if this is a active translation * * @param string $value The criteria string. * * @return bool True if its a valid translation * @since 3.3 */ private function validTranslation(string $value): bool { if (strlen($value) > 0) { if (($published = Super___db87c339_5bb6_4291_a7ef_2c48ea1b06bc___Power::var('translation', $value, 'abbreviation', 'published')) !== null && $published == 1) { return true; } } return false; } ``` We have added this new area, since this can be to many options to automate correctly.

Thanks. Working from example code often works well for me!

Thanks. Working from example code often works well for me!
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: joomla/Component-Builder#1208
No description provided.