Go to main content

Textpattern CMS support forum

You are not logged in. Register | Login | Help

#685 2020-02-21 13:39:28

uli
Moderator
From: Cologne
Registered: 2006-08-15
Posts: 4,306

Re: gbp_permanent_links

Gil, this plugin has no help text at all, so maybe you missed it does have some tags.


In bad weather I never leave home without wet_plugout, smd_where_used and adi_form_links

Offline

#686 2020-02-21 18:00:49

THE BLUE DRAGON
Member
From: Israel
Registered: 2007-11-16
Posts: 637
Website

Re: gbp_permanent_links

uli wrote #321836:

Gil, this plugin has no help text at all, so maybe you missed it does have some tags.

Thanks, I didn’t know about them, anyway since I personally do not use them then for me now everything seems to work fine, just still not having the option to delete a rule without clearing all of them in the database using the SQL query Bloke provided.

Here’s my “working” code as it may help others too, it returns no errors (even on debugging mode) and does let you create and edit rules:

<?php
// Constants
@define('gbp_save', 'save');
@define('gbp_post', 'post');
@define('gbp_separator', '&~&~&');

// require_plugin() will reset the $txp_current_plugin global
global $txp_current_plugin;
$gbp_current_plugin = $txp_current_plugin;
require_plugin('gbp_admin_library');
$txp_current_plugin = $gbp_current_plugin;

class PermanentLinks extends GBPPlugin
{
	var $preferences = array(
		'show_prefix' => array('value' => 0, 'type' => 'yesnoradio'),
		'show_suffix' => array('value' => 0, 'type' => 'yesnoradio'),
		'omit_trailing_slash' => array('value' => 0 , 'type' => 'yesnoradio'),
		'redirect_mt_style_links' => array('value' => 1 , 'type' => 'yesnoradio'),
		'clean_page_archive_links' => array('value' => 1 , 'type' => 'yesnoradio'),
		'join_pretext_to_pagelinks' => array('value' => 1 , 'type' => 'yesnoradio'),
		'check_pretext_category_context' => array('value' => 0 , 'type' => 'yesnoradio'),
		'check_pretext_section_context' => array('value' => 0 , 'type' => 'yesnoradio'),
		'force_lowercase_urls' => array('value' => 1 , 'type' => 'yesnoradio'),
		'automatically_append_title' => array('value' => 1 , 'type' => 'yesnoradio'),
		'use_cleaver_partial_matches' => array('value' => 1 , 'type' => 'yesnoradio'),
		'permlink_redirect_http_status' => array('value' => '301' , 'type' => 'text_input'),
		'url_redirect_http_status' => array('value' => '302' , 'type' => 'text_input'),
		'text_and_regex_segment_scores' => array('value' => '0' , 'type' => 'text_input'),
		'debug' => array('value' => 0, 'type' => 'yesnoradio'),
	);
	var $matched_permlink = array();
	var $partial_matches = array();
	var $cleaver_partial_match;
	var $buffer_debug = array();

	function preload () {
		new PermanentLinksListTabView('list', 'list', $this);
		new PermanentLinksBuildTabView('build', 'build', $this);
		new GBPPreferenceTabView($this);
	}

	function main () {
		require_privs($this->event);
	}

	function get_all_permlinks ($sort = 0, $exclude = array()) {
		static $rs;
		if (!isset($rs))
			$rs = safe_column(
				"REPLACE(name, '{$this->plugin_name}_', '') AS id", 'txp_prefs',
				"`event` = '{$this->event}' AND `name` REGEXP '^{$this->plugin_name}_.{13}$'"
			);

		if (@txpinterface == 'public')
			$this->debug('Loading permlinks from db');
		$i = 0;

		$permlinks = array();
		foreach ($rs as $id) {
			$pl = $this->get_permlink($id);

			// Don't try and load permalink rules from the new version
			if (!isset($pl['components']))
				continue;

			if (count($exclude) > 0)
				foreach ($pl['components'] as $pl_c) {
					if (is_array($exclude) && in_array($pl_c['type'], $exclude))
						continue 2;
					if (is_string($exclude) && $pl_c['type'] === $exclude)
						continue 2;
				}

			$permlinks[$id] = $pl;

			if ($sort)
				$precedence[$id] = $permlinks[$id]['settings']['pl_precedence'];
		}

		// If more than one permanent link, sort by their precedence value.
		if ($sort && count($permlinks) > 1)
			array_multisort($precedence, SORT_DESC, $permlinks);

		if (@txpinterface == 'public') {
			foreach ($permlinks as $pl)
				$this->debug($pl['settings']['pl_precedence'].': '.$pl['settings']['pl_name'].' ('.$pl['settings']['pl_preview'].')');
		}

		return $permlinks;
	}

	function get_permlink ($id) {
		$permlink = $this->pref($id);
		return is_array($permlink) ? $permlink : array();
	}

	function remove_permlink ($id) {
		$permlink = $this->get_permlink($id);
		safe_delete('txp_prefs', "`event` = '{$this->event}' AND `name` LIKE '{$this->plugin_name}_{$id}%'");
		return $permlink['settings']['pl_name'];
	}

	function _feed_entry () {
		static $set;
		if (!isset($set)) {
			// We're in a feed force debugging off.
			$this->preferences['debug']['value'] = $GLOBALS['prefs'][$this->plugin_name.'_debug'] = 0;

			$this->set_permlink_mode(true);
			$set = true;
		}
	}

	function _textpattern () {
		global $plugins_ver, $pretext, $prefs, $plugin_callback;

		$this->debug('Plugin: '.$this->plugin_name.' - '.$plugins_ver[$this->plugin_name]);
		$this->debug('Function: '.__FUNCTION__.'()');

		// URI
		$req = $pretext['req'];
		$req = preg_replace('%\?[^\/]+$%', '', $req);
		$this->debug('Request URI: '.$req);
		$uri = explode('/', trim($req, '/'));

		// The number of components comes in useful when determining the best partial match.
		$uri_component_count = count($uri);

		// Permanent links
		$permlinks = $this->get_all_permlinks(1);

		// Force Textpattern and tags to use messy URLs - these are easier to
		// find in regex
		$this->set_permlink_mode();

		if (count($permlinks)) {

			// We also want to match the front page of the site (for page numbers / feeds etc..).
			// Add a permlinks rule which will do that.
			$permlinks['default'] = array(
				'components' => array(),
				'settings' => array(
					'pl_name' => 'gbp_permanent_links_default', 'pl_precedence' => '', 'pl_preview' => '/',
					'con_section' => '', 'con_category' => '', 'des_section' => '', 'des_category' => '',
					'des_permlink' => '', 'des_feed' => '', 'des_location' => '', 'des_page' => ''
			));

			// Extend the pretext_replacement scope outside the foreach permlink loop
			$pretext_replacement = NULL;

			foreach($permlinks as $id => $pl) {
				// Extract the permlink settings
				$pl_settings = $pl['settings'];
				extract($pl_settings);

				$this->debug('Permlink name: '.$pl_name);
				$this->debug('Permlink id: '.$id);
				$this->debug('Preview: '.$pl_preview);

				$pl_components = $pl['components'];

				// URI components
				$uri_components = $uri;

				$this->debug('PL component count: '.count($pl_components));
				$this->debug('URL component count: '.count($uri_components));

				$date = false; $title_page_feed = false;
				foreach($pl_components as $pl_c)
					// Are we expecting a date component? If so the number of pl and uri components won't match
					if ($pl_c['type'] == 'date')
						$date = true;
					// Do we have either a title, page or a feed component?
					else if (in_array($pl_c['type'], array('title', 'page', 'feed')))
						$title_page_feed = true;

				if (!$title_page_feed)
					// If there isn't a title, page or feed component then append a special type for cleaver partial matching
					$pl_components[] = array('type' => 'title_page_feed', 'prefix' => '', 'suffix' => '', 'regex' => '', 'text' => '');

				// Exit early if there are more URL components than PL components,
				// taking into account whether there is a data component
				if (!$uri_components[0] || count($uri_components) > count($pl_components) + ($date ? 2 : 0)) {
					$this->debug('More URL components than PL components');
					continue;
				}

				// Reset pretext_replacement as we are about to start another comparison
				$pretext_replacement = array('permlink_id' => $id);

				// Reset the article context string
				$context = array();
				unset($context_str);
				if (!empty($des_section))
					$context[] = "`Section` = '$des_section'";
				if (!empty($des_category))
					$context[] = "(`Category1` = '$des_category' OR `Category2` = '$des_category')";
				$context_str = (count($context) > 0 ? 'and '.join(' and ', $context) : '');

				// Assume there is no match
				$partial_match = false;
				$cleaver_partial_match = false;

				// Loop through the permlink components
				foreach ($pl_components as $pl_c_index => $pl_c) {
					// Assume there is no match
					$match = false;

					// Check to see if there are still URI components to be checked.
					if (count($uri_components))
						// Get the next component.
						$uri_c = array_shift($uri_components);

					else if (!$title_page_feed && count($pl_components) - 1 == $uri_component_count) {
						// If we appended a title_page_feed component earlier and permlink and URI components
						// counts are equal, we must of finished checking this permlink, and it matches so break.
						$match = true;
						break;

					} else {
						// If there are no more URI components then we have a partial match.
						// Store the partial match data unless there has been a preceding permlink with the
						// same number of components, as permlink have already been sorted by precedence.
						if (!array_key_exists($uri_component_count, $this->partial_matches))
							$this->partial_matches[$uri_component_count] = $pretext_replacement;

						// Unset pretext_replacement as changes could of been made in a preceding component
						unset($pretext_replacement);

						// Break early form the foreach permlink components loop.
						$partial_match = true;
						break;
					}

					// Extract the permlink components.
					extract($pl_c);

					// If it's a date, grab and combine the next two URI components.
					if ($type == 'date')
						$uri_c .= '/'.array_shift($uri_components).'/'.array_shift($uri_components);

					// Decode the URL
					$uri_c = urldecode($uri_c);

					// Always check the type unless the prefix or suffix aren't there
					$check_type = true;

					// Check prefix
					if ($prefix && $this->pref('show_prefix')) {
						$sanitized_prefix = urldecode($this->encode_url($prefix));
						if (($pos = strpos($uri_c, $sanitized_prefix)) === false || $pos != 0) {
							$check_type = false;
							$this->debug('Can\'t find prefix: '.$prefix);
						} else
							// Check passed, remove prefix ready for the next check
							$uri_c = substr_replace($uri_c, '', 0, strlen($sanitized_prefix));
					}

					// Check suffix
					if ($check_type && $suffix && $this->pref('show_suffix')) {
						$sanitized_suffix = urldecode($this->encode_url($suffix));
						if (($pos = strrpos($uri_c, $sanitized_suffix)) === false) {
							$check_type = false;
							$this->debug('Can\'t find suffix: '.$suffix);
						} else
							// Check passed, remove suffix ready for the next check
							$uri_c = substr_replace($uri_c, '', $pos, strlen($sanitized_suffix));
					}

					// Both the prefix and suffix settings have passed
					if ($check_type) {
						$this->debug('Checking if "'.$uri_c.'" is of type "'.$type.'"');
						$uri_c = doSlash($uri_c);

						//
						if (isset($prefs['permalink_title_format']) && $prefs['permalink_title_format']) {
							$mt_search = array('/_/', '/\.html$/');
						} else {
							$mt_search = array('/(?:^|_)(.)/', '/\.html$/');
						}
						$mt_uri_c = $this->pref('redirect_mt_style_links')
							? preg_replace_callback(
								$mt_search,
								function($m){
									if (isset($prefs['permalink_title_format']) && $prefs['permalink_title_format']) {
										$mt_replace = '-';
									} else {
										$mt_replace = 'strtoupper($m[1])';
									}
									return $mt_replace;
								},
								$uri_c)
							: '';

						// Compare based on type
						switch ($type) {
							case 'section':
								if ($rs = safe_row('name', 'txp_section', "(`name` like '$uri_c' or `name` like '$mt_uri_c') limit 1")) {
									$this->debug('Section name: '.$rs['name']);
									$pretext_replacement['s'] = $rs['name'];
									$context[] = "`Section` = '{$rs['name']}'";
									$match = true;
								}
							break;
							case 'category':
								if ($rs = safe_row('name', 'txp_category', "(`name` like '$uri_c' or `name` like '$mt_uri_c') and `type` = 'article' limit 1")) {
									$this->debug('Category name: '.$rs['name']);
									$pretext_replacement['c'] = $rs['name'];
									$context[] = "(`Category1` = '{$rs['name']}' OR `Category2` = '$uri_c')";
									$match = true;
								}
							break;
							case 'title':
								if ($rs = safe_row('url_title', 'textpattern', "(`url_title` like '$uri_c' or `url_title` like '$mt_uri_c') $context_str and `Status` >= 4 limit 1")) {
									$this->debug('URL Title: '.$rs['url_title']);
									$mt_redirect = ($uri_c != $mt_uri_c);
									$pretext_replacement['url_title'] = $rs['url_title'];
									$match = true;
								}
							break;
							case 'id':
								if ($rs = safe_row('ID, Posted', 'textpattern', "`ID` = '$uri_c' $context_str and `Status` >= 4 limit 1")) {
									$pretext_replacement['id'] = $rs['ID'];
									$pretext_replacement['Posted'] = $rs['Posted'];
									$pretext['numPages'] = 1;
									$pretext['is_article_list'] = false;
									$match = true;
								}
							break;
							case 'author':
								if ($author = safe_field('name', 'txp_users', "RealName like '$uri_c' limit 1")) {
									$pretext_replacement['author'] = $author;
									$context[] = "`AuthorID` = '$author'";
									$match = true;
								}
							break;
							case 'login':
								if ($author = safe_field('name', 'txp_users', "name like '$uri_c' limit 1")) {
									$pretext_replacement['author'] = $author;
									$context[] = "`AuthorID` = '$author'";
									$match = true;
								}
							break;
							case 'custom':
								$custom_options = array_values(array_map(array($this, "encode_url"), safe_column("custom_$custom", 'textpattern', "custom_$custom != ''")));
								if ($this->pref('force_lowercase_urls'))
									$custom_options = array_map("strtolower", $custom_options);
								if (in_array($uri_c, $custom_options)) {
									$match = true;
								}
							break;
							case 'date':
								if (preg_match('/^\d{4}\/\d{2}\/\d{2}$/', $uri_c)) {
									$pretext_replacement['date'] = str_replace('/', '-', $uri_c);
									$match = true;
								}
							break;
							case 'year':
								if (preg_match('/^\d{4}$/', $uri_c)) {
									$pretext_replacement['year'] = $uri_c;
									$match = true;
								}
							break;
							case 'month':
							case 'day':
								if (preg_match('/^\d{2}$/', $uri_c)) {
									$pretext_replacement[$type] = $uri_c;
									$match = true;
								}
							break;
							case 'page':
								if (is_numeric($uri_c)) {
									$pretext_replacement['pg'] = $uri_c;
									$match = true;
								}
							break;
							case 'feed':
								if (in_array($uri_c, array('rss', 'atom'))) {
									$pretext_replacement[$uri_c] = 1;
									$match = true;
								}
							break;
							case 'search':
									$pretext_replacement['q'] = $uri_c;
									$match = true;
							break;
							case 'text':
								if ($this->encode_url($text) == $uri_c) {
									$match = true;
									$pretext_replacement["permlink_text_{$name}"] = $uri_c;
								}
							break;
							case 'regex':
								// Check to see if regex is valid without outputting error messages.
								ob_start();
								preg_match($regex, $uri_c, $regex_matches);
								$is_valid_regex = !(ob_get_clean());
								if ($is_valid_regex && @$regex_matches[0]) {
									$match = true;
									$pretext_replacement["permlink_regex_{$name}"] = $regex_matches[0];
								}
							break;
						} // switch type end

						// Update the article context string
						$context_str = (count($context) > 0 ? 'and '.join(' and ', $context) : '');

						$this->debug(($match == true) ? 'YES' : 'NO');

						if (!$match && !$cleaver_partial_match && $this->pref('use_cleaver_partial_matches')) {
							// There hasn't been a match or a complete cleaver partial match. Lets try to be cleaver and
							// check to see if this component is either a title, page or a feed. This makes it more probable
							// a successful match for a given permlink rule occurs.
							$this->debug('Checking if "'.$uri_c.'" is of type "title_page_feed"');

							if ($type != 'title' && $url_title = safe_field('url_title', 'textpattern', "`url_title` like '$uri_c' $context_str and `Status` >= 4 limit 1")) {
								$pretext_replacement['url_title'] = $url_title;
								$pretext['numPages'] = 1;
								$pretext['is_article_list'] = false;
								$cleaver_partial_match = true;
							} else if ($this->pref('clean_page_archive_links') && $type != 'page' && is_numeric($uri_c)) {
								$pretext_replacement['pg'] = $uri_c;
								$cleaver_partial_match = true;
							} else if ($type != 'feed' && in_array($uri_c, array('rss', 'atom'))) {
								$pretext_replacement[$uri_c] = 1;
								$cleaver_partial_match = true;
							}

							$this->debug(($cleaver_partial_match == true) ? 'YES' : 'NO');

							if ($cleaver_partial_match) {
								$this->cleaver_partial_match = $pretext_replacement;

								// Unset pretext_replacement as changes could of been made in a preceding component
								unset($pretext_replacement);

								$cleaver_partial_match = true;
								continue 2;
							}
						}
					} // check type end

					// Break early if the component doesn't match, as there is no point continuing
					if ($match == false) {
						// Unset pretext_replacement as changes could of been made in a preceding component
						unset($pretext_replacement);
						break;
					}
				} // foreach permlink component end

				if (!isset($pretext_replacement['id'])) {
					if (isset($pretext_replacement['url_title'])) {
						if (isset($pretext_replacement['date'])) {
							$date_val = $pretext_replacement['date'];
						} else if (isset($pretext_replacement['year'])) {
							$date_val = $pretext_replacement['year'];
							if (isset($pretext_replacement['month'])) {
								$date_val .= '-' . $pretext_replacement['month'];
								if (isset($pretext_replacement['day'])) {
									$date_val .= '-' . $pretext_replacement['day'];
								}
							}
						}
						if (isset($date_val))
							$context_str .= " and `Posted` like '$date_val%'";
						if ($rs = safe_row('ID, Posted', 'textpattern', "`url_title` like '{$pretext_replacement['url_title']}' $context_str and `Status` >= 4 order by `Posted` desc limit 1")) {
							if (isset($date_val))
								$this->debug('Found date and title-based match.');
							else
								$this->debug('Found title-based match.');
							$pretext_replacement['id'] = $rs['ID'];
							$pretext_replacement['Posted'] = $rs['Posted'];
							$pretext['numPages'] = 1;
							$pretext['is_article_list'] = false;
						} else {
							$match = false;
							unset($pretext_replacement);
						}
					}
				}

				if ($match || $partial_match || $cleaver_partial_match) {
					// Extract the settings for this permlink
					@extract($permlinks[$pretext_replacement['permlink_id']]['settings']);

					// Check the permlink section and category conditions
					if ((!empty($con_section) && $con_section != @$pretext_replacement['s']) ||
					(!empty($con_category) && $con_category != @$pretext_replacement['c'])) {
						$this->debug('Permlink conditions failed');
						if (@$con_section)  $this->debug('con_section = '. $con_section);
						if (@$con_category) $this->debug('con_category = '. $con_category);

						unset($pretext_replacement);
					}
					else if ($match && isset($pretext_replacement))
						$this->debug('We have a match!');

					else if ($partial_match && count($this->partial_matches))
						$this->debug('We have a \'partial match\'');

					else if ($cleaver_partial_match && isset($cleaver_partial_match))
						$this->debug('We have a \'cleaver partial match\'');

					else {
						$this->debug('Error: Can\'t determine the correct type match');
						// This permlink has failed, continue execution of the foreach permlinks loop
						unset($pretext_replacement);
					}
				}

				// We have a match
				if (@$pretext_replacement)
					break;

			} // foreach permlinks end

			// If there is no match restore the most likely partial match. Sorted by number of components and then precedence
			if (!@$pretext_replacement && count($this->partial_matches)) {
				$pt_slice = array_slice($this->partial_matches, -1);
				$pretext_replacement = array_shift($pt_slice);
			}
			unset($this->partial_matches);

			// Restore the cleaver_partial_match if there is no other matches
			if (!@$pretext_replacement && $this->cleaver_partial_match)
				$pretext_replacement = $this->cleaver_partial_match;
			unset($this->cleaver_partial_match);

			// Extract the settings for this permlink
			@extract($permlinks[$pretext_replacement['permlink_id']]['settings']);

			// If pretext_replacement is still set here then we have a match
			if (@$pretext_replacement) {
				$this->debug('Pretext Replacement '.print_r($pretext_replacement, 1));

				if (!empty($des_section))
					$pretext_replacement['s'] = $des_section;
				if (!empty($des_category))
					$pretext_replacement['c'] = $des_category;
				if (!empty($des_feed))
					$pretext_replacement[$des_feed] = 1;

				if (@$pretext_replacement['id'] && @$pretext_replacement['Posted']) {
					if ($np = getNextPrev($pretext_replacement['id'], $pretext_replacement['Posted'], @$pretext_replacement['s']))
						$pretext_replacement = array_merge($pretext_replacement, $np);
				}
				unset($pretext_replacement['Posted']);

				// If there is a match then we most set the http status correctly as txp's pretext might set it to 404
				$pretext_replacement['status'] = '200';

				// Store the orginial HTTP status code
				// We might need to log the page hit if it equals 404
				$orginial_status = $pretext['status'];

				// Txp only looks at the month, but due to how we phase the month we can manipulate the sql to our needs
				if (array_key_exists('date', $pretext_replacement)) {
					$pretext_replacement['month'] = $pretext_replacement['date'];
					unset($pretext_replacement['date']);
				} else if (array_key_exists('year', $pretext_replacement) ||
				array_key_exists('month', $pretext_replacement) ||
				array_key_exists('day', $pretext_replacement)) {
					$month = '';
					$month .= (array_key_exists('year', $pretext_replacement))
						? $pretext_replacement['year'].'-' : '____-';
					$month .= (array_key_exists('month', $pretext_replacement))
						? $pretext_replacement['month'].'-' : '__-';
					$month .= (array_key_exists('day', $pretext_replacement))
						? $pretext_replacement['day'].' ' : '__ ';

					$pretext_replacement['month'] = $month;
					unset($pretext_replacement['year']);
					unset($pretext_replacement['day']);
				}

				// Section needs to be defined so we can always get a page template.
				if (!array_key_exists('s', $pretext_replacement))
				{
					if (!@$pretext_replacement['id'])
						$pretext_replacement['s'] = 'default';
					else
						$pretext_replacement['s'] = safe_field('Section', 'textpattern', 'ID = '.$pretext_replacement['id']);
				}

				// Set the css and page template, otherwise we get an unknown section
				$section_settings = safe_row('css, page', 'txp_section', "name = '{$pretext_replacement['s']}' limit 1");
				$pretext_replacement['page'] = (@$des_page) ? $des_page : $section_settings['page'];
				$pretext_replacement['css']  = $section_settings['css'];

				$this->matched_permlink = $pretext_replacement;

				global $permlink_mode;

				if (in_array($prefs['permlink_mode'], array('id_title', 'section_id_title')) && @$pretext_replacement['pg'] && !@$pretext_replacement['id']) {
					$pretext_replacement['id'] = '';
					$pretext_replacement['is_article_list'] = true;
				}

				// Merge pretext_replacement with pretext
				$pretext = array_merge($pretext, $pretext_replacement);

				if (is_numeric(@$pretext['id'])) {
					$a = safe_row('*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod', 'textpattern', 'ID='.intval($pretext['id']).' and Status >= 4');
					populateArticleData($a);
				}

				// Export required values to the global namespace
				foreach (array('id', 's', 'c', 'pg', 'is_article_list', 'prev_id', 'prev_title', 'next_id', 'next_title', 'css') as $key) {
					if (array_key_exists($key, $pretext))
						$GLOBALS[$key] = $pretext[$key];
				}

				if (count($this->matched_permlink) || @$mt_redirect) {
					$pl_index = $pretext['permlink_id'];
					if (!@$mt_redirect || !$this->pref('redirect_mt_style_links')) {
						$pl = $this->get_permlink($pretext['permlink_id']);
						$pl_index = @$pl['settings']['des_permlink'];
					}

					if (@$pretext['id'] && $pl_index) {
						if (count($this->get_permlink($pl_index)) > 0) {
							ob_clean();
							global $siteurl;
							$rs = safe_row('*, ID as thisid, unix_timestamp(Posted) as posted', 'textpattern', "ID = '{$pretext['id']}'");
							$host = rtrim(str_replace(rtrim(doStrip($pretext['subpath']), '/'), '', hu), '/');
							$this->redirect($host.$this->_permlinkurl($rs, PERMLINKURL, $pl_index), $this->pref('permlink_redirect_http_status'));
						}
					} else if ($url = @$pl['settings']['des_location']) {
						ob_clean();
						$this->redirect($url, $this->pref('url_redirect_http_status'));
					}
				}

				if (@$pretext['rss']) {
					if (@$pretext['s'])
						$_POST['section'] = $pretext['s'];
					if (@$pretext['c'])
						$_POST['category'] = $pretext['c'];
					ob_clean();
					include txpath.'/publish/rss.php';
					exit(rss());
				}

				if (@$pretext['atom']) {
					if (@$pretext['s'])
						$_POST['section'] = $pretext['s'];
					if (@$pretext['c'])
						$_POST['category'] = $pretext['c'];
					ob_clean();
					include txpath.'/publish/atom.php';
					exit(atom());
				}

				$this->debug('Pretext '.print_r($pretext, 1));
			} else {
				$this->debug('NO CHANGES MADE');
			}

			// Log this page hit
			if (@$orginial_status == 404)
				log_hit($pretext['status']);

			// Start output buffering and pseudo callback to textpattern_end
			ob_start(array(&$this, '_textpattern_end_callback'));

			// TxP 4.0.5 (r2436) introduced the textpattern_end callback making the following redundant
			$version = array_sum(array_map(
				create_function('$line', 'if (preg_match(\'/^\$'.'LastChangedRevision: (\w+) \$/\', $line, $match)) return $match[1];'),
				@file(txpath . '/publish.php')
			));
			if ($version >= '2436') return;

			// Remove the plugin callbacks which have already been called
			function filter_callbacks($c) {
				if ($c['event']!='textpattern') return true;
				if (@$c['function'][0]->plugin_name == 'gbp_permanent_links' &&
						@$c['function'][1] == '_textpattern')
				{
					$GLOBALS['gbp_found_self'] = true;
					return false;
				}
				return @$GLOBALS['gbp_found_self'];
			}
			$plugin_callback = array_filter($plugin_callback, 'filter_callbacks');
			unset($GLOBALS['gbp_found_self']);

			// Re-call textpattern
			textpattern();

			// Call custom textpattern_end callback
			$this->_textpattern_end();

			// textpattern() has run, kill the connection
			die();
		}

	} // function _textpattern end

	function _textpattern_end () {
		// Redirect to a 404 if the page number is greater than the max number of pages
		// Has to be after textpattern() as $thispage is set during <txp:article />
		global $thispage, $pretext;
		if ((@$pretext['pg'] && isset($thispage)) &&
		($thispage['numPages'] < $pretext['pg'])) {
			ob_end_clean();
			txp_die(gTxt('404_not_found'), '404');
		}

		// Stop output buffering, this sends the buffer to _textpattern_end_callback()
		while (@ob_end_flush());

	} // function _textpattern_end end

	function _textpattern_end_callback ($html, $override = '') {
		global $pretext, $production_status;

		if ($override) $pretext['permlink_override'] = $override;
		$html = preg_replace_callback(
			'%href="('.hu.'|\?)([^"]*)"%',
			array(&$this, '_pagelinkurl'),
			$html
		);
		unset($pretext['permlink_override']);

		if ($this->pref('debug') && in_array($production_status, array('debug', 'testing'))) {
			$debug = join(n, $this->buffer_debug);
			$this->buffer_debug = array();
			if ($debug)
				$html = comment(n.$debug.n) . $html;
		}

		return $html;
	} // function _textpattern_end_callback end

	function check_permlink_conditions ($pl, $article_array) {
		if (empty($article_array['section'])) $article_array['section'] = @$article_array['Section'];
		if (empty($article_array['category1'])) $article_array['category1'] = @$article_array['Category1'];
		if (empty($article_array['category2'])) $article_array['category2'] = @$article_array['Category2'];

		if (@$pl['settings']['con_category'] && ($pl['settings']['con_category'] != $article_array['category1'] || $pl['settings']['con_category'] != $article_array['category2']))
			return false;
		if (@$pl['settings']['con_section'] && $pl['settings']['con_section'] != $article_array['section'])
			return false;

		return true;
	}

	function _permlinkurl ($article_array, $type = PERMLINKURL, $pl_index = NULL) {
		global $pretext, $prefs, $production_status;

		if ($type == PAGELINKURL)
			return $this->toggle_custom_url_func('pagelinkurl', $article_array);

		if (empty($article_array)) return;

		if ($pl_index)
			$pl = $this->get_permlink($pl_index);
		else {
			// Get the matched pretext replacement array.
			$matched = (count($this->matched_permlink))
			? $this->matched_permlink
			: @array_shift(array_slice($this->partial_matches, -1));

			if (!isset($pl) && $matched && array_key_exists('id', $matched)) {
				// The permlink id is stored in the pretext replacement array, so we can find the permlink.
				$pl = $this->get_permlink($matched['permlink_id']);
				foreach ($pl['components'] as $pl_c)
					if (in_array($pl_c['type'], array('feed', 'page')) || !$this->check_permlink_conditions($pl, $article_array)) {
						unset($pl);
						break;
					}
			}

			if (!isset($pl)) {
				// We have no permlink id so grab the permlink with the highest precedence.
				$permlinks = $this->get_all_permlinks(1, array('feed', 'page'));
				foreach ($permlinks as $key => $pl)
					if (!$this->check_permlink_conditions($pl, $article_array))
						unset($permlinks[$key]);
				$pl = array_shift($permlinks);
			}
		}

		$uri = '';

		if (is_array($pl) && array_key_exists('components', $pl)) {
			extract($article_array);

			if (!isset($title)) $title = $Title;
			if (empty($url_title)) $url_title = stripSpace($title);
			if (empty($section)) $section = $Section;
			if (empty($posted)) $posted = $Posted;
			if (empty($authorid)) $authorid = @$AuthorID;
			if (empty($category1)) $category1 = @$Category1;
			if (empty($category2)) $category2 = @$Category2;
			if (empty($thisid)) $thisid = $ID;

			$pl_components = $pl['components'];

			// Check to see if there is a title component.
			$title = false;
			foreach($pl_components as $pl_c)
				if ($pl_c['type'] == 'title' || $pl_c['type'] == 'id')
					$title = true;

			// If there isn't a title component then we need to append one to the end of the URI
			if (!$title && $this->pref('automatically_append_title'))
				$pl_components[] = array('type' => 'title', 'prefix' => '', 'suffix' => '', 'regex' => '', 'text' => '');

			$uri = rtrim(doStrip(@$pretext['subpath']), '/');
			foreach ($pl_components as $pl_c) {
				$uri .= '/';

				$type = $pl_c['type'];
				switch ($type) {
					case 'category':
						if (!@$pl_c['category']) $pl_c['category'] = 1;
						$primary = 'category'. $pl_c['category'];
						$secondary = 'category'. (3-(int)$pl_c['category']);
						$check_context = ($this->pref('join_pretext_to_pagelinks') && $this->pref('check_pretext_category_context'));
						if (!$check_context || $$primary == $pretext['c'])
							$uri_c = $$primary;
						else if (!$check_context || $$secondary == $pretext['c'])
							$uri_c = $$secondary;
						else if ($this->pref('debug') && in_array($production_status, array('debug', 'testing')))
							$uri_c = '--INVALID_CATEGORY--';
						else {
							unset($uri);
							break 2;
						}
					break;
					case 'section':
						$check_context = ($this->pref('join_pretext_to_pagelinks') && $this->pref('check_pretext_section_context'));
						if (!$check_context || $section == $pretext['s'])
							$uri_c = $section;
						else {
							unset($uri);
							break 2;
						}
					break;
					case 'title': $uri_c = $url_title; break;
					case 'id': $uri_c = $thisid; break;
					case 'author': $uri_c = safe_field('RealName', 'txp_users', "name like '{$authorid}'"); break;
					case 'login': $uri_c = $authorid; break;
					case 'date': $uri_c = explode('/', date('Y/m/d', $posted)); break;
					case 'year': $uri_c = date('Y', $posted); break;
					case 'month': $uri_c = date('m', $posted); break;
					case 'day': $uri_c = date('d', $posted); break;
					case 'custom':
						if ($uri_c = @$article_array[$prefs["custom_{$pl_c['custom']}_set"]]);
						else if ($uri_c = @$article_array["custom_{$pl_c['custom']}"]);
						else if ($this->pref('debug') && in_array($production_status, array('debug', 'testing')))
							$uri_c = '--UNSET_CUSTOM_FIELD--';
						else {
							unset($uri);
							break 2;
						}
					break;
					case 'text': $uri_c = $pl_c['text']; break;
					case 'regex':
						// Check to see if regex is valid without outputting error messages.
						ob_start();
						preg_match($pl_c['regex'], $pl_c['regex'], $regex_matches);
						$is_valid_regex = !(ob_get_clean());
						if ($is_valid_regex) {
							$key = "permlink_regex_{$pl_c['name']}";
							$uri_c = (array_key_exists($key, $pretext)) ? $pretext[$key] : $regex_matches[0];
						} else if ($this->pref('debug'))
							$uri_c = '--INVALID_REGEX--';
					break;
				}

				if (empty($uri_c))
					if ($this->pref('debug') && in_array($production_status, array('debug', 'testing')))
						$uri_c = '--PERMLINK_FORMAT_ERROR--';
					else {
						unset($uri);
						break;
					}

				if (@$pl_c['prefix'])
					$uri .= $this->encode_url($pl_c['prefix']);

				if (is_array($uri_c)) {
					foreach ($uri_c as $uri_c2)
						$uri .= $this->encode_url($uri_c2) . '/';
					$uri = rtrim($uri, '/');
				} else
					$uri .= $this->encode_url($uri_c);

				if (@$pl_c['suffix'])
					$uri .= $this->encode_url($pl_c['suffix']);

				unset($uri_c);
			}

			if (isset($uri))
				$uri .= '/';
		}

		if ($uri_empty = empty($uri)) {
			// It is possible the uri is still empty if there is no match or if we're using
			// strict matching if so try the default permlink mode.
			$uri = $this->toggle_permlink_mode('permlinkurl', $article_array);
		}

		if ($this->pref('omit_trailing_slash'))
			$uri = rtrim($uri, '/');

		if (!$uri_empty && in_array(txpath.'/publish/rss.php', get_included_files()) || in_array(txpath.'/publish/atom.php', get_included_files()) || txpinterface == 'admin') {
			$host = rtrim(str_replace(rtrim(doStrip(@$pretext['subpath']), '/'), '', hu), '/');
			$uri = $host . $uri;
		}

		return ($this->pref('force_lowercase_urls')) ? strtolower($uri) : $uri;
	}

	function _pagelinkurl ($parts) {
		extract(array(
			'path'		=> 'index.php',
			'query'		=> '',
			'fragment'	=> '',
		), parse_url(html_entity_decode(str_replace('&#38;', '&', $parts[2]))));

		// Tidy up links back to the site homepage
		if ($path == 'index.php' && empty($query))
			return 'href="' .hu. '"';

		// Fix matches like href="?s=foo"
		else if ($path && empty($query) && $parts[1] == '?') {
			$query = $path;
			$path = 'index.php';
		}

		// Check to see if there is query to work with.
		else if (empty($query) || $path != 'index.php' || strpos($query, '/') === true)
			return $parts[0];

		// '&amp;' will break parse_str() if they are found in a query string
		$query = str_replace('&amp;', '&', $query);

		if ($fragment)
			$fragment = '#'.$fragment;

		global $pretext;
		parse_str($query, $query_part);
		if (!array_key_exists('pg', $query_part))
			$query_part['pg'] = 0;
		if (!array_key_exists('id', $query_part))
			$query_part['id'] = 0;
		if (!array_key_exists('rss', $query_part))
			$query_part['rss'] = 0;
		if (!array_key_exists('atom', $query_part))
			$query_part['atom'] = 0;
		if ($this->pref('join_pretext_to_pagelinks'))
			extract(array_merge($pretext, $query_part));
		else
			extract($query_part);

		// We have a id, pass to permlinkurl()
		if ($id) {
			if (@$s == 'file_download') {
				$title = (version_compare($dbversion, '4.2', '>=')) ? NULL : safe_field('filename', 'txp_file', "id = '{$id}'");
				$url = $this->toggle_permlink_mode('filedownloadurl', array($id, $title), true);
			} else {
				$rs = safe_row('*, ID as thisid, unix_timestamp(Posted) as posted', 'textpattern', "ID = '{$id}'");
				$url = $this->_permlinkurl($rs, PERMLINKURL) . $fragment;
			}
			return 'href="'. $url .'"';
		}

		if (@$s == 'default')
			unset($s);

		// Some TxP tags, e.g. <txp:feed_link /> use 'section' or 'category' inconsistent
		// with most other tags. Process these now so we only have to check $s and $c.
		if (@$section)  $s = $section;
		if (@$category) $c = $category;

		// Debugging for buffers
		$this->buffer_debug[] = 'url: '.str_replace('&amp;', '&', $parts[1].$parts[2]);
		$this->buffer_debug[] = 'path: '.$path;
		$this->buffer_debug[] = 'query: '.$query;
		if ($fragment) $this->buffer_debug[] = 'fragment: '.$fragment;

		if (@$id) $this->buffer_debug[] = 'id: '.$id;
		if (@$s) $this->buffer_debug[] = 's: '.$s;
		if (@$c) $this->buffer_debug[] = 'c: '.$c;
		if (@$rss) $this->buffer_debug[] = 'rss: '.$rss;
		if (@$atom) $this->buffer_debug[] = 'atom: '.$atom;
		if (@$pg) $this->buffer_debug[] = 'pg: '.$pg;
		if (@$q) $this->buffer_debug[] = 'q: '.$q;

		if (@$pretext['permlink_override']) {
			$override_ids = explode(',', $pretext['permlink_override']);
			foreach ($override_ids as $override_id) {
				$pl = $this->get_permlink($override_id);
				if (count($pl) > 0) $permlinks[] = $pl;
			}
		}

		if (empty($permlinks)) {
			$permlinks = $this->get_all_permlinks(1);

			$permlinks['gbp_permanent_links_default'] = array(
				'components' => array(
					array('type' => 'text', 'text' => strtolower(urlencode(gTxt('category')))),
					array('type' => 'category'),
				),
				'settings' => array(
					'pl_name' => 'gbp_permanent_links_default', 'pl_precedence' => '', 'pl_preview' => '',
					'con_section' => '', 'con_category' => '', 'des_section' => '', 'des_category' => '',
					'des_permlink' => '', 'des_feed' => '', 'des_location' => '',
			));
		}

		$current_segments = explode('/', ltrim($pretext['request_uri'], '/'));

		$highest_match_count = null;
		foreach ($permlinks as $key => $pl) {
			$this->buffer_debug[] = 'Testing permlink: '. $pl['settings']['pl_name'] .' - '. $key;
			$this->buffer_debug[] = 'Preview: '. $pl['settings']['pl_preview'];
			$out = array(); $keys = array(); $match_count = 0;
			foreach ($pl['components'] as $i => $pl_c) {
				switch ($pl_c['type']) {
					case 'text':
						$out[] = $pl_c['text'];
					break;
					case 'regex':
						$out[] = $pretext['permlink_regex_'.$pl_c['name']];
					break;
					case 'section':
						if (@$s) $out[] = $s;
						else break 2;
					break;
					case 'category':
						if (@$c) $out[] = $c;
						else break 2;
					break;
					case 'feed':
						if (@$rss) $keys[] = $out[] = 'rss';
						else if (@$atom) $keys[] = $out[] = 'atom';
						else break 2;
					break;
					case 'search':
						if (@$q) $out[] = $q;
						else break 2;
					break;
					case 'page':
						if (@$pg) {
							$out[] = $pg;
							$keys[] = 'pg';
						}
						else break 2;
					break;
					default: break 2;
				}
				if (in_array($pl_c['type'], array('text', 'regex'))) {
					if ($current_segments[$i] == end($out) && strlen(end($out)) > 0)
						$match_count += $this->pref('text_and_regex_segment_scores');
				}
				elseif (!in_array($pl_c['type'], array('title', 'id')))
					$match_count++;
				else break;
			}

			$this->buffer_debug[] = 'Match count: '. $match_count;

			// Todo: Store according to the precedence value
			if (count($out) > 0 && ($match_count > $highest_match_count || !isset($highest_match_count)) &&
			!($key == 'gbp_permanent_links_default' && !$match_count)) {
				extract($pl['settings']);
				if ((empty($s) && empty($c)) ||
				(empty($con_section) || @$s == $con_section) ||
				(empty($con_category) || @$c == $con_category)) {
					$this->buffer_debug[] = 'New highest match! '. implode('/', $out);
					$highest_match_count = $match_count;
					$match = $out;
					$match_keys = $keys;
				}
			}
		}

		if (empty($match) && (!(@$pg && $this->pref('clean_page_archive_links')) || (@$pg && @$q))) {
			global $prefs, $pretext, $permlink_mode;
			$this->buffer_debug[] = 'No match';
			$this->buffer_debug[] = '----';
			$pretext['permlink_mode'] = $permlink_mode = $prefs['permlink_mode'];
			$url = pagelinkurl($query_part);
			$pretext['permlink_mode'] = $permlink_mode = 'messy';
			return 'href="'. $url .'"';
		}

		$this->buffer_debug[] = 'match: '.      serialize($match);
		$this->buffer_debug[] = 'match_keys: '. serialize($match_keys);

		$url = '/'.join('/', $match);
		$url = rtrim(hu, '/').rtrim($url, '/').'/';

		if ($rss && !in_array('rss', $match_keys))
			$url .= 'rss';
		else if ($atom && !in_array('atom', $match_keys))
			$url .= 'atom';
		else if ($pg && !in_array('pg', $match_keys)) {
			if ($this->pref('clean_page_archive_links'))
				$url .= $pg;
			else {
				$url .= '?pg='. $pg;
				$omit_trailing_slash = true;
			}
		}

		$url = rtrim($url, '/') . '/';

		if (@$omit_trailing_slash || $this->pref('omit_trailing_slash'))
			$url = rtrim($url, '/');

		$this->buffer_debug[] = $url;
		$this->buffer_debug[] = '----';

		if ($path == 'index.php' && $url != hu)
			return 'href="'. $url . $fragment .'"';

		/*
		1 = index, textpattern/css, NULL (=index)
		2 = id, s, section, c, category, rss, atom, pg, q, (n, p, month, author)
		*/

		return ($this->pref('force_lowercase_urls')) ? strtolower($parts[0]) : $parts[0];
	}

	function set_permlink_mode ($reset_function = false) {
		global $prefs, $pretext, $permlink_mode;
		$prefs['custom_url_func'] = array(&$this, '_permlinkurl');

		if (!$reset_function)
			$pretext['permlink_mode'] = $permlink_mode = 'messy';
		else
			$pretext['permlink_mode'] = $permlink_mode = $prefs['permlink_mode'];
	}

	function reset_permlink_mode () {
		global $prefs, $pretext, $permlink_mode;
		unset($prefs['custom_url_func']);
		$pretext['permlink_mode'] = $permlink_mode = $prefs['permlink_mode'];
	}

	function toggle_custom_url_func ($func, $atts = NULL, $toogle_permlink_mode = false, $expand_arguments = false) {
		global $prefs, $pretext;

		if ($toogle_permlink_mode) {
			global $permlink_mode;
			$_permlink_mode = $permlink_mode;
		}

		$_call_user_func = @$prefs['custom_url_func'];

		unset($prefs['custom_url_func']);
		if ($toogle_permlink_mode)
			$pretext['permlink_mode'] = $permlink_mode = $prefs['permlink_mode'];

		if (is_callable($func)) {
			if (is_array($atts) and $expand_arguments)
				$rs = call_user_func_array($func, $atts);
			else
				$rs = call_user_func($func, $atts);
		}

		$prefs['custom_url_func'] = $_call_user_func;

		if ($toogle_permlink_mode)
			$pretext['permlink_mode'] = $permlink_mode = $_permlink_mode;

		return $rs;
	}

	function toggle_permlink_mode ($func, $atts = NULL, $expand_arguments = false) {
		return $this->toggle_custom_url_func($func, $atts, true, $expand_arguments);
	}

	function encode_url ($text) {
		return urlencode(trim(dumbDown(urldecode($text))));
	}

	function debug () {
		if ($this->pref('debug')) {
			global $production_status;
			$a = func_get_args();

			if (@txpinterface == 'admin')
				foreach ($a as $thing)
					dmp($thing);

			if (@txpinterface == 'public' && in_array($production_status, array('debug', 'testing')))
				foreach ($a as $thing)
					echo comment(is_scalar($thing) ? strval($thing) : var_export($thing, true)),n;
		}
	}
}

class PermanentLinksBuildTabView extends GBPAdminTabView
{
	function preload () {
		register_callback(array(&$this, 'post_save_permlink'), $this->parent->event, gbp_save, 1);
		register_callback(array(&$this, 'post_save_permlink'), $this->parent->event, gbp_post, 1);
	}

	function main () {
		global $prefs;
		extract(gpsa(array('step', gbp_id)));

		// With have an ID, either the permlink has just been saved or the user wants to edit it
		if ($id) {
			// Newly saved or beening edited, either way we're editing a permlink
			$step = gbp_save;
			// Use the ID to grab the permlink data (components & settings)
			$permlink = $this->parent->get_permlink($id);

			if(!isset($permlink['components'])){$permlink['components'] = array();}
			if(!isset($permlink['settings'])){$permlink['settings'] = array();}
			$components = $this->phpArrayToJsArray('components', $permlink['components']);
			$settings = $permlink['settings'];
		} else {
			// Creating a new ID and permlink.
			$step = gbp_post;
			$id = uniqid('');

			// Set the default set of components depending on whether there is parent permlink
			$components = $this->phpArrayToJsArray('components', array(
				array('type' => 'section', 'prefix' => '', 'suffix' => '', 'regex' => '', 'text' => ''),
				array('type' => 'category', 'prefix' => '', 'suffix' => '', 'regex' => '', 'text' => '', 'category' => '1'),
				array('type' => 'title', 'prefix' => '', 'suffix' => '', 'regex' => '', 'text' => ''),
			));

			$settings = array(
				'pl_name' => 'Untitled', 'pl_precedence' => '0',
				'con_section' => '', 'con_category' => '',
				'des_section' => '', 'des_category' => '', 'des_page' => '',
				'des_permlink' => '', 'des_feed' => '', 'des_location' => '',
			);
		}

		// Extract settings - this will be useful when creating the user interface
		extract($settings);

		// PHP & Javascript constants;
		$separator = gbp_separator;
		$components_div = 'permlink_components_ui';
		$components_form = 'permlink_components';
		$settings_form = 'permlink_settings';
		$show_prefix = $this->pref('show_prefix');
		$show_suffix = $this->pref('show_suffix');

		// A little credit here and there doesn't hurt
		$out[] = "<!-- {$this->parent->plugin_name} by Graeme Porteous -->";

		// The Javascript
$out[] = <<<HTML
	<script type="text/javascript" language="javascript" charset="utf-8">
	// <![CDATA[

	// Global variables
var {$components}// components array for all the data

	var _current = 0; // Index of the components array, of the currently selected component
	var c_vals = new Array('type', 'custom', 'category', 'name', 'prefix', 'suffix', 'regex', 'text');

	window.onload = function() {
		component_refresh_all();
		component_switch(component(_current));
	}

	function component_add () {
		// Create data set
		var data = new Array();
		for (key in c_vals) {
			data[c_vals[key]] = '';
		}

		// Add data
		components.push(data);

		// Reset component type list
		form('{$components_form}').type.value = '';

		// Switch to the new component
		_current = components.length - 1;

		// Refresh UI
		component_refresh_all();
		component_update();
	}

	function component_refresh (element) {
		var c = components[element.id];

		// CSS
		if (_current == element.id)
			element.style['backgroundColor'] = 'rgb(249, 206, 73)';
		else
			element.style['backgroundColor'] = 'rgb(255, 254, 211)';
		element.style['color'] = 'rgb(0, 0, 0)';
		element.style['fontFamily'] = 'Arial';
		element.style['fontWeight'] = 'bold';
		element.style['verticalAlign'] = 'middle';
		element.style['textAlign'] = 'center';
		element.style['lineHeight'] = '1.5em';
		element.style['height'] = '1.5em';
		element.style['padding'] = '0 5px';
		element.style['marginRight'] = '5px';
		element.style['cssFloat'] = 'left';
		element.style['display'] = 'inline';

		// Remove all child nodes
		while (element.hasChildNodes()) { element.removeChild(element.firstChild); }

		// Create the visible string representing this component
		switch (c['type']) {
			case '' :
				string = '/';
				break;
			case 'regex' :
			case 'text' :
				string = c[c['type']] + ' /';
				break;
			case 'date' :
				string = c['type'] + ' /';
				break;
			case 'custom' :
				string = c['prefix'] + 'custom_' + c['custom'] + c['suffix'] + ' /';
				break;
			default :
				string = c['prefix'] + c['type'] + c['suffix'] + ' /';
			break;
		}

		// Set the visible string
		element.appendChild(document.createTextNode(string));

		return element;
	}

	function component_refresh_all () {
		// Remove all child nodes
		while (permlink_div().hasChildNodes()) { permlink_div().removeChild(permlink_div().firstChild); }

		for (var i = 0; i < components.length; i++) {
			var c = components[i];

			// Create the new component
			var new_component = document.createElement('div');

			// Set the id interger for this component
			new_component.id = i;

			// Javascript, onmouseup setting
			new_component.setAttribute('onmousedown', 'component_switch(this);');
			new_component.onmousedown = function() { component_switch(this); };

			// Refresh the look of the component
			new_component = component_refresh(new_component);

			// And add the new component to the ui
			permlink_div().appendChild(new_component);
		}
	}

	function component_remove () {
		if (components.length > 1) {
			components.splice(_current, 1);

			if (_current >= components.length)
				_current = components.length - 1;

			component_refresh_all();
		}
	}

	function component_switch (element) {
		// Update current index
		_current = element.id;

		// Refresh UI
		component_refresh_all();

		// Set form input values
		var c = components[_current];
		for (key in c_vals) {
			var k = c_vals[key];
			var e = form('{$components_form}').elements.namedItem(k);

			if (c[k]) e.value = c[k];
			else e.value = '';
		}

		// Hide unneeded form inputs
		component_update();
	}

	function component_update (element) {
		// Store the data in form inputs, and hide all form inputs
		var c = new Array()
		for (key in c_vals) {
			var k = c_vals[key];
			var e = form('{$components_form}').elements.namedItem(k);

			c[k] = e.value;

			e.parentNode.style['display'] = 'none';
		}

		// Reshow type option list
		form('{$components_form}').type.parentNode.style['display'] = '';

		// Set other form inputs to the correct visibility state, dependent on type
		switch (c['type']) {
			case '' :
			case 'date' : break;
			case 'regex' :
				form('{$components_form}').name.parentNode.style['display'] = '';
				form('{$components_form}').regex.parentNode.style['display'] = '';
			break;
			case 'text' :
				form('{$components_form}').name.parentNode.style['display'] = '';
				form('{$components_form}').text.parentNode.style['display'] = '';
			break;
			case 'custom' :
				form('{$components_form}').custom.parentNode.style['display'] = '';
				display_fixes();
			break;
			case 'category' :
				form('{$components_form}').category.parentNode.style['display'] = '';
				display_fixes();
			break;
			default :
				display_fixes();
			break;
		}

		// Save data
		components[_current] = c;

		// Refresh component to reflect new data
		component_refresh(component(_current));

		// Re-focus the active form input
		if (element)
			element.focus();
	}

	function display_fixes () {
		if ('{$show_prefix}')
			form('{$components_form}').prefix.parentNode.style['display'] = '';
		if ('{$show_suffix}')
			form('{$components_form}').suffix.parentNode.style['display'] = '';
	}

	function component_left () {
		if (components.length > 1 && _current > 0) {
			// Store current component
			var c = components[_current];

			// Remove current component
			components.splice(_current, 1);

			// Update current index
			_current--;

			// Re-add current component
			components.splice(_current, 0, c);

			// Refresh UI
			component_refresh_all();
		}
	}

	function component_right () {
		if (_current < components.length - 1) {
			// Store current component
			var c = components[_current];

			// Remove current component
			components.splice(_current, 1);

			// Update current index
			_current++;

			// Re-add current component
			components.splice(_current, 0, c);

			// Refresh UI
			component_refresh_all();
		}
	}

	function save (form) {
		var c = ''; var is_permlink = false; var has_page_or_search = false;
		for (var i = 0; i < components.length; i++) {
			if (components[i]['type'] == 'title' || components[i]['type'] == 'id')
				is_permlink = true;
			if (components[i]['type'] == 'page' || components[i]['type'] == 'feed' || components[i]['type'] == 'search')
				has_page_feed_search = true;
			c = c + jsArrayToPhpArray(components[i]) + '{$separator}';
		}

		if (is_permlink && has_page_or_search)
			alert("Your permanent link can't contain either a 'page', 'feed' or a 'search' component with 'title' or 'id' components.");

		else if (is_permlink && (form.pl_name.value == '' || form.pl_name.value == 'Untitled')) {
			document.getElementById('settings').style['display'] = '';
			form.pl_name.style['border'] = '3px solid rgb(221, 0, 0)';
			form.pl_precedence.style['border'] = '';
			alert('Please enter a name for this permanent link rule.');
		} else if (form.pl_precedence.value == '') {
			document.getElementById('settings').style['display'] = '';
			form.pl_precedence.style['border'] = '3px solid rgb(221, 0, 0)';
			form.pl_name.style['border'] = '';
			alert('Please enter a precedence value for this permanent link rule.');
		} else {
			form.components.value = c;
			if (permlink_div().textContent)
				form.pl_preview.value = permlink_div().textContent;
			else if (permlink_div().innerText)
				form.pl_preview.value = permlink_div().innerText;
			return true;
		}

		return false;
	}

	function jsArrayToPhpArray (array) {
		// http://farm.tucows.com/blog/_archives/2005/5/30/895901.html
		var array_php = "";
		var total = 0;
		for (var key in array) {
			++ total;
			array_php = array_php + "s:" +
				String(key).length + ":\"" + String(key) + "\";s:" +
				String(array[key]).length + ":\"" + String(array[key]) + "\";";
		}
		array_php = "a:" + total + ":{" + array_php + "}";
		return array_php;
	}

	function permlink_div () {
		// Return the permlink rule element
		return document.getElementById('{$components_div}');
	}

	function form (name) {
		if (!name)
			name = '{$components_form}'
		// Return the form element with name
		return document.forms.namedItem(name);
	}

	function component (index) {
		// Return component with index
		return permlink_div().childNodes[index];
	}

	// ]]>
	</script>
HTML;

		// --- Rule --- //

		$out[] = hed('Permanent link rule', 2);
		$out[] = '<div id="'.$components_div.'" style="background-color: rgb(230, 230, 230); width: auto; height: 1.5em; margin: 10px 0; padding: 5px;"></div>';
		$out[] = graf
			(
			$this->fInput('button', 'component_add', 'Add component', array('click' => 'component_add();')).n.
			$this->fInput('button', 'component_remove', 'Remove component', array('click' => 'component_remove();')).n.
			$this->fInput('button', 'component_left', 'Move left', array('click' => 'component_left();')).n.
			$this->fInput('button', 'component_right', 'Move right', array('click' => 'component_right();'))
			);

		// --- Component form --- //

		$out[] = '<form action="index.php" name="'.$components_form.'" onsubmit="return false;">';

		// --- Component type --- //

		$component_types = array (
			'section' => 'Section', 'category' => 'Category',
			'title' => 'Title', 'id' => 'ID',
			'date' => 'Date (yyyy/mm/dd)', 'year' => 'Year',
			'month' => 'Month', 'day' => 'Day',
			'author' => 'Author (Real name)', 'login' => 'Author (Login)',
			'custom' => 'Custom Field', 'page' => 'Page Number',
			'feed' => 'Feed', 'search' => 'Search request',
			'text' => 'Plain Text', 'regex' => 'Regular Expression'
		);
		$out[] = graf($this->fSelect('type', $component_types, '', 1, 'Component type', ' onchange="component_update();"'));

		// --- Component data --- //

		// Grab the custom field titles
		$custom_fields = array();
		for ($i = 1; $i <= 10; $i++) {
			if ($v = $prefs["custom_{$i}_set"])
				$custom_fields[$i] = $v;
		}

		$out[] = graf (
			$this->fSelect('custom', $custom_fields, '', 0, 'Custom', ' onchange="component_update(this);"').n.
			$this->fSelect('category', array('1' => 'Category 1', '2' => 'Category 2'), '', 0, 'Primary Category', ' onchange="component_update(this);"').n.
			$this->fInput('text', 'name', '', array('keyup' => 'component_update(this);'), 'Name').n.
			$this->fInput('text', 'prefix', '', array('keyup' => 'component_update(this);'), 'Prefix').n.
			$this->fInput('text', 'regex', '', array('keyup' => 'component_update(this);'), 'Regular Expression').n.
			$this->fInput('text', 'suffix', '', array('keyup' => 'component_update(this);'), 'Suffix').n.
			$this->fInput('text', 'text', '', array('keyup' => 'component_update(this);'), 'Text')
		);
		$hr = '<hr style="border: 0; height: 1px; background-color: rgb(200, 200, 200); color: rgb(200, 200, 200); margin-bottom: 10px;" />';
		$out[] = $hr;
		$out[] = '</form>';

		// --- Settings form --- //

		$out[] = '<form action="index.php" method="post" name="'.$settings_form.'" onsubmit="return save(this);">';

		// --- Settings --- //

		//if(!isset($pl_name)){$pl_name = null;}
		//if(!isset($pl_precedence)){$pl_precedence = null;}
		$out[] = hed('<a href="#" onclick="toggleDisplay(\'settings\'); return false;">Settings</a>', 2);
		$out[] = '<div id="settings">';
		$out[] = graf($this->fInput('text', 'pl_name', @$pl_name, NULL, 'Name'));
		$out[] = graf($this->fInput('text', 'pl_precedence', @$pl_precedence, NULL, 'Precedence'));
		$out[] = '</div>';
		$out[] = $hr;

		// --- Conditions --- //

		$out[] = hed('<a href="#" onclick="toggleDisplay(\'conditions\'); return false;">Conditions</a>', 2);
		$out[] = '<div id="conditions" style="display:none">';
		$out[] = graf(strong('Only use this permanent link if the following conditions apply...'));

		// Generate a sections array (name=>title)
		$sections = array();
		$rs = safe_rows('name, title', 'txp_section', "name != 'default' order by name");
		foreach ($rs as $sec) {
			$sections[$sec['name']] = $sec['title'];
		}

		// Generate a categories array (name=>title)
		$categories = array();
		$rs = safe_rows('name, title', 'txp_category', "type = 'article' and name != 'root' order by name");
		foreach ($rs as $sec) {
			$categories[$sec['name']] = $sec['title'];
		}

		//if(!isset($con_section)){$con_section = null;}
		//if(!isset($con_category)){$con_category = null;}
		$out[] = graf (
			$this->fSelect('con_section', $sections, @$con_section, 1, 'Within section').n.
			$this->fSelect('con_category', $categories, @$con_category, 1, 'Within category')
		);
		$out[] = '</div>';
		$out[] = $hr;

		// --- Destination --- //

		//if(!isset($des_section)){$des_section = null;}
		$out[] = hed('<a href="#" onclick="toggleDisplay(\'destination\'); return false;">Destination</a>', 2);
		$out[] = '<div id="destination" style="display:none">';
		$out[] = graf(strong('Forward this permanent link to...'));
		$out[] = graf (
			$this->fSelect('des_section', $sections, @$des_section, 1, 'Section').n.
			$this->fSelect('des_category', $categories, @$des_category, 1, 'Category')
		);
		$out[] = graf($this->fSelect('des_page', safe_column('name', 'txp_page', "1"), @$des_page, 1, 'Page'));
		$out[] = graf($this->fBoxes('des_feed', array('rss', 'atom', ''), @$des_feed, NULL, array('RSS feed', 'Atom feed', 'Neither')));
		$out[] = graf(strong('Redirect this permanent link to...'));
		// Generate a permlinks array
		$permlinks = $this->parent->get_all_permlinks(1);
		foreach ($permlinks as $key => $pl) {
			$permlinks[$key] = $pl['settings']['pl_name'];
		}
		unset($permlinks[$id]);
		$out[] = graf($this->fSelect('des_permlink', $permlinks, @$des_permlink, 1, 'Permanent link'));
		$out[] = graf($this->fInput('text', 'des_location', @$des_location, NULL, 'HTTP location'));
		$out[] = '</div>';
		$out[] = $hr;

		// Save button
		$out[] = fInput('submit', '', 'Save permanent link');

		// Extra form inputs which get filled on submit
		$out[] = hInput('components', '');
		$out[] = hInput('pl_preview', '');
		// Event and tab form inputs
		$out[] = $this->form_inputs();
		// Step and ID form inputs
		$out[] = sInput($step);
		$out[] = hInput(gbp_id, $id);

		$out[] = '</form>';

		// Lets echo everything out. Yah!
		echo join(n, $out);
	}

	function fLabel ($label, $contents = '', $label_right = false) {
		// <label> the contents with the name $lable
		$contents = ($label_right)
		? $contents.$label
		: $label.($contents ? ': '.$contents : '');
		return tag($contents, 'label');
	}

	function fBoxes ($name = '', $value = '', $checked_value = '', $on = array(), $label = '') {
		$out = array();
		if (is_array($value)) {
			$i = 0;
			foreach ($value as $val) {
				$o = '<input type="radio" name="'.$name.'" value="'.$val.'"';
				$o .= ($checked_value == $val) ? ' checked="checked"' : '';
				if (is_array($on)) foreach($on as $k => $v)
					$o .= ($on) ? ' on'.$k.'="'.$v.'"' : '';
				$o .= ' />';
				$out[] = $this->fLabel($label[$i++], $o, true);
			}
		} else {
			$o = '<input type="checkbox" name="'.$name.'" value="'.$value.'"';
			$o .= ($checked_value == $value) ? ' checked="checked"' : '';
			if (is_array($on)) foreach($on as $k => $v)
				$o .= ($on) ? ' on'.$k.'="'.$v.'"' : '';
			$o .= ' />';
			$out[] = $this->fLabel($label, $o, true);
		}

		return join('', $out);
	}

	function fInput ($type, $name = '', $value = '', $on = array(), $label = '') {
		if ($type == 'radio' || $type == 'checkbox')
			return $this->fBoxes($name, $value, $on, $label);

		$o = '<input type="'.$type.'" name="'.$name.'" value="'.$value.'"';
		if (is_array($on)) foreach($on as $k => $v)
			$o .= ($on) ? ' on'.$k.'="'.$v.'"' : '';
		$o .= ' />';
		return ($label) ? $this->fLabel($label, $o) : $o;
	}

	function fSelect ($name = '', $array = '', $value = '', $blank_first = '', $label = '', $on_submit = '') {
		$o = selectInput($name, $array, $value, $blank_first, $on_submit);
		return ($label ? $this->fLabel($label, $o) : $o);
	}

	function post_save_permlink () {
		// The function posts or saves a permanent link to txp_prefs

		extract(gpsa(array('step', gbp_id)));

		// Grab the user defined settings from the form POST data
		$settings = gpsa(array(
			'pl_name', 'pl_precedence', 'pl_preview',
			'con_section', 'con_category',
			'des_section', 'des_category', 'des_page',
			'des_permlink', 'des_feed', 'des_location',
		));

		// Remove spaces from the permanent link preview
		$settings['pl_preview'] = preg_replace('%\s+/\s*%', '/', $settings['pl_preview']);

		// Explode the separated string of serialize components - this was made by JavaScript.
		$serialize_components = explode(gbp_separator, rtrim(gps('components'), gbp_separator));

		// Unserialize the components
		$components = array();
		foreach ($serialize_components as $c)
			$components[] = unserialize(stripslashes($c));

		// Complete the permanent link array - this is exactly what needs to be stored in the db
		$permlink = array('settings' => $settings, 'components' => $components);

		// Save it
		$this->set_preference($id, $permlink, 'gbp_serialized');

		$this->parent->message = messenger('', $settings['pl_name'], 'saved');
	}

	function phpArrayToJsArray ($name, $array) {
		// From PHP.net
		if (is_array($array)) {
			$result = $name.' = new Array();'.n;
			foreach ($array as $key => $value)
				$result .= $this->phpArrayToJsArray($name.'[\''.$key.'\']',$value,'').n;
		} else {
			$result = $name.' = \''.$array.'\';';
		}
		return $result;
	}
}

class PermanentLinksListTabView extends GBPAdminTabView
{
	function preload () {
		register_callback(array(&$this, $this->parent->event.'_multi_edit'), $this->parent->event, $this->parent->event.'_multi_edit', 1);
		register_callback(array(&$this, $this->parent->event.'_change_pageby'), $this->parent->event, $this->parent->event.'_change_pageby', 1);
	}

	function main () {
		extract(gpsa(array('page', 'sort', 'dir', 'crit', 'search_method')));

		$event = $this->parent->event;

		$permlinks = $this->parent->get_all_permlinks();
		$total = count($permlinks);

		if ($total < 1) {
			echo graf('You haven\'t created any custom permanent links rules yet.', ' style="text-align: center;"').
				 graf('<a href="'.$this->url(array(gbp_tab => 'build'), true).'">Click here</a> to add one.', ' style="text-align: center;"');
			return;
		}

		$limit = max($this->pref('list_pageby'), 15);

		list($page, $offset, $numPages) = $this->pager($total, $limit, $page);

		if (empty($sort))
			$sort = 'pl_precedence';

		if (empty($dir))
			$dir = 'desc';

		$dir = ($dir == 'desc') ? 'desc' : 'asc';

		// Sort the permlinks via the selected column and then their names.
		foreach ($permlinks as $id => $permlink) {
			$sort_keys[$id] = $permlink['settings'][$sort];
			$name[$id] = $permlink['settings']['pl_name'];
		}
		array_multisort($sort_keys, (($dir == 'desc') ? SORT_DESC : SORT_ASC), $name, SORT_ASC, $permlinks);

		$switch_dir = ($dir == 'desc') ? 'asc' : 'desc';

		$permlinks = array_slice($permlinks, $offset, $limit);

		if (count($permlinks)) {
			echo n.n.'<form name="longform" method="post" action="index.php" onsubmit="return verify(\''.gTxt('are_you_sure').'\')">'.

				n.startTable('list').
				n.tr(
					n.column_head('name', 'pl_name', $event, true, $switch_dir, $crit, $search_method).
					hCell().
					column_head('preview', 'pl_preview', $event, true, $switch_dir, $crit, $search_method).
					column_head('precedence', 'pl_precedence', $event, true, $switch_dir, $crit, $search_method).
					hCell()
				);

			include_once txpath.'/publish/taghandlers.php';

			foreach ($permlinks as $id => $permlink) {
				extract($permlink['settings']);

				$manage = n.'<ul'.(version_compare($GLOBALS['thisversion'], '4.0.3', '<=') ? ' style="margin:0;padding:0;list-style-type:none;">' : '>').
						n.t.'<li>'.href(gTxt('edit'), $this->url(array(gbp_tab => 'build', gbp_id => $id), true)).'</li>'.
						n.'</ul>';

				echo n.n.tr(

					td(
						href($pl_name, $this->url(array(gbp_tab => 'build', gbp_id => $id), true))
					, 75).

					td($manage, 35).

					td($pl_preview, 175).
					td($pl_precedence.'&nbsp;', 50).

					td(
						fInput('checkbox', 'selected[]', $id)
					)
				);
			}
/*
			echo n.n.tr(
				tda(
					select_buttons().
					$this->permlinks_multiedit_form($page, $sort, $dir, $crit, $search_method)
				,' colspan="4" style="text-align: right; border: none;"')
			).

			n.endTable().
			n.'</form>'.
*/
			n.$this->nav_form($event, $page, $numPages, $sort, $dir, $crit, $search_method).

			n.pageby_form($event, $this->pref('list_pageby'));
		}
	}

	function pager ($total, $limit, $page) {
		if (function_exists('pager'))
			return pager($total, $limit, $page);

		// This is taken from txplib_misc.php r1588 it is required for 4.0.3 compatibitly
		$num_pages = ceil($total / $limit);
		$page = $page ? (int) $page : 1;
		$page = min(max($page, 1), $num_pages);
		$offset = max(($page - 1) * $limit, 0);
		return array($page, $offset, $num_pages);
	}

	function nav_form ($event, $page, $numPages, $sort, $dir, $crit, $method) {
		if (function_exists('nav_form'))
			return nav_form($event, $page, $numPages, $sort, $dir, $crit, $method);

		// This is basically stolen from the 4.0.3 version of includes/txp_list.php
		// - list_nav_form() for 4.0.3 compatibitly
		$nav[] = ($page > 1)
			? PrevNextLink($event, $page-1, gTxt('prev'), 'prev', $sort, $dir) : '';
		$nav[] = sp.small($page. '/'.$numPages).sp;
		$nav[] = ($page != $numPages)
			? PrevNextLink($event, $page+1, gTxt('next'), 'next', $sort, $dir) : '';
		return ($nav)
			? graf(join('', $nav), ' align="center"') : '';
	}

	function permlinks_multiedit_form ($page, $sort, $dir, $crit, $search_method) {
		$methods = array(
			'delete' => gTxt('delete'),
		);

		return event_multiedit_form($this->parent->event, $methods, $page, $sort, $dir, $crit, $search_method);
	}

	function permlinks_change_pageby () {
		$this->set_preference('list_pageby', gps('qty'));
	}

	function permlinks_multi_edit () {
		$method = gps('edit_method')
			? gps('edit_method') // From Txp 4.0.4 and greater
			: gps('method'); // Up to Txp 4.0.3

		switch ($method) {
			case 'delete':
				foreach (gps('selected') as $id) {
							$deleted[] = $this->parent->remove_permlink($id);
				}
			break;
		}

		$this->parent->message = (isset($deleted) && is_array($deleted) && count($deleted))
			? messenger('', join(', ', $deleted) ,'deleted')
			: messenger('an error occurred', '', '');
	}
}

global $gbp_pl;
$gbp_pl = new PermanentLinks('Permanent Links', 'permlinks', 'admin');
if (@txpinterface == 'public') {
	register_callback(array(&$gbp_pl, '_feed_entry'), 'rss_entry');
	register_callback(array(&$gbp_pl, '_feed_entry'), 'atom_entry');
	register_callback(array(&$gbp_pl, '_textpattern'), 'textpattern');
	register_callback(array(&$gbp_pl, '_textpattern_end'), 'textpattern_end');

	function gbp_if_regex ($atts, $thing) {
		global $pretext;
		extract(lAtts(array(
			'name' => '',
			'val'  => '',
		),$atts));
		$match = (@$pretext["permlink_regex_{$name}"] == $val);
		return parse(EvalElse($thing, $match));
	}

	function gbp_if_text ($atts, $thing) {
		global $pretext;
		extract(lAtts(array(
			'name' => '',
			'val'  => '',
		),$atts));

		$match = false;
		if (!empty($name)) {
			if (empty($val))
				$match = (isset($pretext["permlink_text_{$name}"]));
			else
				$match = (@$pretext["permlink_text_{$name}"] == $val);
		}
		return parse(EvalElse($thing, $match));
	}

	function gbp_use_pagelink ($atts, $thing = '') {
		global $gbp_pl;
		extract(lAtts(array(
			'rule' => '',
		),$atts));
		return $gbp_pl->_textpattern_end_callback(parse($thing), $rule);
	}

	function gbp_disable_permlinks ($atts, $thing = '') {
		global $gbp_pl;
		return $gbp_pl->toggle_permlink_mode('parse', $thing);
	}
}

Offline

#687 2023-01-13 23:45:18

THE BLUE DRAGON
Member
From: Israel
Registered: 2007-11-16
Posts: 637
Website

Re: gbp_permanent_links

TXP v4.8.8
Errors in gbp_admin_library

Methods with the same name as their class will not be constructors in a future version of PHP; GBPPlugin has a deprecated constructor.

Same for these:
GBPAdminTabView
GBPPreferenceTabView
GBPWizardTabView

Fix:
Change all these function names to __construct
Here’s the fixed code:

Edit!
There was also a need to change the way calling the parent contructor,
so also changed GBPAdminTabView::GBPAdminTabView to parent::__construct

// Constants
define('gbp_tab', 'tab');
define('gbp_id', 'id');

class GBPPlugin {
	// Internal variables
	var $plugin_name;
	var $title;
	var $event;
	var $message = '';
	var $tabs = array();
	var $active_tab = 0;
	var $use_tabs = false;
	var $gp = array();
	var $preferences = array();
	var $permissions = '1,2,3,4,5,6';
	var $wizard_key;
	var $wizard_installed = false;

	// Constructor
	function __construct($title = '', $event = '', $parent_tab = '') {

		global $txp_current_plugin;

		// Store a reference to this class so we can get PHP 4 to work
		if (version_compare(phpversion(), '5.0.0', '<'))
			global $gbp_admin_lib_refs; $gbp_admin_lib_refs[$txp_current_plugin] = &$this;

		// Get the plugin_name from the global txp_current_plugin variable
		$this->plugin_name = $txp_current_plugin;

		// When making a GBPAdminView there must be event attributes
		$this->event = $event;

		// Add privs for this event
		global $txp_permissions;
		$perms = @$txp_permissions[$this->permissions];
		add_privs($this->event, ($perms ? $perms : $this->permissions));

		if (@txpinterface == 'admin') {
			// We are admin-side.

			// There must be title and event attributes
			$this->title = $title;

			// The parent_tab can only be one of four things, make sure it is
			if ($event AND $title AND $parent_tab AND array_search($parent_tab, array('content', 'presentation', 'admin', 'extensions')) === false)
				$parent_tab = 'extensions';

			// Set up the get-post array
			$this->gp = array_merge(array('event', gbp_tab), $this->gp);

			// Check if our event is active, if so call preload()
			if (gps('event') == $event) {

				$this->load_preferences();

				$this->preload();

				// Tabs should be loaded by now
				if ($parent_tab && $this->use_tabs) {

					foreach (array_keys($this->tabs) as $key) {
						$tab = &$this->tabs[$key];
						$tab->php_4_fix();
						if (is_a($tab, 'GBPWizardTabView')) {
							$this->wizard_key = $key;
							$this->wizard_installed = $tab->installed();
						}
					}

					if (!$this->wizard_installed && $this->wizard_key)
						$this->active_tab = $this->wizard_key;

					// Let the active_tab know it's active and call it's preload()
					$tab = &$this->tabs[$this->active_tab];
					$tab->is_active = 1;
					$tab->preload();
				}
			}

			// Call txp functions to register this plugin
			if ($parent_tab) {
				register_tab($parent_tab, $event, $title);
				register_callback(array(&$this, 'render'), $event, null, 0);
			}
		}
		if (@txpinterface == 'public')
			$this->load_preferences();
	}

	function load_preferences () {
		/*
		Grab and store all preferences with event matching this plugin.
		*/
		global $prefs;

		// Override the default values if the prefs have been stored in the preferences table.
		$preferences = safe_rows("name, html as type",
		'txp_prefs', "event = '{$this->event}'");

		// Add the default preferences which aren't saved in the db but defined in the plugin's source.
		foreach ($this->preferences as $key => $pref) {
			$db_pref = array('name' => $this->plugin_name.'_'.$key, 'type' => $pref['type']);
			if (array_search($db_pref, $preferences) === false)
				$preferences[] = $db_pref + array('default_value' => $pref['value']);
		}

		foreach ($preferences as $name => $pref) {
			// Extract the name and type.
			extract($pref);

			$base_name = $name;
			$value = get_pref($name);

			// If there is no value then revert to the default value if it exists.
			if ((!$value || (@!$value[0] && count($value) <= 1)) && isset($default_value))
				$value = $default_value;

			// Else if this a custom type (E.g. gbp_serialized OR gbp_array_text)
			// call it's db_get method to decode it's value.
			else if (is_callable(array(&$this, $type)))
				$value = call_user_func(array(&$this, $type), 'db_get', $value);

			// Re-set the combined and decoded value to the global prefs array.
			$prefs[$base_name] = $value;

			// If the preference exists in our preference array set the new value and correct type.
			$base_name = substr($base_name, strlen($this->plugin_name.'_'));
			if (array_key_exists($base_name, $this->preferences))
				$this->preferences[$base_name] = array('value' => $value, 'type' => $type);
		}
	}

	function set_preference ($key, $value, $type = '') {
		global $prefs, $txp_current_plugin;

		// If the plugin_name or event isn't set is it safe to assume
		// $txp_current_plugin and gps('event') are correct?
		$plugin = ($this->plugin_name) ? $this->plugin_name : $txp_current_plugin;
		$event = ($this->event) ? $this->event : gps('event');

		// Set some standard db fields
		$base_name = $plugin.'_'.$key;
		$name = $base_name;

		// If a type hasn't been specified then look the key up in our preferences.
		// Else assume it's type is 'text_input'.
		if (empty($type) && array_key_exists($key, $this->preferences))
			$type = $this->preferences[$key]['type'];
		else if (empty($type))
			$type = 'text_input';

		// Set the new value to the global prefs array and if the preference exists
		// to our own preference array.
		$prefs[$name] = $value;
		if (array_key_exists($key, $this->preferences))
			$this->preferences[$key] = array('value' => $value, 'type' => $type);

		// If this preference has a custom type (E.g. gbp_serialized OR gbp_array_text)
		// call it's db_set method to encode the value.
		if (is_callable(array(&$this, $type)))
			$value = call_user_func(array(&$this, $type), 'db_set', $value);

		$this->remove_preference($name);

		// Make sure preferences which equal NULL are saved
		if (empty($value))
			set_pref($name, '', $event, 2, $type);

		$value = doSlash($value);
		set_pref($name, $value, $event, 2, $type);
	}

	function remove_preference ($key) {
		$event = $this->event;
		safe_delete('txp_prefs', "event = '$event' AND ((name LIKE '$key') OR (name LIKE '{$key}_%'))");
	}

	function gbp_serialized ($step, $value, $item = '') {
		switch (strtolower($step)) {
			default:
			case 'ui_in':
				if (!is_array($value)) $value = array($value);
				return text_input($item, implode(',', $value), 50);
			break;
			case 'ui_out':
				return explode(',', $value);
			break;
			case 'db_set':
				return base64_encode(serialize($value));
			break;
			case 'db_get':
				return unserialize(base64_decode($value));
			break;
		}
		return '';
	}

	function gbp_array_text ($step, $value, $item = '') {
		switch (strtolower($step)) {
			default:
			case 'ui_in':
				if (!is_array($value)) $value = array($value);
				return text_input($item, implode(',', $value), 50);
			break;
			case 'ui_out':
				return explode(',', $value);
			break;
			case 'db_set':
				return implode(',', $value);
			break;
			case 'db_get':
				return explode(',', $value);
			break;
		}
		return '';
	}

	function &add_tab ($tab, $is_default = NULL) {

		// Check to see if the tab is active
		if (($is_default && !gps(gbp_tab)) || (gps(gbp_tab) == $tab->event))
			$this->active_tab = count($this->tabs);

		if (is_a($tab, 'GBPWizardTabView')) {
			$tab->parent = &$this;

			// Wizard routines
			$step = gps('step');
			if (in_array($step, array('setup', 'cleanup'))) {
				$installation_steps = ($step == 'setup')
					? array_keys($tab->installation_steps)
					: array_reverse(array_keys($tab->installation_steps));
				foreach ($installation_steps as $key) {
					$function = array(&$tab, $step.'_'.$key);
					if (is_callable($function)) {
						$optional = @$tab->installation_steps[$key]['optional'];
						if (($optional && gps('optional_'.$key)) || !$optional)
							call_user_func($function);
						else
							$tab->add_report_item($tab->installation_steps[$key][$step], 'skipped');
					}
				}
			}
		}

		// Store the tab
		$this->tabs[] = $tab;

		// We've got a tab, lets assume we want to use it
		$this->use_tabs = true;

		return $this;
	}

	function preload () {
		// Override this function if you require sub tabs.
	}

	function render () {

		// render() gets called because it is specified in txp's register_callback()

		// After a callback we lose track of the current plugin in PHP 4
		global $txp_current_plugin;
		$txp_current_plugin = $this->plugin_name;

		$this->render_header();
		$this->main();

		if ($this->use_tabs) {

			$this->render_tabs();
			$this->render_tab_main();
		}

		$this->render_footer();
		$this->end();
	}

	function render_header () {

		// Render the pagetop, a txp function
		pagetop($this->title, $this->message);

		// Once a message has been used we discard it
		$this->message = '';
	}

	function render_tabs () {
		// This table, which contains the tags, will have to be changed if any improvements
		// happen to the admin interface
		$out[] = '<table cellpadding="0" cellspacing="0" width="100%" style="margin-top:-2em;margin-bottom:2em;">';
		$out[] = '<tr><td align="center" class="tabs">';
		$out[] = '<table cellpadding="0" cellspacing="0" align="center"><tr>';

		$style = 'style="padding: 0 30px;"';
		// Force the wizard to be the only tab if the plugin isn't installed
		if ($this->wizard_installed || !$this->wizard_key)
			foreach (array_keys($this->tabs) as $key) {
				// Render each tab but keep a reference to the tab so any changes made are stored
				$tab = &$this->tabs[$key];
				$out[] = $tab->render_tab();
				$fn = array(&$tab , 'get_canvas_style');
				if (is_callable($fn)) {
					$res = call_user_func($fn);
					$style = (false!==$res) ? $res : $style ;
				}
			}
		else {
			$tab = &$this->tabs[$this->wizard_key];
			$out[] = $tab->render_tab();
			$fn = array(&$tab , 'get_canvas_style');
			if (is_callable($fn)) {
				$res = call_user_func($fn);
				$style = (false!==$res) ? $res : $style ;
			}
		}

		$out[] = '</tr></table>';
		$out[] = '</td></tr>';
		$out[] = '</table><div '.$style.'>';

		echo join('', $out);
	}

	function main () {
		// Override this function
	}

	function render_tab_main () {

		// Call main() for the active_tab
		$tab = &$this->tabs[$this->active_tab];
		if (($this->wizard_installed || !$this->wizard_key || $tab->event == 'wizard') && has_privs($this->event.'.'.$tab->event))
			$tab->main();
		else
			echo '<p style="margin-top:3em;text-align:center">'.gTxt('restricted_area').'</p>';
	}

	function render_footer () {

		// A simple footer
		global $plugins_ver;
		$out[] = '</div>';
		$out[] = '<div style="padding-top: 3em; text-align: center; clear: both;">';
		$out[] = $this->plugin_name;
		if (@$plugins_ver[$this->plugin_name])
		 	$out[] = ' &#183; ' . $plugins_ver[$this->plugin_name];
		$out[] = '</div>';

		echo join('', $out);
	}

	function end () {
		// Override this function
	}

	function form_inputs () {

		$out[] = eInput($this->event);

		if ($this->use_tabs) {

			$tab = $this->tabs[$this->active_tab];
			$out[] = hInput(gbp_tab, $tab->event);
		}

		return join('', $out);
	}

	function url ($vars = array(), $gp = false) {
		/*
		Expands $vars into a get style url and redirects to that location. These can be
		overriden with the current get, post, session variables defined in $this->gp
		by setting $gp = true
		NOTE: If $vars is not an array or is empty then we assume $gp = true.
		*/

		if (!is_array($vars))
			$vars = gpsa($this->gp);
		else if ($gp || !count($vars))
			$vars = array_merge(gpsa($this->gp), $vars);

		foreach ($vars as $key => $value) {
			if (!empty($value))
				$out[] = $key.'='.$value;
		}

		$script = hu.basename(txpath).'/index.php';
		return $script . (isset($out)
			? '?'.join('&', $out)
			: '');
	}

	function redirect ($url = '', $status = 303) {
		/*
		If $vars is an array, use url() to expand as an GET style url and redirect to
		that location using the HTTP status code definition defined by $status.
		*/

		static $status_definitions = array (
			301 => "Moved Permanently",
			302 => "Found",
			303 => "See Other",
			307 => "Temporary Redirect"
		);

		if (!in_array($status, array_keys($status_definitions)))
			$status = 303;

		if (is_array($url))
			$url = $this->url($url);
		else {
			$url_details = parse_url($url);
			if (!@$url_details['scheme'])
				$url = 'http://'.$url;
		}

		if (empty($_SERVER['FCGI_ROLE']) and empty($_ENV['FCGI_ROLE'])) {
			header('HTTP/1.1 '.$status.' '.$status_definitions[$status]);
			header('Status: '.$status);
			header('Location: '.$url);
			header('Connection: close');
			header('Content-Length: 0');
			exit(0);
			} else {
			global $sitename;
			$url = htmlspecialchars($url);
			echo <<<END
			<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
			<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
			<head>
				<title>$sitename</title>
				<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
				<meta http-equiv="refresh" content="0;url=$url" />
			</head>
			<body>
			<a href="$url">{$status_definitions[$status]}</a>
			</body>
			</html>
END;
		}
	}

	function pref ($key) {
		global $prefs, $txp_current_plugin;

		$plugin = ($this->plugin_name) ? $this->plugin_name : $txp_current_plugin;
		$key = $plugin.'_'.$key;

		if (@$this->preferences[$key])
			return $this->preferences[$key]['value'];
		if (@$prefs[$key])
			return $prefs[$key];
		return NULL;
	}
}

class GBPAdminTabView {
	//	Internal variables
	var $title;
	var $event;
	var $is_active;
	var $parent;
	var $permissions = '1,2,3,4,5,6';

	//	Constructor
	function __construct($title, $event, &$parent, $is_default = NULL) {

		$this->title = (function_exists('mb_convert_case'))
			? mb_convert_case($title, MB_CASE_TITLE, "UTF-8")
			: ucwords($title);

		$this->event = $event;

		// Note: $this->parent only gets set correctly for PHP 5
		$this->parent =& $parent->add_tab($this, $is_default);

		// Add privs for this tab
		global $txp_permissions;
		$perms = @$txp_permissions[$this->permissions];
		add_privs($this->parent->event.'.'.$this->event, ($perms ? $perms : $this->permissions));
	}

	function php_4_fix () {

		// Fix references in PHP 4 so sub tabs can access their parent tab
		if (version_compare(phpversion(), '5.0.0', '<')) {
			global $txp_current_plugin, $gbp_admin_lib_refs;
			$this->parent =& $gbp_admin_lib_refs[$txp_current_plugin];
		}
	}

	function preload () {
		// Override this function
	}

	function render_tab () {

		// Grab the url to this tab
		$url = $this->parent->url(array(gbp_tab => $this->event), true);

		// Will need updating if any improvements happen to the admin interface
		$out[] = '<td class="' . ($this->is_active ? 'tabup' : 'tabdown2');
		$out[] = '" onclick="window.location.href=\'' .$url. '\'">';
		$out[] = '<a href="' .$url. '" class="plain">' .$this->title. '</a></td>';

		return join('', $out);
	}

	function main () {
		// Override this function
	}

	function pref ($key) {
		return @$this->parent->pref($key);
	}

	function redirect ($vars = '') {
		$this->parent->redirect($vars);
	}

	function set_preference ($key, $value, $type = '') {
		return $this->parent->set_preference($key, $value, $type);
	}

	function remove_preference ($key) {
		return $this->parent->remove_preference($key);
	}

	function url ($vars, $gp = false) {
		return $this->parent->url($vars, $gp);
	}

	function form_inputs () {
		return $this->parent->form_inputs();
	}
}

class GBPPreferenceTabView extends GBPAdminTabView {

	var $permissions = 'prefs';

	function __construct(&$parent, $is_default = NULL) {
		// Call the parent constructor
		parent::__construct(gTxt('tab_preferences'), 'preference', $parent, $is_default);
	}

	function preload () {
		if (ps('step') == 'prefs_save') {
			foreach ($this->parent->preferences as $key => $pref) {
				extract($pref);
				$value = ps($key);
				if (is_callable(array(&$this->parent, $type)))
					$value = call_user_func(array(&$this->parent, $type), 'ui_out', $value);
				$this->parent->set_preference($key, $value);
			}
		}
	}

	function main () {
		// Make txp_prefs.php happy :)
		global $event;
		$event = $this->parent->event;

		include_once txpath.'/include/txp_prefs.php';

		echo
			'<form action="index.php" method="post">',
			startTable('list');

		foreach ($this->parent->preferences as $key => $pref) {
			extract($pref);

			$out = tda(gTxt($key), ' style="text-align:right;vertical-align:middle"');

			switch ($type) {
				case 'text_input':
					$out .= td(pref_func('text_input', $key, $value, 20));
				break;
				default:
					if (is_callable(array(&$this->parent, $type)))
						$out .= td(call_user_func(array(&$this->parent, $type), 'ui_in', $value, $key));
					else
						$out .= td(pref_func($type, $key, $value, 50));
				break;
			}

			$out .= tda($this->popHelp($key), ' style="vertical-align:middle"');
			echo tr($out);
		}

		echo
			tr(tda(fInput('submit', 'Submit', gTxt('save_button'), 'publish'), ' colspan="3" class="noline"')),
			endTable(),
			$this->parent->form_inputs(),
			sInput('prefs_save'),
			'</form>';
	}

	function popHelp ($helpvar) {
		$script = hu.basename(txpath).'/index.php';
		return '<a href="'.$script.'?event=plugin&step=plugin_help&name='.$this->parent->plugin_name.'#'.$helpvar.'" class="pophelp">?</a>';
	}
}

class GBPWizardTabView extends GBPAdminTabView {

	var $installation_steps = array();
	var $wiz_report = array();
	var $permissions = 'admin.edit';

	function __construct(&$parent, $is_default = NULL, $title = 'Wizards') {
		global $textarray;

		#
		#	Get the strings and merge into the textarray before we get the steps...
		#
		$strings = $this->get_strings();
		$textarray = array_merge($strings , $textarray);

		#
		#	Now get the steps...
		#
		$this->installation_steps = $this->get_steps();

		// Call the parent constructor
		parent::__construct($title, 'wizard', $parent, $is_default);
	}

	function get_steps () {
		#
		#	Override this method in derived classes to return the appropriate setup/cleanup steps.
		#
		$steps = array(
			'basic' 		=> array('setup' => 'Basic setup step', 'cleanup' => 'Basic cleanup step'),
			'optional'		=> array('setup' => 'Optional setup step', 'cleanup' => 'Optional cleanup step', 'optional' => true , 'checked' => 0),
			'has_options'	=> array('setup' => 'Setup step with a option', 'cleanup' => 'Cleanup step with a option', 'has_options' => true),
		);
		return $steps;
	}

	function get_strings ($language = '') {
		#
		#	Override this function in derived classes to define/change the set of strings to
		# inject into $textarray to localise the wizard.
		#
		$strings = array(
			'gbp_adlib_wiz-version_errors'		=> 'Version Errors',
			'gbp_adlib_wiz-version_reason'		=> 'This plugin cannot operate in this installation because&#8230;',
			'gbp_adlib_wiz-version_item'		=> 'It requires <strong class="failure">{name} {min}</strong> or above, current install is {current}.',
			'gbp_adlib_wiz-setup' 				=> 'Setup',
			'gbp_adlib_wiz-setup_steps' 		=> 'The following setup steps will be taken&#8230;',
			'gbp_adlib_wiz-setup_report'		=> 'Setup Report&#8230;',
			'gbp_adlib_wiz-cleanup' 			=> 'Cleanup',
			'gbp_adlib_wiz-cleanup_steps' 		=> 'The following cleanup steps will be taken&#8230;',
			'gbp_adlib_wiz-cleanup_report'		=> 'Cleanup Report&#8230;',
			'gbp_adlib_wiz-cleanup_next' 		=> 'The plugin can now be disabled and/or uninstalled.',
			'gbp_adlib_wiz-done' 				=> 'Done',
			'gbp_adlib_wiz-skipped'				=> 'Skipped',
			'gbp_adlib_wiz-failed'				=> 'Failure',
			'gbp_adlib_wiz-step_basic'			=> 'Basic Step',
			'gbp_adlib_wiz-step_optional'		=> 'Optional step',
			'gbp_adlib_wiz-step_complex'		=> 'Step with option(s)',
			'gbp_adlib_wiz-step_complex_txt'	=> 'This {step} step has an option/options.',
		);
		return $strings;
	}

	function versions_ok () {
		$msg = '';

		#
		#	Check the plugin can run in this environment.
		#
		#	Return: TRUE -> Yes.
		#	HTML formatted string -> No, and explain why.
		#
		$tests = $this->get_required_versions();
		if (count($tests))
			foreach ($tests as $name => $versions) {
				if( array_key_exists( 'custom_handler' , $versions ) && is_callable( $versions['custom_handler'] ) ) {
					#
					# Allow derived classes to define their own checking routines...
					#
					$fn = $versions['custom_handler'];
					$res = call_user_func( $fn , $name , $versions );
					if( $res )
						$msg[] = tag($res , 'li', ' style="text-align: left; padding-top: 0.75em;"');
				}
				else {
					if (version_compare($versions['current'], $versions['min'] , '<')) {
						$res = gTxt('gbp_adlib_wiz-version_item' , array('{name}' => $name , '{min}' => $versions['min'] , '{current}' => $versions['current']));
						$msg[] = tag($res , 'li', ' style="text-align: left; padding-top: 0.75em;"');
					}
				}
			}

		if (!empty($msg))
			return tag(join('' , $msg) , 'ol');

		return true;
	}

	function get_required_versions () {
		global $prefs;

		#
		#	Override this function to return an array of tests to be carried out.
		#
		$tests = array('TxP' => array(
			'current'	=> $prefs['version'],
			'min'		=> '4.0.3',
		));
		return $tests;
	}

	function main () {
		$out[] = '<style type="text/css"> .success { color: #009900; } .failure { color: #FF0000; } .skipped { color: #0000FF; } </style>';
		$out[] = '<div style="border: 1px solid gray; width: 50em; text-align: center; margin: 1em auto; padding: 1em; clear: both;">';

		$feaildset_style = ' style="text-align: left; padding: 1em 0"';

		$step = gps('step');
		if (empty($step)) {
			$result = $this->versions_ok();
			if (is_string($result))
				$step = 'version_error';
			else
				$step = ($this->installed()) ? 'cleanup-verify' : 'setup-verify';

			$_POST['step'] = $step;
		}

		switch ($step) {
			case 'version_error':
				$out[] = hed(gTxt('gbp_adlib_wiz-version_errors') , 1);
				$out[] = graf(gTxt('gbp_adlib_wiz-version_reason'));
				$out[] = $result;
			break;

			case 'setup-verify':
			// Render the setup wizard initial step...
				$out[] = hed(gTxt('gbp_adlib_wiz-setup') , 1);
				$out[] = graf(gTxt('gbp_adlib_wiz-setup_steps'));
				$out[] = tag(tag($this->wizard_steps('setup') , 'ol') , 'fieldset', $feaildset_style);
				$out[] = fInput('submit', '', gTxt('gbp_adlib_wiz-setup'), '');
				$out[] = $this->form_inputs();
				$out[] = sInput('setup');
			break;

			case 'setup':
			// Render the post-setup screen...
				$out[] = hed(gTxt('gbp_adlib_wiz-setup_report') , 1);
				$out[] = tag($this->wizard_report() , 'fieldset', $feaildset_style);
				$out[] = fInput('submit', '' , gTxt('next') , '');
				$out[] = eInput($this->parent->event);
				$out[] = hInput(gbp_tab, 'preference');
			break;

			case 'cleanup-verify':
			// Render the cleanup wizard initial step...
				$out[] = hed(gTxt('gbp_adlib_wiz-cleanup') , 1);
				$out[] = graf(gTxt('gbp_adlib_wiz-cleanup_steps'));
				$out[] = tag(tag($this->wizard_steps('cleanup') , 'ol') , 'fieldset', $feaildset_style);
				$out[] = fInput('submit', '', gTxt('gbp_adlib_wiz-cleanup'), '');
				$out[] = $this->form_inputs();
				$out[] = sInput('cleanup');
			break;

			case 'cleanup':
			// Render the post-cleanup screen...
				$out[] = hed(gTxt('gbp_adlib_wiz-cleanup_report') , 1);
				$out[] = tag($this->wizard_report() , 'fieldset', $feaildset_style);
				$out[] = graf(gTxt('gbp_adlib_wiz-cleanup_next'));
				$out[] = fInput('submit', '' , gTxt('next') , '');
				$out[] = eInput('plugin');
			break;
		}

		$out[] = '</div>';

		$verify = (in_array($step, array('setup-verify', 'cleanup-verify')))
			? "verify('".doSlash(gTxt('are_you_sure'))."')"
			: '';

		echo form(join(n, $out), '', $verify);
	}

	function installed () {
		return false;
	}

	function wizard_steps ($step) {
		$step_details = '';

		foreach ($this->installation_steps as $key => $detail) {
			if (@$detail[$step]) {
				$options = '';
				if (@$detail['has_options']) {
					$function = array(&$this, 'option_'.$key);
					if (is_callable($function))
						$options = n.tag(call_user_func($function, $step), 'span', ' id="wizard_'.$key.'" style="display: block; margin-right: 1em; padding: 0.5em; background-color: #eee;"');
				}

				$checkbox = '';
				if (@$detail['optional']) {
					$checked = (isset($detail['checked'])) ? $detail['checked'] : 0 ;
					$checkbox = checkbox2('optional_'.$key, $checked, ($options ? '" onclick="toggleDisplay(\'wizard_'.$key.'\');' : ''));
				}

				$step_details .= n.tag(graf(
					tag($detail[$step].$checkbox, 'label').$options
				), 'li');
			}
		}

		return $step_details.n;
	}

	function wizard_report () {
		// Render the wizard report as an ordered list. There maybe
		// 'sub' reports which we need to also render as ordered lists
		$out = array();
		foreach ($this->wiz_report as $report) {
			$out_sub = array();

			// Skip the first element as it is in fact the parent report
			next($report);

			// Lets generate a sub report - if there are more elements
			while (list($key, $report_sub) = each($report))
				$out_sub[] = tag($report_sub , 'li');

			// Check to see if we actually have a sub report - tag it as necessary
			$out_sub = (count($out_sub) > 0)
				? tag(join(n , $out_sub), 'ol')
				: '';

			$out[] = tag($report[0] . $out_sub , 'li');
		}
		return tag(join(n , $out) , 'ol');
	}

	function add_report_item ($string , $ok = NULL, $sub = false) {
		if (isset($ok)) {
			switch ($ok) {
				case '1' :
					$class = 'success';
					$okfail = gTxt('gbp_adlib_wiz-done');
				break;

				default :
				case '0' :
					$class = 'failure';
					$okfail = gTxt('gbp_adlib_wiz-failed');
				break;

				case 'skipped' :
					$class = 'skipped';
					$okfail = gTxt('gbp_adlib_wiz-skipped');
				break;
			}
			$okfail = ' : <span class="'.$class.'">'.tag($okfail, 'strong').'</span>';
		}

		$line = graf($string . (isset($okfail) ? $okfail : ''));

		if ($sub && count($this->wiz_report) > 0)
			$this->wiz_report[count($this->wiz_report) - 1][] = $line;
		else
			$this->wiz_report[] = array($line);
	}

	function setup_basic () {
		$this->add_report_item(gTxt('gbp_adlib_wiz-step_basic'), true);
	}

	function cleanup_basic () {
		$this->add_report_item(gTxt('gbp_adlib_wiz-step_basic'), false);
	}

	function setup_optional () {
		$this->add_report_item(gTxt('gbp_adlib_wiz-step_optional'), true);
	}

	function cleanup_optional () {
		$this->add_report_item(gTxt('gbp_adlib_wiz-step_optional'), false);
	}

	function setup_has_options () {
		$this->add_report_item(gTxt('gbp_adlib_wiz-step_complex'), true);
	}

	function cleanup_has_options () {
		$this->add_report_item(gTxt('gbp_adlib_wiz-step_complex'), false);
	}

	function option_has_options ($step) {
		return graf(gTxt('gbp_adlib_wiz-step_complex_txt' , array('{step}' => $step))).yesnoRadio('wizard_has_options_test', 1);
	}
}

Last edited by THE BLUE DRAGON (2023-01-14 13:39:04)

Offline

#688 2024-07-24 14:26:06

THE BLUE DRAGON
Member
From: Israel
Registered: 2007-11-16
Posts: 637
Website

Re: gbp_permanent_links

Hi, I’m trying to get this plugin to work on PHP 8 and would like to get your help please.

There are currently 2 issues in the gbp_admin_library.

The first issue I have already solved, by just replacing this:

// Add privs for this event
global $txp_permissions;
$perms = @$txp_permissions[$this->permissions];
add_privs($this->event, ($perms ? $perms : $this->permissions));

with this:

// Add privs for this event
add_privs($this->event, '1,2,3,4,5,6');

same for replacing this:

// Add privs for this tab
global $txp_permissions;
$perms = @$txp_permissions[$this->permissions];
add_privs($this->parent->event.'.'.$this->event, ($perms ? $perms : $this->permissions));

with this:

// Add privs for this tab
add_privs($this->parent->event.'.'.$this->event, '1,2,3,4,5,6');

As you can understand there is an issue where the code can’t reach the permissions variable, so I have just copied the variable value directly to the two add_privs function calls to solve this one, not a real fix but it works.

—————————————————————————-

Now for the second issue, is with this code:

function pref ($key) {
	global $prefs, $txp_current_plugin;

	$plugin = ($this->plugin_name) ? $this->plugin_name : $txp_current_plugin;
	$key = $plugin.'_'.$key;

	if (@$this->preferences[$key])
		return $this->preferences[$key]['value'];
	if (@$prefs[$key])
		return $prefs[$key];
	return NULL;
}

It returns this error: Trying to access array offset on value of type null
Here I have no idea what to do and I would like to get your help solving this please.

Offline

#689 2024-07-24 18:21:57

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 4,726
Website

Re: gbp_permanent_links

I usually have to insert dmp($youvar); statements into the code to find out which variable has no content. If you don’t get any output dumped to the screen, Stef showed me a trick recently for directing dmp output to a file. Add the following line to your /textpattern/config.php:

// dmp() outputs to /textpattern/tmp/debug.txt
define('txpdmpfile', 'debug.txt');

and then check /textpattern/tmp/debug.txt for any output when you load the relevant page or admin panel. Remember to set it back or disable it, or else that file will quietly grow and grow in size without you realising.

In the past, the @ signs in the code silenced errors of this kind but no longer do so in PHP8, so they can be removed. That may be why you didn’t see them before. With some trial and error, you may identify the cases that are triggering the error, and once you’ve found the culprit, you can then guard against the “illegal” situation with an if (empty($myvar)) { … } or a relevant default value, or some other method that applies to the situation.

—-

An aside / another thought:

This plugin was immensely capable but I’ve also not got it working with newer versions. Ultimately, I mostly only ever needed it to cater for one or two specific situations on a site where I needed a slightly different url pattern, and for that it is a huge plugin. In the end I found it much easier, with a little help, to write a considerably smaller site-specific router plugin. There are a few callbacks that allow you quite specifically dissect the URL parts and then set Textpattern’s context variables before passing it on for handling via Textpattern. If the cases you are trying to cover and not overly complex, perhaps that’s an easier avenue to follow.

Here’s an example from the now defunct “txpmag” website that prior to that used gbp_permanent_links:

<?php
if (defined('txpinterface') && txpinterface == 'public') {
  // callback for custom url handling
  register_callback('txpmag_url_handler', 'pretext', '');  // end with , '' or with 1 if rewriting REQUEST_URI

}

/**
 * Custom url handler as $pretext callback.
 *
 * Overrides permlink_mode for
 * section = issues/{issue-number} and saves $pretext['issue']
 * section = issues/{yyyy/mm} and saves $pretext['month']
 * section = people/{person-name} and saves $pretext['member']
 *
 */

function txpmag_url_handler()
{
    global $pretext;

    // Set to true for debug output
    $debug = false;

    // debug: Incoming Request_uri
    if ($debug) {
        dmp("Request_uri: " . serverSet('REQUEST_URI'));
    }

    // Url parts are stored in $pretext[1],  $pretext[2],  $pretext[3] …

    switch ($pretext[1]) {  // Test for sections
        case 'issues':
            // Set section
            $pretext['s'] = 'issues';

            if (isset($pretext[2]) && is_numeric($pretext[2])) {
                // Anything above 2001 is assumed to be a year. Limits the mag to 2001 issues!
                if ($pretext[2] < 2002) {
                    // Set issue number
                    $pretext['issue'] = $pretext[2];
                } else {
                    // Year
                    $date[] = $pretext[2];
                    if (isset($pretext[3]) && is_numeric($pretext[3])) {
                        // Month
                        $date[] = str_pad( (int) $pretext[3], 2, '0', STR_PAD_LEFT);
                        if (isset($pretext[4]) && is_numeric($pretext[4])) {
                           // Day
                           $date[] = str_pad( (int) $pretext[4], 2, '0', STR_PAD_LEFT);
                        }
                    }
                    // Set txp’s month filter
                    $pretext['month'] = join('-', $date);
                }
            }
            break;
        case 'people':
            // Set section
            $pretext['s'] = 'people';
            if (isset($pretext[2])) {
                // Set person name
                $pretext['member'] = $pretext[2];
            }
            break;
    }

    // debug: $pretext after processing
    if ($debug) {
        print "<pre>\$pretext: ";
        print_r($pretext);
        print "</pre>";
    }
}

TXP Builders – finely-crafted code, design and txp

Offline

#690 2024-07-28 12:09:58

THE BLUE DRAGON
Member
From: Israel
Registered: 2007-11-16
Posts: 637
Website

Re: gbp_permanent_links

Thank you very much for all the info, unfortunately it is way above my knowledge, but I’m trying to work with it somehow.

All I really need is to allow adding /en/ to the begining of the site path.
For example: https://example.com/en
And https://example.com/en/section/id/title.
The /en part should be ignored and not be treated as a dynamic part, but just as a plain text.
Then in my code I can check it there is /en in the path and save it as a variable and then determine which content to load.
That is for what I use this plugin for, and it works good in PHP7.4 but not in PHP8, so if there is a simple code for doing this then it will be much better then using a plugin for that please.
Is there a way to see the output code for the rules I have created in this plugin and just use that specific code please?

Here’s the plugin rule content from my database, this isn’t really not helping as it isn’t the final output but just the settings.

a:2:{s:8:"settings";a:11:{s:7:"pl_name";s:7:"English";s:13:"pl_precedence";s:1:"0";s:10:"pl_preview";s:14:"en/section/id/";s:11:"con_section";s:0:"";s:12:"con_category";s:0:"";s:11:"des_section";s:0:"";s:12:"des_category";s:0:"";s:8:"des_page";s:0:"";s:12:"des_permlink";s:0:"";s:8:"des_feed";s:0:"";s:12:"des_location";s:0:"";}s:10:"components";a:3:{i:0;a:8:{s:4:"type";s:4:"text";s:6:"custom";s:0:"";s:8:"category";s:0:"";s:4:"name";s:2:"en";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:5:"regex";s:0:"";s:4:"text";s:2:"en";}i:1;a:8:{s:4:"type";s:7:"section";s:6:"custom";s:0:"";s:8:"category";s:0:"";s:4:"name";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:5:"regex";s:0:"";s:4:"text";s:0:"";}i:2;a:8:{s:4:"type";s:2:"id";s:6:"custom";s:0:"";s:8:"category";s:1:"1";s:4:"name";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:5:"regex";s:0:"";s:4:"text";s:0:"";}}}

Offline

#691 2024-07-28 13:30:42

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 4,726
Website

Re: gbp_permanent_links

I think I once tried that one out before and it worked. Just need to find it… I’ll post back if I do.

EDIT: Hah, I did do it before … and it was for you! I guess we both forgot about it.

It was called jcr_langprefix_url and we had an email exchange about it in early August 2021. At the time it needed an extra workaround to not include the local web server path when breaking down the url into its parts. If there’s a reason why that still didn’t / doesn’t work, let me know. Maybe we can find a satisfactory solution.

I think this was the last iteration of the plugin we had at the time:

# Name: jcr_langprefix_url v0.1.1
# Custom language prefix url scheme and permlink handler
# Author: jcr / txpbuilders
# URL: http://txp.builders/
# Recommended load order: 4
# .....................................................................
# This is a plugin for Textpattern - https://textpattern.com/
# To install: textpattern > admin > plugins
# Paste the following text into the "Install plugin" box:
# .....................................................................

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
d/DhH/WdEnQMMgAA

TXP Builders – finely-crafted code, design and txp

Offline

#692 2024-07-29 21:32:01

THE BLUE DRAGON
Member
From: Israel
Registered: 2007-11-16
Posts: 637
Website

Re: gbp_permanent_links

jakob wrote #337486:

I think I once tried that one out before and it worked. Just need to find it… I’ll post back if I do.

EDIT: Hah, I did do it before … and it was for you! I guess we both forgot about it.

Wow, I have completely forgot about it, I have just took a look at the emails between us from 3 years ago and didn’t remembered any of that at all, so sorry about it 🙏😅🤦♂️

Thank you so much, I gave it a try and it seems to work really good 👍👏

Offline

#693 2024-07-30 08:54:45

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 4,726
Website

Re: gbp_permanent_links

Cool! Pleased it helps. Not sure whether to publish it properly as it only handles the url routing and permlink generation side of things. It’s just behind the scenes tools: you have to construct your site to respond properly to the current language. You also have the particularity on your site that your “home” language has no language prefix.

It is mostly suited to sites where one article has multiple languages stored within the same article, i.e. using custom fields or some other method where a single article acts as the base url for different language content pulled in from elsewhere – for example page-less sections as parallel language articles linked by a parent custom field as Oleg has described here.

I’d be interested to hear how you have structured your multi-lingual site and/or content.

Most of the small-ish multi-language sites I have made have parallel url structures in their respective languages that reference the other language via a custom field (in articles, sections, etc.), so I’ve rarely used the setup you have here.


TXP Builders – finely-crafted code, design and txp

Offline

#694 2024-08-20 16:43:20

THE BLUE DRAGON
Member
From: Israel
Registered: 2007-11-16
Posts: 637
Website

Re: gbp_permanent_links

Thank you, I do use it with custom fields.
I created a form and named it “article_vars” and in it I’m doing so:

<txp:variable name="article_title"><txp:title /></txp:variable>
<txp:variable name="article_body"><txp:body /></txp:variable>

<txp:if_variable name="lang" value="en">
    <txp:variable name="article_title"><txp:custom_field name="title_en" /></txp:variable>
    <txp:variable name="article_body"><txp:custom_field name="body_en" /></txp:variable>
</txp:if_variable>

In my “header” form I’m doing so:

<txp:variable name="lang" value="he" />
<txp:variable name="langName" value="Hebrew" />

<txp:if_plugin name="jcr_langprefix_url">
	<txp:variable name="lang_prefix"><txp:jcr_page_langprefix /></txp:variable>

	<txp:if_variable name="lang_prefix" value="en">
		<txp:variable name="lang" value="en" />
		<txp:variable name="langName" value="English" />
	</txp:if_variable>
</txp:if_plugin>

Then I’m ouptput the “article_vars” form where needed, for example:

<html>
<body>
    <txp:output_form form="header" />

    <txp:article> (or article_custom)

        <txp:output_form form="article_vars" />

        <h1><txp:variable name="article_title" /></h1>
        <div><txp:variable name="article_body" /></div>

    </txp:article>

    <txp:output_form form="footer" />
</body>
</html>

Offline

#695 2024-08-20 21:05:29

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 4,726
Website

Re: gbp_permanent_links

Thanks for the explanation. I’ve done something similar for a recent site. I have a page_vars form at the very top of the page, a bit like you have for articles, that sets various variables including the page’s lang variable.

Then, in my page template I use different forms, different custom_fields, and different variables, even different logo filenames that end in _en or _de. You can then build your form attributes in txp:article, txp:output_form, txp:section_list etc. using the page’s language variable, e.g. form='myform_<txp_variable name="lang" />'.
The advantage is you end up with fewer if … else constructions in your template.

For custom strings I used smd_babel and made a tiny plugin that is essentially the same “txp:text” but with a lang attribute. Bloke may make that part of the plugin in a forthcoming version.


TXP Builders – finely-crafted code, design and txp

Offline

#696 2024-08-20 22:11:27

THE BLUE DRAGON
Member
From: Israel
Registered: 2007-11-16
Posts: 637
Website

Re: gbp_permanent_links

jakob wrote #337687:

The advantage is you end up with fewer if … else constructions in your template.

Thanks for the tips 👍

Offline

Board footer

Powered by FluxBB